Skip to content

Instantly share code, notes, and snippets.

@Bleuje
Last active October 1, 2023 19:07
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Bleuje/5a71f27afedfb7869daf8c81f7a05367 to your computer and use it in GitHub Desktop.
Save Bleuje/5a71f27afedfb7869daf8c81f7a05367 to your computer and use it in GitHub Desktop.
// code updates are now there:
// https://github.com/Bleuje/processing-animations-code/blob/main/code/fractalsliding2d/fractalsliding2d.pde
// Processing code by Etienne JACOB
// for collab with Yann Le Gall (https://demozoo.org/graphics/322553/)
// motion blur template by beesandbombs
// See the license information at the end of this file.
// View the rendered result at: https://bleuje.com/gifanimationsite/single/2dfractalslidingsquares/
// using double instead of float makes the code a bit more complicated
// (there seems to be a precision issue with float)
int[][] result;
float t, c;
double ease(double p, double g) {
if (p < 0.5)
return 0.5 * Math.pow(2*p, g);
else
return 1 - 0.5 * Math.pow(2*(1 - p), g);
}
double c01(double x)
{
return Math.min(1,Math.max(0,x));
}
void draw() {
if (!recording) {
t = (mouseX*1.3/width)%1;
c = mouseY*1.0/height;
if (mousePressed)
println(c);
draw_();
} else {
for (int i=0; i<width*height; i++)
for (int a=0; a<3; a++)
result[i][a] = 0;
c = 0;
for (int sa=0; sa<samplesPerFrame; sa++) {
t = map(frameCount-1 + sa*shutterAngle/samplesPerFrame, 0, numFrames, 0, 1);
t %= 1;
draw_();
loadPixels();
for (int i=0; i<pixels.length; i++) {
result[i][0] += pixels[i] >> 16 & 0xff;
result[i][1] += pixels[i] >> 8 & 0xff;
result[i][2] += pixels[i] & 0xff;
}
}
loadPixels();
for (int i=0; i<pixels.length; i++)
pixels[i] = 0xff << 24 |
int(result[i][0]*1.0/samplesPerFrame) << 16 |
int(result[i][1]*1.0/samplesPerFrame) << 8 |
int(result[i][2]*1.0/samplesPerFrame);
updatePixels();
if (frameCount<=numFrames)
{
saveFrame("fr###.gif");
println(frameCount,"/",numFrames);
}
if (frameCount==numFrames)
stop();
}
}
// end of template
//////////////////////////////////////////////////////////////////////////////
int samplesPerFrame = 5;
int numFrames = 175;
float shutterAngle = 1.0;
boolean recording = false; // if false, time is controlled by mouse X
int MAX_DEPTH = 9; // maximum recursion depth
float L; // L = width/2; in setup()
// tree structure
class Structure
{
int numberOfChildren = 3;
Structure [] children = new Structure[numberOfChildren];
int depth;
int childIndex;
boolean isMain; // true if it ends up in top left corner
float offset;
float splitDelay;
float direction; // square path orientation
Structure(int index_,int depth_,boolean isMain_,float offset_,float splitDelay_,float direction_)
{
childIndex = index_;
depth = depth_;
isMain = isMain_;
offset = offset_;
splitDelay = splitDelay_;
direction = direction_;
float nextOffsets = isMain?0:random(1);
float nextSplitDelay = isMain?0:(1-pow(random(1),3.0))*2-1;
float nextDirection = isMain?1:floor(random(0,2))*2-1;
if(depth<MAX_DEPTH)
{
for(int i=1;i<numberOfChildren;i++)
{
children[i] = new Structure(i,depth+1,false,nextOffsets,nextSplitDelay,nextDirection);
}
children[0] = new Structure(0,depth+1,isMain,nextOffsets,nextSplitDelay,nextDirection);
}
}
// square path, using polar coordinates, maybe not the best choice :)
// also has easing to make the movement stop
PVector path0(double q)
{
q = (q+1234*4)%1; // making sure q is in [0,1[
double doubleIndex = 4*q; // has index range, but still a double so far
int index1 = (int)Math.floor(doubleIndex);
int index2 = index1+1;
double angle1 = index1*HALF_PI+QUARTER_PI-PI;
double angle2 = index2*HALF_PI+QUARTER_PI-PI;
double fr = doubleIndex-index1; // fractional part of doubleIndex
double es = ease(c01(fr*4),2.5); // classic easing + moving and stopping with the constrain (c01)
double radius = 2/sqrt(2);
PVector v1 = new PVector((float)(Math.cos(angle1)*radius),(float)(Math.sin(angle1)*radius)); // fixed position 1 to define path
PVector v2 = new PVector((float)(Math.cos(angle2)*radius),(float)(Math.sin(angle2)*radius)); // fixed position 2 to define path
// interpolate between fixed positions with easing
PVector v = v1.copy().lerp(v2,(float)es);
return v;
}
// offset on previous path
PVector path1(double q)
{
double q2 = q-float(childIndex)/numberOfChildren-0.05+offset;
// (childIndex/3 is the correct offset to have the 3 children evenly at different places of the path)
return path0(q2*direction);
}
PVector path2(double q)
{
// using pow to make the movement slower and slower
// also using the min function to stop moving completely
return path1(-Math.pow(-Math.min(0,q*0.2),2.5));
}
void show(double p,int maxDepth2) // maxDepth2 is used to stop the recursion, it can be different from MAX_DEPTH
{
stroke(255);
noFill();
strokeWeight(5.8*(float)c01(-1.0-1.3*p));
rectMode(CENTER);
rect(0,0,L*2,L*2);
float param = depth-t+2-splitDelay-maxDepth2;
if(param>=0) // if true, stop recursion
{
if(childIndex==0) // only show if it's a child 0 (not clean :( )
{
// drawing a dot...
float reduce = constrain(map(param,0.1,0,1,0),0,1)*1.15; // size change parameter if we're close to splitting
strokeWeight(0.95*L*reduce);
stroke(255);
point(0,0);
}
}
else
{
float growth = pow(constrain(map(param,0,-0.1,0,1),0,1),1.7); // size change parameter if we're close to splitting
PVector pos = path2(p);
pos.mult(0.5*L);
push();
translate(pos.x,pos.y);
scale(0.5); // with this scale change during recursions, we don't have to adjust pixel positions with factors
scale(growth); // just for splitting, generally at one, no scale change here
// to show yourself you show your children "one loop time ago" (p-1)
for(int i=1;i<numberOfChildren;i++)
{
children[i].show(p-1,maxDepth2);
}
if(isMain)
{
// a square that ends in the top left corner is treated differently
// you show yourself in the past (one loop ago)
// and decrease the max depth argument by one
show(p-1,maxDepth2-1);
// arguably the trickiest thing in the entire code
}
else // child 0 is not a isMain
{
children[0].show(p-1,maxDepth2);
}
pop();
}
}
}
Structure mainStructure;
void setup(){
size(650,650,P2D);
result = new int[width*height][3];
randomSeed(92645);
mainStructure = new Structure(0,0,true,0,0,1);
L = width/2;
smooth(8);
}
void draw_(){
background(0);
push();
scale(pow(2.0,t)*2*1.003);
translate(width/2,height/2);
mainStructure.show(t-0.5,MAX_DEPTH);
pop();
}
/* License:
*
* Copyright (c) 2023 Etienne Jacob
*
* All rights reserved.
*
* This code after the template and the related animations are the property of the
* copyright holder. Any reproduction, distribution, or use of this material,
* in whole or in part, without the express written permission of the copyright
* holder is strictly prohibited.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment