├── .gitignore ├── README.md └── FractalFlame ├── Pixel.pde ├── Palette.pde ├── Variation.pde └── FractalFlame.pde /.gitignore: -------------------------------------------------------------------------------- 1 | RenderMany/ 2 | .DS_Store 3 | *.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FractalFlame 2 | Exploring Fractal Flame algorithm 3 | 4 | https://flam3.com/flame_draves.pdf 5 | -------------------------------------------------------------------------------- /FractalFlame/Pixel.pde: -------------------------------------------------------------------------------- 1 | class Pixel { 2 | float value; 3 | float r, g, b; 4 | 5 | Pixel() { 6 | value = 0; 7 | r = g = b = 0; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /FractalFlame/Palette.pde: -------------------------------------------------------------------------------- 1 | color[] palette ={ 2 | color(11, 106, 136), 3 | color(45, 197, 244), 4 | color(11, 106, 136), 5 | color(112, 50, 126), 6 | color(146, 83, 161), 7 | color(164, 41, 99), 8 | color(236, 1, 90), 9 | color(240, 99, 164), 10 | color(241, 97, 100), 11 | color(248, 158, 79), 12 | color(252, 238, 33) 13 | }; 14 | 15 | color randomColor() { 16 | int i = floor(random(palette.length)); 17 | return palette[i]; 18 | } 19 | -------------------------------------------------------------------------------- /FractalFlame/Variation.pde: -------------------------------------------------------------------------------- 1 | class Variation { 2 | float[] preTransform = new float[6]; 3 | float[] postTransform = new float[6]; 4 | float colorVal; 5 | String name; 6 | 7 | Variation setColor(float val) { 8 | colorVal = val; 9 | return this; 10 | } 11 | 12 | Variation setTransform(float[] pre) { 13 | this.preTransform = pre; 14 | return this; 15 | } 16 | 17 | Variation() { 18 | for (int i = 0; i < 6; i++) { 19 | this.preTransform[i] = random(-1, 1); 20 | this.postTransform[i] = random(-1, 1); 21 | } 22 | } 23 | 24 | PVector affine(PVector v, float[] coeff) { 25 | float x = coeff[0] * v.x + coeff[1] * v.y + coeff[2]; 26 | float y = coeff[3] * v.x + coeff[4] * v.y + coeff[5]; 27 | return new PVector(x, y); 28 | } 29 | 30 | PVector f(PVector v) { 31 | return v.copy(); 32 | } 33 | 34 | PVector flame(PVector input) { 35 | // Pre transform 36 | PVector v = this.affine(input, this.preTransform); 37 | 38 | // Apply variation 39 | v = this.f(v); 40 | 41 | // Color averages 42 | v.z = (input.z + colorVal) * 0.5; 43 | 44 | // Skipping post transform for testing 45 | // v = this.affine(v, this.postTransform); 46 | return v; 47 | } 48 | } 49 | 50 | class Fisheye extends Variation { 51 | Fisheye() { 52 | super(); 53 | this.name = "Fisheye"; 54 | } 55 | 56 | PVector f(PVector v) { 57 | float r = v.x * v.x + v.y * v.y; 58 | PVector newV = new PVector(v.y, v.x); 59 | newV.mult(2 / (r+1)); 60 | return newV; 61 | } 62 | } 63 | 64 | class Diamond extends Variation { 65 | Diamond() { 66 | super(); 67 | this.name = "Diamond"; 68 | } 69 | 70 | PVector f(PVector v) { 71 | float r = v.x * v.x + v.y * v.y; 72 | float theta = atan(v.x / v.y); 73 | float x = sin(theta) * cos(r); 74 | float y = cos(theta) * sin(r); 75 | return new PVector(x, y); 76 | } 77 | } 78 | 79 | class Hyperbolic extends Variation { 80 | Hyperbolic() { 81 | super(); 82 | this.name = "Hyperbolic"; 83 | } 84 | 85 | PVector f(PVector v) { 86 | float r = v.x * v.x + v.y * v.y; 87 | float theta = atan(v.x / v.y); 88 | float x = sin(theta) / r; 89 | float y = r * cos(theta); 90 | return new PVector(x, y); 91 | } 92 | } 93 | 94 | class Spiral extends Variation { 95 | Spiral() { 96 | super(); 97 | this.name = "Spiral"; 98 | } 99 | 100 | PVector f(PVector v) { 101 | float r = v.x * v.x + v.y * v.y; 102 | float theta = atan(v.x / v.y); 103 | float x = (1/r) * (cos(theta) + sin(r)); 104 | float y = (1/r) * (sin(theta) - cos(r)); 105 | return new PVector(x, y); 106 | } 107 | } 108 | 109 | 110 | class Disc extends Variation { 111 | Disc() { 112 | super(); 113 | this.name = "Disc"; 114 | } 115 | 116 | PVector f(PVector v) { 117 | float r = v.x * v.x + v.y * v.y; 118 | float theta = atan(v.x / v.y); 119 | float x = (theta/PI) * sin(PI * r); 120 | float y = (theta/PI) * cos(PI * r); 121 | return new PVector(x, y); 122 | } 123 | } 124 | 125 | class Heart extends Variation { 126 | Heart() { 127 | super(); 128 | this.name = "Heart"; 129 | } 130 | 131 | PVector f(PVector v) { 132 | float r = v.x * v.x + v.y * v.y; 133 | float theta = atan(v.x / v.y); 134 | float x = r * sin(theta * r); 135 | float y = -r * cos(theta * r); 136 | return new PVector(x, y); 137 | } 138 | } 139 | 140 | 141 | class Hankerchief extends Variation { 142 | Hankerchief() { 143 | super(); 144 | this.name = "Hankerchief"; 145 | } 146 | 147 | PVector f(PVector v) { 148 | float r = v.x * v.x + v.y * v.y; 149 | float theta = atan(v.x / v.y); 150 | float x = r * sin(theta + r); 151 | float y = r * cos(theta - r); 152 | return new PVector(x, y); 153 | } 154 | } 155 | 156 | class Polar extends Variation { 157 | Polar() { 158 | super(); 159 | this.name = "Polar"; 160 | } 161 | 162 | PVector f(PVector v) { 163 | float r = v.x * v.x + v.y * v.y; 164 | float theta = atan(v.x / v.y); 165 | float x = theta / PI; 166 | float y = r - 1; 167 | return new PVector(x, y); 168 | } 169 | } 170 | 171 | 172 | class HorseShoe extends Variation { 173 | HorseShoe() { 174 | super(); 175 | this.name = "HorseShoe"; 176 | } 177 | 178 | PVector f(PVector v) { 179 | float r = v.x * v.x + v.y * v.y; 180 | float x = (v.x - v.y) * (v.x + v.y); 181 | float y = 2 * v.x * v.y; 182 | return new PVector(x / r, y / r); 183 | } 184 | } 185 | 186 | class Spherical extends Variation { 187 | Spherical() { 188 | super(); 189 | this.name = "Spherical"; 190 | } 191 | 192 | PVector f(PVector v) { 193 | float r = v.x*v.x + v.y*v.y; 194 | return new PVector(v.x / r, v.y / r); 195 | } 196 | } 197 | 198 | 199 | class Swirl extends Variation { 200 | Swirl() { 201 | super(); 202 | this.name = "Swirl"; 203 | } 204 | 205 | PVector f(PVector v) { 206 | float r = v.magSq(); 207 | return new PVector(v.x * sin(r) - v.y * cos(r), v.x * cos(r) - v.y * sin(r)); 208 | } 209 | } 210 | 211 | 212 | class Sinusoidal extends Variation { 213 | Sinusoidal() { 214 | super(); 215 | this.name = "Sinusoidal"; 216 | } 217 | 218 | PVector f(PVector v) { 219 | return new PVector(sin(v.x), sin(v.y)); 220 | } 221 | } 222 | 223 | 224 | class Linear extends Variation { 225 | float amt; 226 | 227 | Linear(float amt) { 228 | super(); 229 | this.amt = amt; 230 | } 231 | 232 | PVector f(PVector v) { 233 | return new PVector(v.x * amt, v.y * amt); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /FractalFlame/FractalFlame.pde: -------------------------------------------------------------------------------- 1 | PVector current; 2 | Variation finalV; 3 | 4 | ArrayList variations = new ArrayList(); 5 | 6 | Pixel[][] pixies; 7 | PImage flameImg; 8 | 9 | int total = 10000000; 10 | int perFrame = 500000; 11 | int count = 0; 12 | 13 | // From: https://github.com/scottdraves/flam3/blob/master/test.flam3 14 | // 15 | // 16 | // 17 | // 18 | 19 | // 20 | // 21 | // 22 | // 23 | 24 | color c1, c2; 25 | void setup() { 26 | size(640, 480); 27 | 28 | // Pick two colors 29 | c1 = randomColor(); 30 | c2 = randomColor(); 31 | // Make sure they aren't the same 32 | while (c1 == c2) { 33 | c2 = randomColor(); 34 | } 35 | 36 | // Four hard-coded variations 37 | Variation s1 = new Spherical().setColor(1); 38 | s1.setTransform(new float[]{-0.681206, -0.0779465, 0.20769, 0.755065, -0.0416126, -0.262334}); 39 | Variation s2 = new Spherical().setColor(0.66); 40 | s2.setTransform(new float[]{0.953766, 0.48396, 0.43268, -0.0542476, 0.642503, -0.995898}); 41 | Variation s3 = new Spherical().setColor(0.33); 42 | s3.setTransform(new float[]{0.840613, -0.816191, 0.318971, -0.430402, 0.905589, 0.909402}); 43 | Variation s4 = new Spherical().setColor(0); 44 | s4.setTransform(new float[]{0.960492, -0.466555, 0.215383, -0.727377, -0.126074, 0.253509}); 45 | 46 | variations.add(s1); 47 | variations.add(s2); 48 | variations.add(s3); 49 | variations.add(s4); 50 | 51 | // pixel map and image 52 | pixies = new Pixel[width][height]; 53 | for (int i = 0; i < width; i++) { 54 | for (int j = 0; j < height; j++) { 55 | pixies[i][j] = new Pixel(); 56 | } 57 | } 58 | flameImg = createImage(width, height, RGB); 59 | 60 | //variations.add(new Linear(1)); 61 | //variations.add(new Sinusoidal()); 62 | //variations.add(new Swirl()); 63 | //variations.add(new Spherical()); 64 | //variations.add(new HorseShoe()); 65 | //variations.add(new Polar()); 66 | //variations.add(new Hankerchief()); 67 | //variations.add(new Heart()); 68 | //variations.add(new Disc()); 69 | //variations.add(new Hyperbolic()); 70 | //variations.add(new Fisheye()); 71 | 72 | // Starting point 73 | current = new PVector(); 74 | // Random xy 75 | current.x = random(-1, 1); 76 | current.y = random(-1, 1); 77 | // Using z for "c" (color) 78 | current.z = random(0, 1); 79 | } 80 | 81 | void draw() { 82 | 83 | // Iterations per frame 84 | for (int i = 0; i < perFrame; i++) { 85 | // Pick a variation (equal probabilities) 86 | int index = int(random(variations.size())); 87 | Variation variation = variations.get(index); 88 | // Save previous point just in case 89 | PVector previous = current.copy(); 90 | 91 | // New point 92 | current = variation.flame(current); 93 | 94 | // If we end up in some divide by zero disaster land go back to previous point 95 | if (Float.isNaN(current.x) || Float.isNaN(current.y) || !Float.isFinite(current.x) || !Float.isFinite(current.y)) { 96 | current = previous.copy(); 97 | } 98 | 99 | // Silly double-checking 100 | if (Float.isNaN(current.x) || Float.isNaN(current.y) || !Float.isFinite(current.x) || !Float.isFinite(current.y)) { 101 | println("problem!"); 102 | } 103 | 104 | 105 | // A final transformation to fit on window 106 | float zoom = 0.5; 107 | float x = current.x * width * zoom; 108 | float y = -1*current.y * height * zoom; 109 | 110 | // Pixel location 111 | int px = int(x + width/2); 112 | int py = int(y + height/2); 113 | 114 | // Are we in the window? 115 | if (px >= 0 && px < width && py >= 0 && py < height) { 116 | // Increase count 117 | pixies[px][py].value++; 118 | // Pick color 119 | color c = lerpColor(c1, c2, current.z); 120 | // Increase rgb counters 121 | pixies[px][py].r += red(c) / 255; 122 | pixies[px][py].g += green(c) / 255; 123 | pixies[px][py].b += blue(c) / 255; 124 | } 125 | } 126 | 127 | // Find maximum 128 | float max = 0; 129 | for (int i = 0; i < width; i++) { 130 | for (int j = 0; j < height; j++) { 131 | Pixel pix = pixies[i][j]; 132 | float value = (float) Math.log10(pix.value); 133 | max = max(max, value); 134 | } 135 | } 136 | 137 | // Set all pixels 138 | flameImg.loadPixels(); 139 | for (int i = 0; i < width; i++) { 140 | for (int j = 0; j < height; j++) { 141 | Pixel pix = pixies[i][j]; 142 | float value = (float) Math.log10(pix.value)/max; 143 | int index = i + j * width; 144 | float r = pix.r * value; 145 | float g = pix.g * value; 146 | float b = pix.b * value; 147 | 148 | // Apply gamma 149 | float gamma = 1 / 4.0; 150 | r = 255 * pow((r/255), gamma); 151 | g = 255 * pow((g/255), gamma); 152 | b = 255 * pow((b/255), gamma); 153 | 154 | flameImg.pixels[index] = color(r, g, b); 155 | } 156 | } 157 | flameImg.updatePixels(); 158 | 159 | // Draw image 160 | background(0); 161 | image(flameImg, 0, 0); 162 | 163 | // Check progress 164 | count += perFrame; 165 | if (count < total) { 166 | float percent = float(count) / total; 167 | fill(255); 168 | rect(0, 50, percent * width, 50); 169 | } else { 170 | noLoop(); 171 | saveFrame("render"+millis()+".png"); 172 | } 173 | } 174 | --------------------------------------------------------------------------------