///// DIVING MODE //////
void flyThrough() {
rotateMouse();
mouseButtons();
if (mousePressed) {
if (julia && shiftPressed()) {
// scale julia param
if (mouseButton == LEFT) {
jx += map(rmouseX,0, width, -mr, +mr) / fps;
jy += map(rmouseY,0, height, -mr, +mr) / fps;
}
// scale julia param in opposite direction
if (mouseButton == RIGHT) {
jx -= map(rmouseX,0, width, -mr, +mr) / fps;
jy -= map(rmouseY,0, height, -mr, +mr) / fps;
}
// rotate and scale julia param
if (mouseButton == CENTER) {
float ang = map(mouseX, 0, width, -PI/fps, PI/fps);
float dr = pow(.70, 1f/fps);
float d = map(mouseY, 0, height, dr, 1/dr);
jx = d * (jx * cos(ang) - jy * sin(ang));
jy = d * (jx * sin(ang) + jy * cos(ang));
println(dist(0,0,jx,jy));
}
}
else {
// zoom in and move
if (mouseButton == LEFT) {
mx += map(rmouseX,0, width, -mr, +mr) / fps; // maximum horizontal speed: one window width per second
my += map(rmouseY,0, height, -mr, +mr) / fps;
mr *= pow(.70, 1f/fps);
}
// zoom out and move
if (mouseButton == RIGHT) {
mx -= map(rmouseX,0, width, -mr, +mr) / fps;
my -= map(rmouseY,0, height, -mr, +mr) / fps;
mr /= pow(.70, 1f/fps);
}
// zoom and rotate
if (mouseButton == CENTER) {
theta += map(mouseX, 0, width, -PI/fps, PI/fps);
float dr = pow(.70, 1f/fps);
mr *= map(mouseY, 0, height, dr, 1/dr);
}
}
}
}
///// DRAG MODE //////
void mouseDragged() {
if(flying) return;
mouseButtons();
rotateMouse();
if(julia && shiftPressed()) {
// drag julia params
cursor(CROSS);
switch(mouseButton) {
case LEFT: // move
jx -= map(rmouseX - prmouseX, 0, width, 0, mr);
jy -= map(rmouseY - prmouseY, 0, height, 0, mr);
break;
case RIGHT: // scale julia params
float d = float(rmouseY - prmouseY) / height;
jx *= 1 + d;
jy *= 1 + d;
break;
case CENTER: // rotate julia params
float ang = 2 * (atan2(mouseX - cx, mouseY - cy) - atan2(pmouseX - cx, pmouseY - cy));
float temp = jx * cos(ang) + jy * sin(ang);
jy = jx * -sin(ang) + jy * cos(ang);
jx = temp;
break;
}
}
else {
// drag sampling window
cursor(HAND);
switch(mouseButton) {
case LEFT: // move
mx -= map(rmouseX - prmouseX, 0, cx, 0, mr);
my -= map(rmouseY - prmouseY, 0, cy, 0, mr);
break;
case RIGHT: // zoom
float d = float(mouseY - pmouseY) / height;
mr *= 1 + d;
break;
case CENTER: // rotate
theta += atan2(rmouseX - cx, rmouseY - cy) - atan2(prmouseX - cx, prmouseY - cy) ;
break;
}
}
}
void mouseReleased() {
if(!flying) cursor(ARROW);
}
/// MOUSE HACKS ///
// add a mousewheel listener ...
void useMouseWheel() {
addMouseWheelListener(new java.awt.event.MouseWheelListener() {
public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
mouseWheel(evt.getWheelRotation());
}
}
);
}
// use the mousewheel for rotation
void mouseWheel(int delta) {
float ang = PI / 24 * delta;
if(julia && shiftPressed()) {
float temp = jx * cos(ang) + jy * sin(ang);
jy = jx * -sin(ang) + jy * cos(ang);
jx = temp;
}
else {
theta -= ang;
}
}
// map mouse motions to our rotated coordinate system
void rotateMouse() {
rmouseX = cx + int(cosinus * (mouseX-cx) - sinus * (mouseY-cy));
rmouseY = cy + int(sinus * (mouseX-cx) + cosinus * (mouseY-cy));
prmouseX = cx + int(cosinus * (pmouseX-cx) - sinus * (pmouseY-cy));
prmouseY = cy + int(sinus * (pmouseX-cx) + cosinus * (pmouseY-cy));
}
// make mac users happy ( mapping modifier keys to mouse buttons )
void mouseButtons() {
// we have to access the keyEvent directly since processing does not allow to
// check for multiple modifier keys at once ...
if (keyPressed && (keyEvent.getModifiers() & KeyEvent.CTRL_MASK) > 0) mouseButton = CENTER;
if (keyPressed && (keyEvent.getModifiers() & KeyEvent.ALT_MASK) > 0) mouseButton = RIGHT;
}
boolean shiftPressed() {
return keyPressed && ((keyEvent.getModifiers() & KeyEvent.SHIFT_MASK) > 0);
}
/// KEYBOARD INTERACTION ///
void keyPressed() {
switch(key) {
case 'a': // animate the palette
startcolor += maxcolors-1;
break;
case 'A':
startcolor++;
break;
case 's': // smooth colors
createPalette(maxcolors + 1);
break;
case 'S':
createPalette(maxcolors - 1);
break;
case 'd': // deep thought
maxiterations++ ;
break;
case 'D':
maxiterations-- ;
break;
case 'f': // browsing mode
flying =! flying;
if(flying) noCursor(); else cursor(ARROW);
break;
case 'g':
case 'h':
goHome();
break;
case 'j': // julia mode
julia =! julia;
break;
case 'c': // capture
saveFrame(screenshotName);
break;
case 'C': // capture hires
saveScreenshot(screenshotName);
break;
case 'R': // start recording
recording = true;
break;
case 'r': // stop recording
recording = false;
break;
}
}
///////////////////////////////////////////////
// //
// //
// Yet Another Julibrot Explorer //
// //
// //
///////////////////////////////////////////////
// (c) Martin Schneider 2009
////////////////// Key Map ////////////////////
// [j] julia/mandelbrot switch
// [f] flying mode on/off
// [g][h] go home!
// [a] [A] animate colors
// [s] [S] smooth colors (change the number of colors)
// [d] [D] deep thought (change the number of iterations)
// [c] capture screenshot
// [R] start recording a movie
// [r] stop recording
//////////////// Mouse Map ////////////////////
///// Dragging Mode /////
// drag the mouse to change the mandelbrot params
// shift-drag it to change juliaset parameters
// [left] drag
// [right] zoom
// [center] rotate
///// Flying Mode /////
// hold and move the mouse to dive in.
// shift-move it to fly through the space of julia sets
// [left] zoom in & move
// [right] zoom out & move
// [center] zoom & rotate
// Note: You can press [ctrl] and [alt] while using the mouse
// to emulate middle and right mouse buttons ...
// mandelbrot params
float mx, my, mr;
float mx0 = 0; // x origin
float my0 = 0; // y origin
float mr0 = 3; // radius
// julia set params
float jx = -.6;
float jy = .6;
// sampling params
int maxiterations = 32;
// rotation
float theta;
float sinus, cosinus;
int rmouseX, rmouseY;
int prmouseX, prmouseY;
int cx, cy;
// colors
int startcolor = 0;
int maxcolors = 32;
color[] palette;
// toggles
boolean flying = false; // navigation mode
boolean julia = false; // julia mode
boolean recording; // recording animations
// saving
int hires = 2000;
String screenshotName = "fractal-screenshot-####.png";
// recording
String frameName = "fractal-animation-######.jpg";
float recordingFps = 60;
// animation
float fps;
// julibrot transition
float J, M;
int tween;
int jbmillis = 2000;
void setup() {
size(500, 500);
createPalette(maxcolors);
cx = width / 2;
cy = height / 2;
useMouseWheel();
goHome();
}
void draw() {
// adaptive framerate for live browsing
// fixed framerate for recording ...
fps = recording ? recordingFps : frameRate;
// morph the julibrot intersection plane between mandelbrot and julia set
morphing();
// do the mandelbrot calculations ...
render(g);
// save the result if we are in recording mode
if(recording) saveFrame(frameName);
// frame-based flying mode interaction
if(flying) flyThrough();
}
void render(PImage img) {
// access image pixels
img.loadPixels();
// boundaries for sampling
float xmin = mx - mr;
float xmax = mx + mr;
float ymin = my - mr;
float ymax = my + mr;
// sampling density
float dx = (xmax - xmin) / img.width ;
float dy = (ymax - ymin) / img.height;
// rotation angles
sinus = sin(theta);
cosinus = cos(theta);
// go sample!
for(int j = 0; j < img.height; j++) {
float y = ymin + j * dy;
for(int i = 0; i < img.width; i++) {
float x = xmin + i * dx;
// rotation
float xr = mx + cosinus * (x-mx) - sinus * (y-my);
float yr = my + sinus * (x-mx) + cosinus * (y-my);
// calculation
int n = julibrot(J*jx + M*xr, J*jy + M*yr, J*xr, J*yr, maxiterations);
// paint the pixel
if ( n == maxiterations) {
img.pixels[i+j*img.width] = color(#ffffff);
} else {
img.pixels[i+j*img.width] = palette[(startcolor + n) % maxcolors];
}
}
}
img.updatePixels();
}
//// FRACTAL FUN
int julibrot(float x, float y, float a, float b, int maxiterations) {
int n = 0;
for(; n < maxiterations; n++) {
float aa = a * a;
float bb = b * b;
b = 2 * a * b + y;
a = aa - bb + x;
// bail out if the distance to the origin is > 2
if(aa + bb > 4) break;
}
return n;
}
void morphing() {
// set the target for julibrot transition
int target = julia ? 1 : 0;
// lerpstep is the amount of lerping needed
// to reach 90% of the target within jbmillis
float lerpstep = 2.3f /fps * 1000 / jbmillis;
J = lerp(J, target, lerpstep);
// stop lerping after 99.9 %
float lerpstop = .001;
if( target==1 && J > target-lerpstop) J=1;
if( target==0 && J < lerpstop) J=0;
M = 1-J;
}
void goHome() {
mx = mx0;
my = my0;
mr = mr0;
}
//// HELPER FUNCTIONS
void createPalette(int s) {
// readjust startcolor
startcolor = int(startcolor * float(s) / maxcolors);
maxcolors = max(1, s);
colorMode(HSB, maxcolors , 255, 255);
palette = new color[maxcolors];
for(int i=0; i<maxcolors; i++) {
palette[i] = color(i, 128, 255);
}
}
void saveScreenshot(String name) {
PGraphics tmp = g;
PGraphics img = createGraphics(hires, hires, P2D);
render(img);
g = img;
saveFrame(name);
g = tmp;
}