// Strange Symmetry 2 // A 'lite' version without color // Licensed under Creative Commons, W:Blut 2008, Vanhoutte Frederik // http://creativecommons.org/licenses/by/2.0/be/deed.en // // Disclaimer: the software // // This Processing sketch is free code and provided without any warranty. // It is free for any use. It represents some amateur coding by the author. // It is not intended to have any instructional value nor does it represent good coding practice. // // Disclaimer: the maths // // The formulae used are not necessarily attractors, strange, chaotic or otherwise. These terms have a well-defined // mathematical definition. The only intent of this code is the generation of interesting looking images using a similar // methodology. // // // Concept // // 2D points (particles) are iterated through some functions: x=f(x,y), y=f(x,y). At each step the position is recorded. // For certain functions, the random paths of thousands of particles reveal remarkable structure. Search on phrases like "chaotic attractor", // "strange attractor" or the somewhat similar "iterated function system" (IFS). // // Structural outline // // A 'map' is essentially two functions in one object, representing the x and y iterating functions. An array of particles ('Particle') // is collected together with a randomly generated map into an 'AttractorSystem'. The AttractorSystem object is responsible for generating // new particles, tracking them through the functions and killing them when expired. The coordinates used by the particles are the real function // values. // // The 'AccumulationGrid' stores information about particle hits in a discrete 2D array. It stores a hit count and potentially, also several channels (hue, // saturation and alpha). The accumulation grid defines the resolution and the visible portion of the function space, // what information to assign to each channel and how to process each hit. The coordinates used in the grid are again the real function values. // // An 'AttractorDisplay' couples an AttractorSystem to an AccumulationGrid and handles the display of an accumulation grid, converting it to an // arbitrarily sized images. The coordinates of an AttractorDisplay are naturally in pixels. All functions for visualizing the accumulation grid are // handled here, turning the grid counts and channels into color information. // // The interface is handled by a 'AttractorDisplayArray'. To avoid excessive memory requirements it is actually an array of Map, not of // AttractorDisplay. Unfortunately this does imply that changes to the AttractorSystem and Grid are lost when returnig to the explorer... // // Main display AttractorDisplayArray attractorDisplayArray; Colorbar briBar; // GUI library by Andreas Schlegel. For more info: http://www.sojamo.de/libraries/controlP5/ import controlP5.*; ControlP5 controlP5; int controlWidth=295; void setup(){ // size is explicitely set for applet export. If using offline, delete this line and uncomment the next two. size(1095,800); // int w=800; // A minimum of 800 is assumed // size(w+controlWidth,w); background(30); // color and other values are most easily handled if constrained between 0 and 1. This way you don't have to handle // rescaling when using powers or roots. (This is convenient for gamma correction etc.). colorMode(HSB,1.0f); attractorDisplayArray = new AttractorDisplayArray(5,5,0,0,width-controlWidth,height,0,0,width-controlWidth,height); setupGUI(); } void draw(){ attractorDisplayArray.update(); updateGUI(); } void mousePressed(){ attractorDisplayArray.mousePressed(); } void mouseReleased(){ attractorDisplayArray.mouseReleased(); } void keyPressed(){ attractorDisplayArray.keyPressed(); } void keyReleased(){ attractorDisplayArray.keyReleased(); } // Everything beyond this point is for the GUI. I used the controlP5 library // by Andreas Schlegel. For more info: http://www.sojamo.de/libraries/controlP5/ void setupGUI(){ controlP5 = new ControlP5(this); Bang b=controlP5.addBang("save",width-controlWidth+10,750,275,40); b.setLabel(""); b.setId(205); Textlabel lbl=controlP5.addTextlabel("lblSave","save image", width-controlWidth+120,765); ControlGroup selection = controlP5.addGroup("selection",width-controlWidth+10,150,275); selection.setVisible(false); Radio r = controlP5.addRadio("attractorType",0,10); r.add("random",-1); r.add("blended",0); r.add("branched",1); r.add("simple blended",2); r.add("simple branched",3); r.add("Gumowski-Mira",4); r.add("modified Gumowski-Mira",5); r.add("trigonometric",6); r.add("Clifford",7); r.add("Peter de Jong",8); r.add("plaid",9); r.add("wreath",10); r.add("modified wreath",11); r.add("desynced wreath",12); r.add("generic wreath",13); r.add("generic branched",14); r.add("generic blended",15); r.activate("random"); r.setGroup(selection); r.setId(1); ControlGroup general = controlP5.addGroup("general",width-controlWidth+10,150,275); general.setVisible(false); b=controlP5.addBang("mutate",0,10,275,40); b.setLabel(""); b.setGroup(general); b.setId(3); lbl=controlP5.addTextlabel("lblMutate","mutate attractor", 110,25); lbl.setGroup(general); ControlGroup render =controlP5.addGroup("render",width-controlWidth+10,170,275); render.setVisible(false); r = controlP5.addRadio("quality",80,10); r.add("fast",0); r.add("bilinear",1); r.add("gaussian",2); r.activate("fast"); r.setGroup(render); r.setId(11); r = controlP5.addRadio("sourceType",150,10); r.addItem("square",0); r.addItem("cross",1); r.addItem("grid",2); r.addItem("circle",3); r.addItem("rings",4); r.addItem("mozaic",5); r.addItem("spiral",6); r.activate("square"); r.setGroup(render); r.setId(12); ControlGroup particles =controlP5.addGroup("particles",width-controlWidth+10,300,275); particles.setVisible(false); Slider s =controlP5.addSlider("range",0.1f,200f,2f,0,10,120,10); s.setGroup(particles); s.setId(20); s =controlP5.addSlider("numberOfParticles",1,10000,1000,0,30,120,10); s.setGroup(particles); s.setId(21); s =controlP5.addSlider("particleLife",1,1000,500,0,50,120,10); s.setGroup(particles); s.setId(23); s =controlP5.addSlider("cutoff",0,20,1,0,70,120,10); s.setGroup(particles); s.setId(24); ControlGroup transforms= controlP5.addGroup("transforms",width-controlWidth+10,410,275); transforms.setVisible(false); s =controlP5.addSlider("symmetry",1,16,1,100,10,175,10); s.setGroup(transforms); s.setLabel(""); s.setId(30); lbl=controlP5.addTextlabel("lblSymm","rotational symmetry",0,10); lbl.setGroup(transforms); lbl=controlP5.addTextlabel("lblMirror","mirror symmetry",0,30); lbl.setGroup(transforms); r = controlP5.addRadio("mirror",10,45); r.add("no mirror",0); r.add("x mirror",2); r.add("y mirror",3); r.add("x and y mirror",6); r.activate("no mirror"); r.setGroup(transforms); r.setId(31); Knob k=controlP5.addKnob("mirrorRotation",0f,360f,0f,130,30,60); k.setGroup(transforms); k.setId(33); k=controlP5.addKnob("rotation",0f,360f,0f,0,110,60); k.setGroup(transforms); k.setId(34); ControlGroup centers= controlP5.addGroup("centers",width-controlWidth+10,620,275); centers.setVisible(false); r = controlP5.addRadio("centerMode",15,30); r.add("display center",0); r.add("symmetry center",1); r.add("mirror center",2); r.add("rotation center",3); r.setGroup(centers); r.setId(35); lbl=controlP5.addTextlabel("lblCenters","Press 'c' to put selected center at mouse position.", 13,10); lbl.setGroup(centers); ControlGroup channel= controlP5.addGroup("channel",width-controlWidth+10,120,275); channel.setVisible(false); lbl=controlP5.addTextlabel("lblChannel","select color channel",0,10); lbl.setGroup(channel); r = controlP5.addRadio("colorChannel",10,25); r.add("bri",2); r.activate("bri"); r.setGroup(channel); r.setId(36); ControlGroup controls= controlP5.addGroup("controls",width-controlWidth+10,230,275); controls.setVisible(false); lbl=controlP5.addTextlabel("lblBias","bias",30,10); lbl.setGroup(controls); lbl=controlP5.addTextlabel("lblGain","gain",130,10); lbl.setGroup(controls); lbl=controlP5.addTextlabel("lblMultiplier","multiplier",220,10); lbl.setGroup(controls); k=controlP5.addKnob("bias",0.1f,4.0f,1.0f,0,25,75); k.setId(40); k.setLabel(""); k.setGroup(controls); k=controlP5.addKnob("gain",0.01f,0.99f,.5f,100,25,75); k.setLabel(""); k.setId(50); k.setGroup(controls); k=controlP5.addKnob("multiplier",0f,3f,1f,200,25,75); k.setLabel(""); k.setId(60); k.setGroup(controls); b =controlP5.addBang("biasMinusL",0,110,11,11); b.setLabel(""); b.setId(41); b.setGroup(controls); b =controlP5.addBang("biasMinusS",16,110,11,11); b.setLabel(""); b.setId(42); b.setGroup(controls); b =controlP5.addBang("biasReset",32,110,11,11); b.setLabel(""); b.setId(43); b.setGroup(controls); b =controlP5.addBang("biasPlusS",48,110,11,11); b.setLabel(""); b.setId(44); b.setGroup(controls); b =controlP5.addBang("biasPlusL",64,110,11,11); b.setLabel(""); b.setId(45); b.setGroup(controls); b =controlP5.addBang("gainMinusL",100,110,11,11); b.setLabel(""); b.setId(51); b.setGroup(controls); b =controlP5.addBang("gainMinusS",116,110,11,11); b.setLabel(""); b.setId(52); b.setGroup(controls); b =controlP5.addBang("gainReset",132,110,11,11); b.setLabel(""); b.setId(53); b.setGroup(controls); b =controlP5.addBang("gainPlusS",148,110,11,11); b.setLabel(""); b.setId(54); b.setGroup(controls); b =controlP5.addBang("gainPlusL",164,110,11,11); b.setLabel(""); b.setId(55); b.setGroup(controls); b =controlP5.addBang("multMinusL",200,110,11,11); b.setLabel(""); b.setId(61); b.setGroup(controls); b =controlP5.addBang("multMinusS",216,110,11,11); b.setLabel(""); b.setId(62); b.setGroup(controls); b =controlP5.addBang("multReset",232,110,11,11); b.setLabel(""); b.setId(63); b.setGroup(controls); b =controlP5.addBang("multPlusS",248,110,11,11); b.setLabel(""); b.setId(64); b.setGroup(controls); b =controlP5.addBang("multPlusL",264,110,11,11); b.setLabel(""); b.setId(65); b.setGroup(controls); ControlGroup options= controlP5.addGroup("options",width-controlWidth+10,515,275); options.setVisible(false); lbl=controlP5.addTextlabel("lblOptions","select options for selected channel",0,10); lbl.setGroup(options); Toggle t=controlP5.addToggle("symmetric",10,55,10,10); t.setLabel(""); t.setGroup(options); t.setId(102); lbl=controlP5.addTextlabel("lblSymmetric","symmetric",25,55); lbl.setGroup(options); t=controlP5.addToggle("repeat",10,70,10,10); t.setLabel(""); t.setGroup(options); t.setId(103); lbl=controlP5.addTextlabel("lblRepeat","repeat",25,70); lbl.setGroup(options); t=controlP5.addToggle("pingpong",10,85,10,10); t.setLabel(""); t.setGroup(options); t.setId(104); lbl=controlP5.addTextlabel("lblPingPong","pingpong",25,85); lbl.setGroup(options); t=controlP5.addToggle("invert",10,100,10,10); t.setLabel(""); t.setGroup(options); t.setId(105); lbl=controlP5.addTextlabel("lblInvert","invert",25,100); lbl.setGroup(options); ControlGroup colorBars= controlP5.addGroup("colorBars",width-controlWidth+10,660,275); colorBars.setVisible(false); lbl=controlP5.addTextlabel("lblBriBar","B",0,42); lbl.setGroup(colorBars); briBar = new Colorbar(width-controlWidth+25,700,260,10,new genericFunction(0.5f,0.5f,1f,0f,0f,1f,false,false,false,true,false),2); ControlGroup info= controlP5.addGroup("info",width-controlWidth+10,20,275); controlP5.addTextlabel("nfo1"," ",0,10).setGroup(info); controlP5.addTextlabel("nfo2"," ",10,32).setGroup(info); controlP5.addTextlabel("nfo3"," ",10,44).setGroup(info); controlP5.addTextlabel("nfo4"," ",10,56).setGroup(info); controlP5.addTextlabel("nfo5"," ",10,68).setGroup(info); controlP5.addTextlabel("nfo6"," ",10,80).setGroup(info); controlP5.addTextlabel("nfo7"," ",10,92).setGroup(info); controlP5.addTextlabel("nfo8"," ",10,104).setGroup(info); controlP5.addTextlabel("nfo9"," ",100,10).setGroup(info); } void updateGUI(){ fill(0f,0f,.1f); rect(width-controlWidth,0,controlWidth-1,height); String s1=""; String s2=""; String s3=""; String s4=""; String s5=""; String s6=""; String s7=""; String s8=""; String s9=""; switch(attractorDisplayArray.mode){ case AttractorDisplayArray.INIT: s1="initializing"; break; case AttractorDisplayArray.WAITING: s1="waiting for selection"; s2="click attractor for full screen mode."; s3="'1' : generate random attractors."; break; case AttractorDisplayArray.FULLSCREENACTIVE: s1="active view."; s9="updates: "+((attractorDisplayArray.active)?attractorDisplayArray.activeDisplay.NOfUpdates:0); s2="'1' : go back to attractor selection. All changes will be lost!"; s3="'3' : go to attractor controls."; s4="'4' : go to color controls."; s5="'5' : go to stealth mode (a lot faster)."; s7="Click save to save image to browser."; s8="This may take some time!"; break; case AttractorDisplayArray.FULLSCREENMODIFY: s1="modification preview"; s2="'2' : go to active view"; s3="'5' : go to stealth mode (a lot faster)."; s5="use arrow keys to move attractor."; s6="use '+' and '-' to zoom."; s7="press 'c' to put selected center at mouse position."; break; case AttractorDisplayArray.FULLSCREENCOLOR: s1="color preview."; s2="'2' : go back to active view"; s3="'3' : go to attractor controls."; s4="'5' : go to stealth mode(a lot faster)."; s6="Click save to save image to browser."; s7="This may take some time!"; break; case AttractorDisplayArray.FULLSCREENSTEALTH: s1="stealth mode."; s9="updates: "+((attractorDisplayArray.active)?attractorDisplayArray.activeDisplay.NOfUpdates:0); s2="'2' : go to active view."; s3="'3' : go to attractor controls."; s4="'4' : go to color controls."; s6="Click display to update render. "; break; case AttractorDisplayArray.SAVING: s1="saving image"; break; } ((Textlabel)controlP5.controller("nfo1")).setValue(s1); ((Textlabel)controlP5.controller("nfo2")).setValue(s2); ((Textlabel)controlP5.controller("nfo3")).setValue(s3); ((Textlabel)controlP5.controller("nfo4")).setValue(s4); ((Textlabel)controlP5.controller("nfo5")).setValue(s5); ((Textlabel)controlP5.controller("nfo6")).setValue(s6); ((Textlabel)controlP5.controller("nfo7")).setValue(s7); ((Textlabel)controlP5.controller("nfo8")).setValue(s8); ((Textlabel)controlP5.controller("nfo9")).setValue(s9); controlP5.getGroup("selection").setVisible(false); controlP5.getGroup("general").setVisible(false); controlP5.getGroup("render").setVisible(false); controlP5.getGroup("particles").setVisible(false); controlP5.getGroup("transforms").setVisible(false); controlP5.getGroup("centers").setVisible(false); controlP5.getGroup("channel").setVisible(false); controlP5.getGroup("controls").setVisible(false); controlP5.getGroup("options").setVisible(false); controlP5.getGroup("colorBars").setVisible(false); fill(0,0,0.2f); rect(width-controlWidth+10,750,275,40); controlP5.controller("save").setVisible(false); controlP5.controller("lblSave").setVisible(false); switch(attractorDisplayArray.mode){ case AttractorDisplayArray.INIT: controlP5.getGroup("selection").setVisible(true); controlP5.controller("attractorType").setValue(attractorDisplayArray.defaultASP.type); break; case AttractorDisplayArray.WAITING: controlP5.getGroup("selection").setVisible(true); controlP5.controller("attractorType").setValue(attractorDisplayArray.defaultASP.type); break; case AttractorDisplayArray.FULLSCREENACTIVE: controlP5.getGroup("general").setVisible(true); controlP5.controller("save").setVisible(true); controlP5.controller("lblSave").setVisible(true); break; case AttractorDisplayArray.FULLSCREENMODIFY: controlP5.getGroup("render").setVisible(true); controlP5.getGroup("particles").setVisible(true); controlP5.getGroup("transforms").setVisible(true); controlP5.getGroup("centers").setVisible(true); controlP5.controller("quality").setValue(attractorDisplayArray.activeDisplay.parameters.quality); controlP5.controller("sourceType").setValue(attractorDisplayArray.activeDisplay.attractorSystem.parameters.sourceType); controlP5.controller("mirror").setValue(attractorDisplayArray.activeDisplay.attractorSystem.parameters.mirror); controlP5.controller("symmetry").setValue(attractorDisplayArray.activeDisplay.attractorSystem.parameters.symmetry); controlP5.controller("rotation").setValue(attractorDisplayArray.activeDisplay.attractorSystem.parameters.rotation*360f/TWO_PI); controlP5.controller("mirrorRotation").setValue(attractorDisplayArray.activeDisplay.attractorSystem.parameters.mirrorRotation*360f/TWO_PI); controlP5.controller("range").setValue(attractorDisplayArray.activeDisplay.attractorSystem.parameters.maxX); controlP5.controller("numberOfParticles").setValue(attractorDisplayArray.activeDisplay.attractorSystem.parameters.numParticles); controlP5.controller("particleLife").setValue(attractorDisplayArray.activeDisplay.attractorSystem.parameters.particleLife); controlP5.controller("cutoff").setValue(min(attractorDisplayArray.activeDisplay.attractorSystem.parameters.particleLife-1,attractorDisplayArray.activeDisplay.parameters.cutoff)); controlP5.controller("centerMode").setValue(attractorDisplayArray.activeDisplay.parameters.centerMode); break; case AttractorDisplayArray.FULLSCREENCOLOR: genericFunction gf; switch((int)controlP5.controller("colorChannel").value()){ default: gf=attractorDisplayArray.activeDisplay.parameters.briFunction; break; } controlP5.controller("bias").setValue(gf.bias); controlP5.controller("gain").setValue(gf.gain); controlP5.controller("multiplier").setValue(gf.multiplier); controlP5.controller("invert").setValue(gf.invert?1f:0f); controlP5.controller("repeat").setValue(gf.repeat?1f:0f); controlP5.controller("pingpong").setValue(gf.pingPong?1f:0f); controlP5.controller("symmetric").setValue(gf.symmetric?1f:0f); controlP5.controller("save").setVisible(true); controlP5.controller("lblSave").setVisible(true); //controlP5.getGroup("channel").setVisible(true); controlP5.getGroup("controls").setVisible(true); controlP5.getGroup("options").setVisible(true); controlP5.getGroup("colorBars").setVisible(true); briBar.cf=attractorDisplayArray.activeDisplay.parameters.briFunction; briBar.draw(); break; } } void mirror(int ID){ if(attractorDisplayArray.responsiveToAttractorChange){ attractorDisplayArray.activeDisplay.setMirror(ID); } } void symmetry(int s){ if(attractorDisplayArray.responsiveToAttractorChange){ attractorDisplayArray.activeDisplay.setSymmetry(s); } } void rotation(float r){ if(attractorDisplayArray.responsiveToAttractorChange){ attractorDisplayArray.activeDisplay.setRotation(r/360f*TWO_PI); } } void mirrorRotation(float r){ if(attractorDisplayArray.responsiveToAttractorChange){ attractorDisplayArray.activeDisplay.setMirrorRotation(r/360f*TWO_PI); } } void controlEvent(ControlEvent theEvent) { switch(theEvent.controller().id()) { case 1: if(attractorDisplayArray.mode==AttractorDisplayArray.WAITING) attractorDisplayArray.setType((int)theEvent.controller().value()); break; case 3: attractorDisplayArray.processMutate(); break; case 205: attractorDisplayArray.switchMode(AttractorDisplayArray.SAVING); break; } if(attractorDisplayArray.active){ AttractorDisplay attractorDisplay = attractorDisplayArray.activeDisplay; if(attractorDisplayArray.responsiveToGridChange){ switch(theEvent.controller().id()) { case(11): attractorDisplay.setQuality((int)theEvent.controller().value()); break; case(12): attractorDisplay.setSourceType((int)theEvent.controller().value()); break; case(35): attractorDisplay.setCenterMode((int)theEvent.controller().value()); break; } } if(attractorDisplayArray.responsiveToAttractorChange){ switch(theEvent.controller().id()) { case(20): attractorDisplay.setRange(theEvent.controller().value()); break; case(21): attractorDisplay.setNumParticles((int)theEvent.controller().value()); break; case(23): attractorDisplay.setParticleLife((int)theEvent.controller().value()); break; case(24): attractorDisplay.setCutoff((int)theEvent.controller().value()); break; } } if(attractorDisplayArray.responsiveToColorChange){ genericFunction gf; switch((int)controlP5.controller("colorChannel").value()){ default: gf=attractorDisplay.parameters.briFunction; break; } switch(theEvent.controller().id()) { case 40: gf.setBias(theEvent.controller().value()); break; case 41: gf.setBias(gf.bias-0.1f); break; case 42: gf.setBias(gf.bias-0.01f); break; case 43: gf.setBias(gf.defBias); break; case 44: gf.setBias(gf.bias+0.01f); break; case 45: gf.setBias(gf.bias+0.1f); break; case 50: gf.setGain(theEvent.controller().value()); break; case 51: gf.setGain(gf.gain-0.1f); break; case 52: gf.setGain(gf.gain-0.01f); break; case 53: gf.setGain(gf.defGain); break; case 54: gf.setGain(gf.gain+0.01f); break; case 55: gf.setGain(gf.gain+0.1f); break; case 60: gf.setMultiplier(theEvent.controller().value()); break; case 61: gf.setMultiplier(gf.multiplier-0.1f); break; case 62: gf.setMultiplier(gf.multiplier-0.01f); break; case 63: gf.setMultiplier(gf.defMultiplier); break; case 64: gf.setMultiplier(gf.multiplier+0.01f); break; case 65: gf.setMultiplier(gf.multiplier+0.1f); break; case 70: gf.setOffset(theEvent.controller().value()-1f); break; case 105: gf.invert=(theEvent.controller().value()==1.0f); break; case 102: gf.symmetric=(theEvent.controller().value()==1.0f); break; case 103: gf.repeat=(theEvent.controller().value()==1.0f); break; case 104: gf.pingPong=(theEvent.controller().value()==1.0f); break; } } } }