airsun-Scope

For this project, I was new to Java, so I first spent a while trying to learn the syntax. After finding out the syntax shared many similarities with Javascript, I started by experimenting with different ideas and see how it will turn out visually. The idea of the scope is to demonstrate four fingers of a hand, facing downwards, and scratching a surface. The index is the moving and looping object and it will always leave a scratch mark.

Preparatory hand-drawn sketches of my design:

The PNG file:

The loop gif:

// Template for KidzLabs/4M/Toysmith Animation Praxinoscope
// https://www.amazon.com/4M-3474-Animation-Praxinoscope/dp/B000P02HYC
// https://www.walmart.com/ip/Animation-Praxinoscope-Science-Kits-by-Toysmith-3474/45681503
// Developed for Processing 3.3.6 * http://processing.org
// 23 January 2018 * Golan Levin 
 
// See information about Processing PDF export at: 
// https://processing.org/reference/libraries/pdf/index.html
// PDF generated by Processing can be opened in Adobe Illustrator.
import processing.pdf.*;
boolean bRecordingPDF = false;
 
float inch = 72; 
float diamArtInner = inch * 1.50; 
float diamArtOuter = inch * 4.80; 
float diamCutInner = inch * 1.41; 
float diamCutOuter = inch * 4.875; 
float holeDy = inch * 0.23;
float holeDx = inch * 0.20;
float holeD = inch * 0.1;
 
final int nFrames = 10; 
int myFrameCount = 0;
int exportFrameCount = 0; 
boolean bAnimate = true; 
boolean bExportFrameImages = false;
 
//-------------------------------------------------------
void setup() {
  size(792, 612); // 11x8.5" at 72DPI
  frameRate(15);
  smooth();
} 
 
//-------------------------------------------------------
void draw() {
  background(240); 
  if (bRecordingPDF) {
    beginRecord(PDF, "praxinoscope-output.pdf");
  }
 
  // Do all the drawing. 
  pushMatrix(); 
  translate(width/2, height/2);
  drawCutLines(); 
  drawGuides(); 
  drawAllFrames();
  popMatrix();
 
  if (bExportFrameImages) {
    // If activated, export .PNG frames 
    if (exportFrameCount < nFrames) { String filename = "frame_" + nf((exportFrameCount%nFrames), 3) + ".png"; saveFrame("frames/" + filename); println("Saved: " + filename); exportFrameCount++; if (exportFrameCount >= nFrames) {
        bExportFrameImages = false;
        exportFrameCount = 0;
      }
    }
  }
 
  if (bRecordingPDF) {
    endRecord();
    bRecordingPDF = false;
  }
}
 
 
//-------------------------------------------------------
void keyPressed() {
  switch (key) {
  case ' ': 
    // Press spacebar to pause/unpause the animation. 
    bAnimate = !bAnimate;
    break;
 
  case 'p': 
  case 'P':
    // Press 'p' to export a PDF for the Praxinoscope.
    bRecordingPDF = true; 
    break;
 
  case 'f': 
  case 'F': 
    // Press 'f' to export .png Frames (to make an animated .GIF)
    myFrameCount = 0; 
    exportFrameCount = 0; 
    bExportFrameImages = true;
    bAnimate = true; 
    break;
  }
}
 
//-------------------------------------------------------
void drawCutLines() {
  fill(0); 
  textAlign(CENTER, BOTTOM); 
  text("Praxinoscope Template", 0, 0-diamCutOuter/2-6); 
 
  stroke(0); 
  strokeWeight(1.0);
 
  noFill(); 
  if (!bRecordingPDF) {
    fill(255); 
  }
  ellipse(0, 0, diamCutOuter, diamCutOuter);
 
  noFill(); 
  if (!bRecordingPDF) {
    fill(240); 
  }
  ellipse(0, 0, diamCutInner, diamCutInner);
 
  noFill(); 
  ellipse(diamCutOuter/2 - holeDx, 0-holeDy, holeD, holeD); 
 
  line (diamCutInner/2, 0, diamCutOuter/2, 0);
}
 
//-------------------------------------------------------
void drawGuides() {
  // This function draws the guidelines. 
  // Don't draw these when we're exporting the PDF. 
  if (!bRecordingPDF) {
 
    noFill(); 
    stroke(128); 
    strokeWeight(0.2); 
    ellipse(0, 0, diamArtInner, diamArtInner); 
    ellipse(0, 0, diamArtOuter, diamArtOuter);
 
    for (int i=0; i<nFrames; i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
      float pxi = diamArtInner/2 * cos(angle);
      float pyi = diamArtInner/2 * sin(angle);
      float pxo = diamArtOuter/2 * cos(angle);
      float pyo = diamArtOuter/2 * sin(angle);
      stroke(128); 
      strokeWeight(0.2);
      line (pxi, pyi, pxo, pyo);
    }
 
    // Draw the red wedge outline, highlighting the main view.
    int redWedge = 7; // assuming nFrames = 10
    for (int i=redWedge; i<=(redWedge+1); i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
      float pxi = diamArtInner/2 * cos(angle);
      float pyi = diamArtInner/2 * sin(angle);
      float pxo = diamArtOuter/2 * cos(angle);
      float pyo = diamArtOuter/2 * sin(angle);
      stroke(255, 0, 0); 
      strokeWeight(2.0);
      line (pxi, pyi, pxo, pyo);
    }
    noFill(); 
    stroke(255, 0, 0); 
    strokeWeight(2.0);
    float startAngle = redWedge*TWO_PI/nFrames;
    float endAngle = (redWedge+1)*TWO_PI/nFrames;
    arc(0, 0, diamArtInner, diamArtInner, startAngle, endAngle); 
    arc(0, 0, diamArtOuter, diamArtOuter, startAngle, endAngle); 
 
 
    for (int i=0; i<nFrames; i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
 
      pushMatrix();
      rotate(angle); 
      float originY = ((diamArtOuter + diamArtInner)/2)/2;
      translate(0, 0-originY); 
 
      noFill(); 
      stroke(128); 
      strokeWeight(0.2);
      line (-inch/2, 0, inch/2, 0); 
      line (0, -inch/2, 0, inch/2); 
 
      popMatrix();
    }
  }
}
 
//-------------------------------------------------------
void drawAllFrames() {
  for (int i=0; i<nFrames; i++) {
    float angle = map(i, 0, nFrames, 0, TWO_PI); 
    float originY = ((diamArtOuter + diamArtInner)/2)/2;
 
    pushMatrix();
    rotate(angle); 
    translate(0, 0-originY); 
    scale(0.8, 0.8); // feel free to ditch this 
 
    int whichFrame = i; 
    if (bAnimate) {
      whichFrame = (i+myFrameCount)%nFrames;
    }
    drawArtFrame (whichFrame); 
    // drawArtFrameAlternate (whichFrame); 
 
    popMatrix();
  }
  myFrameCount++;
}
 
 
//-------------------------------------------------------
void drawArtFrame (int whichFrame) { 
  // Draw the artwork for a generic frame of the Praxinoscope, 
  // given the framenumber (whichFrame) out of nFrames.
  // NOTE #1: The "origin" for the frame is in the center of the wedge.
  // NOTE #2: Remember that everything will appear upside-down!
 
  // Draw the frame number
  fill(0); 
  noStroke(); 
  textAlign(CENTER, CENTER); 
 
  // Draw some expanding boxes, centered on the local origin
  int nBoxes = 32;
 
  float ry=0;
  float rx=14;
  float rs=11;
  float W1 = whichFrame%nBoxes;
  float Yoffset = 15;
  stroke(0);
  strokeWeight(2.5);
  line(-45,ry, 45,ry);
 
  fill(0);
  strokeWeight(1);
  line(0-rx-2, ry, 0-rx-2, ry+40);
  ellipse(0-rx-2, ry+(W1), rs, rs*5);
  ellipse(0, ry, rs, rs*5);
  ellipse(rx, ry, rs, rs*5);
  ellipse(rx*2, ry, rs, rs*5);
  pushMatrix();
  rotate((7*PI)/4);
  ellipse(18, -3, 10, 35);
  ellipse(28, 8, 10, 35);
  popMatrix();
  pushMatrix();
  rotate((13*PI)/7);
  ellipse(36, 5, 10, 30);
  popMatrix();
 
  fill(255);
  noStroke();
  rect(0-rx-5, ry+(W1)+Yoffset,6.7,8,15,15,45,45);
  fill(200,200,200);
  stroke(255);
  strokeWeight(1);
  line(0-rx-2-rs/4, ry+(W1)+Yoffset-4, 0-rx-2+rs/4, ry+(W1)+Yoffset-4);
  line(0-rx-2-rs/4, ry+(W1)+Yoffset-6, 0-rx-2+rs/4, ry+(W1)+Yoffset-6);
 
  for (int i=0; i<3; i++){
    fill(255);
    rect(-3+14*i, ry+Yoffset,6.7,8,15,15,45,45);
    line(3+14*i, ry+Yoffset-4, 0-rs/4+14*i, ry+Yoffset-4);
    line(3+14*i, ry+Yoffset-6, 0-rs/4+14*i, ry+Yoffset-6);
  }
 
 
}
 
//-------------------------------------------------------
void drawArtFrameAlternate(int whichFrame) { 
  // An alternate drawing test. 
  // Draw a falling object. 
 
 
  // Draw a little splat on the frame when it hits the ground. 
  if (whichFrame == (nFrames-1)) {
    stroke(0, 0, 0); 
    strokeWeight(0.5); 
    int nL = 10;
    for (int i=0; i<nL; i++) {
      float a = HALF_PI + map(i, 0, nL-1, 0, TWO_PI);
      float cx = 12 * cos(a);
      float cy = 10 * sin(a); 
      float dx = 16 * cos(a);
      float dy = 13 * sin(a); 
      line (cx, 45+cy, dx, 45+dy);
    }
  }
 
  // Draw a little box frame
  fill(255); 
  stroke(0, 0, 0);
  strokeWeight(1); 
  rect(-5, -50, 10, 100); 
 
  // Make the puck accelerate downward
  float t = map(whichFrame, 0, nFrames-1, 0, 1); 
  float t2 = pow(t, 2.0); 
  float rh = 8 + whichFrame * 0.5; // wee stretch
  float ry = map(t2, 0, 1, 0, 100-rh) - 50; 
 
  noStroke(); 
  fill(0, 0, 0);
  rect(-5, ry, 10, rh);
}

chromsan-AnimatedLoop

For this project, I wanted to make something using just geometry made in processing and try to give it a hypnotic quality. I also wanted to play with 2D and 3D shapes and try and find ways to create illusions with their combinations. I experimented a bit and decided on making a set of triangles which merge into a square, which then becomes a pyramid viewed above, which then becomes a triangular hole. After creating this, I wasn't really getting the hypnotic quality I was going for so I added a ball for your eye to follow around the screen, also playing with the physics of the ball and how it interacts with the shapes, in addition to some random movement to give the illusion of air resistance. When coding the loop, the most helpful function, by far, was the map function. I used this to control how every movement is played and the amount of time that each takes as a percentage of the total time. In addition, I added the double exponential sigmoid function to liven up the movements. I liked the way that it gives the transitions a snappy feel. I'm fairly happy with the result, but I think the loop could be reworked towards the end to create a cleverer transition to the beginning, which is much more interesting.

Code is formatted incorrectly by wordpress, the original file is on GitHub

// looping template by
// Prof. Golan Levin, January 2018
 
// Global variables. 
String  myNickname = "nickname"; 
int     nFramesInLoop = 260;
int     nElapsedFrames;
boolean bRecording; 
float myScale = 0.01;
float radius = 100.0;
 
void setup() {
  size (640, 640, P3D); 
  bRecording = false;
  nElapsedFrames = 0;
  frameRate(40);
}
 
void keyPressed() {
  if ((key == 'f') || (key == 'F')) {
    bRecording = true;
    nElapsedFrames = 0;
  }
}
 
void draw() {
 
  // Compute a percentage (0...1) representing where we are in the loop.
  float percentCompleteFraction = 0; 
  if (bRecording) {
    percentCompleteFraction = (float) nElapsedFrames / (float)nFramesInLoop;
  } else {
    percentCompleteFraction = (float) (frameCount % nFramesInLoop) / (float)nFramesInLoop;
  }
 
  // Render the design, based on that percentage. 
  renderMyDesign (percentCompleteFraction);
 
  // If we're recording the output, save the frame to a file. 
  if (bRecording) {
    saveFrame("frames/" + myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png");
    nElapsedFrames++; 
    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
void renderMyDesign (float percent) {
 
  smooth(); 
  // create a rounded percent for easy positioning within the loop
  int roundedPercent = round(percent * 100);
 
  pushMatrix();
 
  if (roundedPercent >= 0 && roundedPercent <= 33){ background(201, 197, 209); noStroke(); // triangles moving in from edges to center float eased = function_DoubleExponentialSigmoid(map(percent, 0, 0.34, 0, 1), 0.8); fill(112, 88, 124); triangle(0, height, width, height, width/2, map(eased, 0, 1, height, height/2 )); fill( 57, 43, 88); triangle(0, 0, 0, height, width/2 * map(eased, 0, 1, 0, 1), height/2); fill(45, 3, 32); triangle(0, 0, width, 0, width/2, height/2 * map(eased, 0, 1, 0, 1)); fill(108, 150, 157); triangle(width, 0, width, height, map(eased, 0, 1, width, width/2), height/2); // rectangle in center shrinking float rectWidth = map(eased, 0, 1, width, 0); float rectHeight = map(eased, 0,1, height, 0); fill(219, 216, 224); rect((width- rectWidth)/2, (height - rectHeight)/2, rectWidth, rectHeight); } else if (roundedPercent > 33 && roundedPercent <= 66) { pushMatrix(); translate(width/2, height/2, 0); ortho(-width/2, width/2, -height/2, height/2); float eased = function_DoubleExponentialSigmoid(map(percent, 0.33, 0.67, 0, 1), 0.9); // Move from above to side, rotate 45 deg as well rotateX(PI/map(eased, 0, 1, 1200, 2)); rotateZ(PI/map(eased, 0, 1, 1200, 2)); background(201, 197, 209); // 3D Triangle fill(45, 3, 32); beginShape(); scale(map(percent, 0.33, 0.66, 3.6, 1.8), map(percent, 0.33, 0.66, 3.6, 1.8), map(percent, 0.33, 0.66, 3.6, 1.8)); vertex(-100, -100, -100); vertex( 100, -100, -100); vertex( 0, 0, 100); endShape(); fill(108, 150, 157); beginShape(); vertex( 100, -100, -100); vertex( 100, 100, -100); vertex( 0, 0, 100); endShape(); fill(112, 88, 124); beginShape(); vertex( 100, 100, -100); vertex(-100, 100, -100); vertex( 0, 0, 100); endShape(); fill( 57, 43, 88); beginShape(); vertex(-100, 100, -100); vertex(-100, -100, -100); vertex( 0, 0, 100); endShape(); popMatrix(); } else if (roundedPercent > 66) {
 
    float eased = function_DoubleExponentialSigmoid(map(percent, 0.66, 1, 0, 1), 0.9);
    // transition from pale blue to dark purple
    color start = color(108, 150, 157);
    color end = color(102, 71, 92);
    color lerpedCol = lerpColor(start, end, eased);
 
    if (roundedPercent < 83){
 
     background(201, 197, 209);
     fill(lerpedCol);
     translate(0,0);
     // replace 3D triangle with 2D triangle (nasty specific values)
     triangle(map(eased, 0, 1, 140, -140), map(eased, 0, 1, 500, 700), 
              map(eased, 0, 1, 500, 700), map(eased, 0, 1, 500, 700), 
              map(eased, 0, 1, 320, 320), map(eased, 0, 1, 140, -140));
    }
    else {
      // square move in from center to complete loop
      float eased2 = function_DoubleExponentialSigmoid(map(percent, 0.82, 1, 0, 1), 0.5);
      background(lerpedCol);
      float rectWidth = map(eased2, 0, 1, 0, width);
      float rectHeight = map(eased2, 0, 1, 0, height);
      fill(219, 216, 224);
      rect((width- rectWidth)/2, (height - rectHeight)/2, rectWidth, rectHeight);
    }
  }
  popMatrix();
  pushMatrix();
  renderBall(percent, roundedPercent);
  popMatrix();
}
 
// weird bouncing ball shenanigans 
void renderBall(float percent, int roundedPercent) {
 
  // make nose map that gives ball a little random sway 
   int currStep = frameCount % nFramesInLoop;
   float t = map(currStep, 0, nFramesInLoop, 0, TWO_PI); 
   float px = width/2.0 + radius * cos(t); 
   float py = width/2.0 + radius * sin(t);
   float noiseAtLoc = height - 100.0 * noise(myScale*px, myScale*py);
   float noiseAdjusted = map(noiseAtLoc, 570, 620, -15, 15);
   float xDrift = noiseAdjusted;
   float yDrift = noiseAdjusted;
 
 
    if (roundedPercent <= 25){ // ball moves towards bottom, larger float x = lerp(width/2, width/2 + 100, map(percent, 0, 0.25, 0, 1)); float y = lerp(height/2, height - 200, map(percent, 0, 0.25, 0, 1)); float scale = lerp(5, 75, map(percent, 0, 0.25, 0, 1)); translate(x + xDrift, y + yDrift, 400); fill(map(scale, 85, 5, 255,50)); sphere(scale); } else if (roundedPercent > 25 && roundedPercent <= 55) { // ball moves towards center, smaller float x = lerp(width/2 + 100, width/2, map(percent, 0.25, 0.55, 0, 1)); float y = lerp(height - 200, height/2, map(percent, 0.25, 0.55, 0, 1)); float scale = lerp(75, 2, map(percent, 0.25, 0.55, 0, 1)); translate(x + xDrift, y + yDrift, 400); fill(map(scale, 85, 5, 255, 50)); sphere(scale); } else if (roundedPercent > 55 && roundedPercent <= 60) { // ball moves to side float x = lerp(width/2, 15, map(percent, 0.55, 0.6, 0, 1)); float y = lerp(height/2, height/2, map(percent, 0.55, 0.6, 0, 1)); float scale = lerp(5, 25, map(percent, 0.55, 0.6, 0, 1)); translate(x + xDrift, y + yDrift, 400); fill(map(scale, 75, 5, 255, 50)); sphere(scale); } else if (roundedPercent > 60 && roundedPercent <= 66) { // ball goes right float x = lerp(15, width/4, map(percent, 0.6, 0.66, 0, 1)); float y = lerp(height/2, height/3 * 2, map(percent, 0.6, 0.66, 0, 1)); //float scale = lerp(5, 25, map(percent, 0.6, 0.6, 0.66, 1)); translate(x + xDrift, y + yDrift, 400); fill(map(25, 75, 5, 255, 50)); sphere(25); } else if (roundedPercent > 66 && roundedPercent <= 85) { // ball gets smaller, towards center float x = lerp(width/4, width/2, map(percent, 0.6, 0.85, 0, 1)); float y = lerp(height/3 * 2, height/2, map(percent, 0.6, 0.85, 0, 1)); float easedx = function_DoubleExponentialSigmoid(map(x, width/4, width/2, 0, 1), 0.8); float easedy = function_DoubleExponentialSigmoid(map(y, height/3 * 2, height/2, 0, 1), 0.8); float scale = lerp(25, 15, map(percent, 0.66, 0.85, 0, 1)); translate(map(easedx, 0, 1, width/4, width/2) + xDrift, map(easedy, 0, 1, height/3 * 2, height/2) + yDrift, 400); fill(map(scale, 75, 5, 255, 50)); sphere(scale); } else if (roundedPercent > 85 && roundedPercent <= 100) {
    // ball gets larger 
    float scale = lerp(15, 5, map(percent, 0.85, 1, 0, 1));
    translate(width/2 + xDrift, height/2 + yDrift, 400);
    fill(map(scale, 75, 5, 255, 50));
    sphere(scale);
  }
}
 
// https://github.com/golanlevin/Pattern_Master
float function_DoubleExponentialSigmoid (float x, float a) {
  // functionName = "Double-Exponential Sigmoid";
 
  float min_param_a = 0.0 + EPSILON;
  float max_param_a = 1.0 - EPSILON;
  a = constrain(a, min_param_a, max_param_a); 
  a = 1-a;
 
  float y = 0;
  if (x<=0.5) {
    y = (pow(2.0*x, 1.0/a))/2.0;
  } else {
    y = 1.0 - (pow(2.0*(1.0-x), 1.0/a))/2.0;
  }
  return y;
}

shuann-Scope

PDF file: shuann-praxinoscope-output

For this exercise I wanted to practice using sin/cos function to create wave patterns so I explored some aspects of that. I also played a little with rotation, transformation, push, pop, and scaling. One challenge I found that it is harder to design for a very limited space. Also, since this is my first time using processing I also tried to familiarized myself with the syntax and the available built-in functions. The good thing is that I did find that it is very similar to javascript which saved my a lot of time.

 

// Template for KidzLabs/4M/Toysmith Animation Praxinoscope
// https://www.amazon.com/4M-3474-Animation-Praxinoscope/dp/B000P02HYC
// https://www.walmart.com/ip/Animation-Praxinoscope-Science-Kits-by-Toysmith-3474/45681503
// Developed for Processing 3.3.6 * http://processing.org
// 23 January 2018 * Golan Levin 
 
// See information about Processing PDF export at: 
// https://processing.org/reference/libraries/pdf/index.html
// PDF generated by Processing can be opened in Adobe Illustrator.
import processing.pdf.*;
boolean bRecordingPDF = false;
 
float inch = 72; 
float diamArtInner = inch * 1.50; 
float diamArtOuter = inch * 4.80; 
float diamCutInner = inch * 1.41; 
float diamCutOuter = inch * 4.875; 
float holeDy = inch * 0.23;
float holeDx = inch * 0.20;
float holeD = inch * 0.1;
 
final int nFrames = 10; 
int myFrameCount = 0;
int exportFrameCount = 0; 
boolean bAnimate = true; 
boolean bExportFrameImages = false;
 
//-------------------------------------------------------
void setup() {
  size(792, 612); // 11x8.5" at 72DPI
  frameRate(15);
  smooth();
} 
 
//-------------------------------------------------------
void draw() {
  background(240); 
  if (bRecordingPDF) {
    beginRecord(PDF, "praxinoscope-output.pdf");
  }
 
  // Do all the drawing. 
  pushMatrix(); 
  translate(width/2, height/2);
  drawCutLines(); 
  drawGuides(); 
  drawAllFrames();
  popMatrix();
 
  if (bExportFrameImages) {
    // If activated, export .PNG frames 
    if (exportFrameCount < nFrames) {
      String filename = "frame_" + nf((exportFrameCount%nFrames), 3) + ".png";
      saveFrame("frames/" + filename);
      println("Saved: " + filename); 
      exportFrameCount++;
      if (exportFrameCount >= nFrames) {
        bExportFrameImages = false;
        exportFrameCount = 0;
      }
    }
  }
 
  if (bRecordingPDF) {
    endRecord();
    bRecordingPDF = false;
  }
}
 
 
//-------------------------------------------------------
void keyPressed() {
  switch (key) {
  case ' ': 
    // Press spacebar to pause/unpause the animation. 
    bAnimate = !bAnimate;
    break;
 
  case 'p': 
  case 'P':
    // Press 'p' to export a PDF for the Praxinoscope.
    bRecordingPDF = true; 
    break;
 
  case 'f': 
  case 'F': 
    // Press 'f' to export .png Frames (to make an animated .GIF)
    myFrameCount = 0; 
    exportFrameCount = 0; 
    bExportFrameImages = true;
    bAnimate = true; 
    break;
  }
}
 
//-------------------------------------------------------
void drawCutLines() {
  fill(0); 
  textAlign(CENTER, BOTTOM); 
  text("Praxinoscope Template", 0, 0-diamCutOuter/2-6); 
 
  stroke(0); 
  strokeWeight(1.0);
 
  noFill(); 
  if (!bRecordingPDF) {
    fill(255); 
  }
  ellipse(0, 0, diamCutOuter, diamCutOuter);
 
  noFill(); 
  if (!bRecordingPDF) {
    fill(240); 
  }
  ellipse(0, 0, diamCutInner, diamCutInner);
 
  noFill(); 
  ellipse(diamCutOuter/2 - holeDx, 0-holeDy, holeD, holeD); 
 
  line (diamCutInner/2, 0, diamCutOuter/2, 0);
}
 
//-------------------------------------------------------
void drawGuides() {
  // This function draws the guidelines. 
  // Don't draw these when we're exporting the PDF. 
  if (!bRecordingPDF) {
 
    noFill(); 
    stroke(128); 
    strokeWeight(0.2); 
    ellipse(0, 0, diamArtInner, diamArtInner); 
    ellipse(0, 0, diamArtOuter, diamArtOuter);
 
    for (int i=0; i<nFrames; i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
      float pxi = diamArtInner/2 * cos(angle);
      float pyi = diamArtInner/2 * sin(angle);
      float pxo = diamArtOuter/2 * cos(angle);
      float pyo = diamArtOuter/2 * sin(angle);
      stroke(128); 
      strokeWeight(0.2);
      line (pxi, pyi, pxo, pyo);
    }
 
    // Draw the red wedge outline, highlighting the main view.
    int redWedge = 7; // assuming nFrames = 10
    for (int i=redWedge; i<=(redWedge+1); i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
      float pxi = diamArtInner/2 * cos(angle);
      float pyi = diamArtInner/2 * sin(angle);
      float pxo = diamArtOuter/2 * cos(angle);
      float pyo = diamArtOuter/2 * sin(angle);
      stroke(255, 0, 0); 
      strokeWeight(2.0);
      line (pxi, pyi, pxo, pyo);
    }
    noFill(); 
    stroke(255, 0, 0); 
    strokeWeight(2.0);
    float startAngle = redWedge*TWO_PI/nFrames;
    float endAngle = (redWedge+1)*TWO_PI/nFrames;
    arc(0, 0, diamArtInner, diamArtInner, startAngle, endAngle); 
    arc(0, 0, diamArtOuter, diamArtOuter, startAngle, endAngle); 
 
 
    for (int i=0; i<nFrames; i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
 
      pushMatrix();
      rotate(angle); 
      float originY = ((diamArtOuter + diamArtInner)/2)/2;
      translate(0, 0-originY); 
 
      noFill(); 
      stroke(128); 
      strokeWeight(0.2);
      line (-inch/2, 0, inch/2, 0); 
      line (0, -inch/2, 0, inch/2); 
 
      popMatrix();
    }
  }
}
 
//-------------------------------------------------------
void drawAllFrames() {
  for (int i=0; i<nFrames; i++) {
    float angle = map(i, 0, nFrames, 0, TWO_PI); 
    float originY = ((diamArtOuter + diamArtInner)/2)/2;
 
    pushMatrix();
    rotate(angle); 
    translate(0, 0-originY); 
    scale(0.8, 0.8); // feel free to ditch this 
 
    int whichFrame = i; 
    if (bAnimate) {
      whichFrame = (i+myFrameCount)%nFrames;
    }
    drawArtFrame (whichFrame); 
    // drawArtFrameAlternate (whichFrame); 
 
    popMatrix();
  }
  myFrameCount++;
}
 
 
//-------------------------------------------------------
void drawArtFrame (int whichFrame) { 
  // Draw the artwork for a generic frame of the Praxinoscope, 
  // given the framenumber (whichFrame) out of nFrames.
  // NOTE #1: The "origin" for the frame is in the center of the wedge.
  // NOTE #2: Remember that everything will appear upside-down!
 
  pushMatrix();
  noStroke();
  strokeWeight(1); 
  translate(0, 40);
  float t = map(whichFrame, 0, nFrames, 0, 1); 
  float factor = map(cos(t*TWO_PI), -1, 1, 0.3, 1); 
  float col = map(t, 0, 1, 120, 0); 
  fill(col);
  scale(factor, factor);
  triangle(0, -15, 10, 0, -10, 0); 
  triangle(10, 0, 20, 15, 0, 15); 
  triangle(-10, 0, 0, 15, -20, 15); 
  popMatrix();
 
  pushMatrix();
  translate(0, -35);
  noFill();
  stroke(0);
  float ang = map(t, 0, nFrames, 0, TWO_PI); 
  rotate(ang); 
  for (int j=0; j<10; j++){
    rotate(radians(36));
    ellipse(15, 0, 12, 5);
  }
  endShape();
  popMatrix();
 
  //draw waves
  pushMatrix();
  translate(0, 10);
  beginShape(POINTS);
  stroke(0);
  for (int i=0; i<70; i++){
    float h = 0 - map(i, 0, 69, 0, 1);
    if (whichFrame%nFrames<5){
      vertex(i - 35, 18 * cos(h * TWO_PI) + whichFrame);
    } else {
      vertex(i - 35, 18 * cos(h * TWO_PI) + (9-whichFrame));
    }
  }
  endShape();
  popMatrix();
}
 
//-------------------------------------------------------
void drawArtFrameAlternate(int whichFrame) { 
  // An alternate drawing test. 
  // Draw a falling object. 
 
 
  // Draw a little splat on the frame when it hits the ground. 
  if (whichFrame == (nFrames-1)) {
    stroke(0, 0, 0); 
    strokeWeight(0.5); 
    int nL = 10;
    for (int i=0; i<nL; i++) {
      float a = HALF_PI + map(i, 0, nL-1, 0, TWO_PI);
      float cx = 12 * cos(a);
      float cy = 10 * sin(a); 
      float dx = 16 * cos(a);
      float dy = 13 * sin(a); 
      line (cx, 45+cy, dx, 45+dy);
    }
  }
 
  // Draw a little box frame
  fill(255); 
  stroke(0, 0, 0);
  strokeWeight(1); 
  rect(-5, -50, 10, 100); 
 
  // Make the puck accelerate downward
  float t = map(whichFrame, 0, nFrames-1, 0, 1); 
  float t2 = pow(t, 2.0); 
  float rh = 8 + whichFrame * 0.5; // wee stretch
  float ry = map(t2, 0, 1, 0, 100-rh) - 50; 
 
  noStroke(); 
  fill(0, 0, 0);
  rect(-5, ry, 10, rh);
}

.

nixel-AnimatedLoop

I had a lot of fun with this project!

My process:

I started with some messy brainstorms from when we first got the prompt:

I thought that loops of characters bouncing down stairs, or being transported by a never ending escalator would be fun. I also considered doing skyscapes that looped between sunrise and sunset and a swinging character. In the end, I went with rain because it was the simplest in terms of shape and color design.

I thought that I would do some sort of walk cycle and plotted some leg rotations.

I went online and found a shape-heavy walk cycle gif and spliced some frames out to break it down.

However, when I began to code and actually look into the easing functions, I realized that it would be simpler to pair them with a bouncing animation instead of a walking one. It was sad to leave all the walking animation work I did, but I think I made a better choice in terms of time and simplicity. I think I will go back one day and make all of the ideas I had initially.

After a lot of experimentation (and suffering) I chose to use primarily the Flat Top Window easing function since it was bouncy, playful, and worked well for a looping animation since it was symmetrical.

Here is a thumbnail I made half way through coding in order to hash out the locations of some body parts.

I struggled with the rain since I thought it added to the narrative of the gif, but didn't necessarily loop. I also struggled with the umbrella since it's an interesting visual element but doesn't make much sense since the character isn't holding on to it. I did at one point code an umbrella handle and hands, but it made the image too complex so I got rid of them.

Critiquing myself, I think that I did well in keeping the design simple and appealing. I could have been more ambitious with connecting the umbrella to the character and been more efficient with my code (I organized it in groups of body parts but ended up repeating a lot of simple functions like noStroke() or fill() when I could have just organized the code more efficiently).

 

 
// This is a template for creating a looping animation in Processing/Java. 
// When you press the 'F' key, this program will export a series of images
// into a "frames" directory located in its sketch folder. 
// These can then be combined into an animated gif. 
// Known to work with Processing 3.3.6
// Prof. Golan Levin, January 2018
 
 
//===================================================
// Global variables. 
String  myNickname = "nixel"; 
int     nFramesInLoop = 60;
int     nElapsedFrames;
boolean bRecording; 
Drop[] drops = new Drop[50];
 
//===================================================
void setup() {
  size (640, 640); 
  bRecording = false;
  nElapsedFrames = 0;  
  for (int i = 0; i < drops.length; i++) {
   drops[i] = new Drop(); 
  }
}
//===================================================
void keyPressed() {
  if ((key == 'f') || (key == 'F')) {
    bRecording = true;
    nElapsedFrames = 0;
  }
}
//===================================================
void draw() {
  // Compute a percentage (0...1) representing where we are in the loop.
  float percentCompleteFraction = 0; 
  if (bRecording) {
    percentCompleteFraction = (float) nElapsedFrames / (float)nFramesInLoop;
  } else {
    percentCompleteFraction = (float) (frameCount % nFramesInLoop) / (float)nFramesInLoop;
  }
 
  // Render the design, based on that percentage. 
  renderMyDesign (percentCompleteFraction);
 
  // If we're recording the output, save the frame to a file. 
  if (bRecording) {
    saveFrame("frames/" + myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png");
    nElapsedFrames++; 
    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
    }
  }
}
//===================================================
void renderMyDesign (float percent) {
  int bgColor = 255;
  background (bgColor);
  stroke (0, 0, 0); 
  strokeWeight (2); 
  float pennerEase = FlatTopWindow(percent);
  noStroke();
 
  //umbrella
  float umbrellashift = (map(pennerEase, 0, 1, -50, -20)); 
  fill(255, 0, 0);
  ellipse(width/2, 240 + umbrellashift, 360, 360);
 
  //cutout umbrella
  fill(bgColor);
  int r = 250;
  for (int i = 0; i < 360; i += 30){
  float dy = sin(radians(i)) * r;
  float dx = cos(radians(i)) * r;
  ellipse(width/2 + dx, 240 + umbrellashift + dy, 180, 180);
  }
 
  rectMode(CENTER);
 
  //ground
  fill(10, 95, 223);
  rect(width/2, 600, width, 200);
 
  //puddle
  noFill();
  stroke(255);
  float jumpWeight = map(percent, 0, 1, 15, 0);
  float puddleEase = map(PennerEaseOutExpo(percent-0.7), 0, 1, 100, 1000);
  float puddleEaseY = map(PennerEaseOutExpo(percent), 0, 1, 0, 50); 
  strokeWeight(jumpWeight);
 
  if (percent >= 0.7){
    ellipse(355, 530, puddleEase, puddleEaseY);
    ellipse(200, 560, puddleEase, puddleEaseY);
    ellipse(300, 600, puddleEase, puddleEaseY);
    ellipse(50, 650, puddleEase, puddleEaseY);
    ellipse(550, 610, puddleEase, puddleEaseY);
  }
 
  //hair
  noStroke();
  float hairshift = (map(pennerEase, 0, 1, 60, -40)); 
  fill(0);
  arc(width/2, 240 + hairshift, 180, 300, PI, TWO_PI);
 
 
  //legs 
  float rX = map(pennerEase, 0, 1, 350, 340);
  float Y = map(pennerEase, 0, 1, 560, 510);
  float squishX = map(pennerEase, 0, 1, 10, 0);
  float lX = map(pennerEase, 0, 1, width - 360, width - 350);
  fill(247,227,40);
 
  //right leg
  triangle(rX, Y, rX - 5 - squishX, Y - 150, rX + 10 + squishX, Y - 150);
 
  //left leg
  triangle(lX, Y, lX - 5 - squishX, Y - 150, lX + 10 + squishX, Y - 150);
 
  //body
	float bsquish = map(pennerEase, 0, 1, 10, -10);
  float bodyshift = (map(pennerEase, 0, 1, 50, -20)); 
  fill(247, 227, 40);
  triangle(width/2, 170 + bodyshift, width/2 - 100 - bsquish, 380 + bodyshift, width/2 + 100 + bsquish, 380 + bodyshift);
 
  //mirrored legs 
  float mrX = map(pennerEase, 0, 1, 350, 340);
  float mrY = map(pennerEase, 0, 1, 510, 560);
  float msquishX = map(pennerEase, 0, 1, 10, 0);
  float mlX = map(pennerEase, 0, 1, width - 360, width - 350);
  fill(247,227,40);
 
  //right leg
  triangle(mrX, mrY + 50, mrX - 5 - msquishX, mrY + 175, mrX + 10 + msquishX, mrY + 175);
 
  //left leg
  triangle(mlX, mrY + 50, mlX - 5 - msquishX, mrY + 175, mlX + 10 + msquishX, mrY + 175);
  //head
  float headshift = (map(pennerEase, 0, 1, 55, -30)); 
  fill(255);
  ellipse(width/2, 170 + headshift, 100, 100);
 
  //mouth
  float mouthshift = (map(pennerEase, 0, 1, 30, -70)); 
  float mouthY = (map(pennerEase, 0, 1, -5, 10)); 
  fill(255, 0, 0);
  ellipse(width/2, 225 + mouthshift, 20, 15 + mouthY);
 
  //bangs  
  fill(0);
  arc(width/2, 150 + hairshift, 100, 100, PI, TWO_PI);
 
  //eyes
  float eyeY = (map(pennerEase, 0, 1, 1, 5)); 
  float eyeX = (map(pennerEase, 0, 1, 1, 5)); 
  ellipse(width/2 - 30, 200 + mouthshift, 10 + eyeX, 15 + eyeY);
  ellipse(width/2 + 30, 200 + mouthshift, 10 + eyeX, 15 + eyeY);
 
  //arms
  fill(247,227,40);
 
  float Ya = map(pennerEase, 0, 1, 10, 80);
  //right arm
  triangle(345, 230 + bodyshift, 345, 260 + bodyshift, 450, 250 + Ya);
 
  //left arm
  triangle(width - 345, 230 + bodyshift, width - 345, 260 + bodyshift, width - 450, 250 + Ya);
 
  //rain
  for (int i = 0; i < drops.length; i++){
  drops[i].fall();
  drops[i].show();
  }
 
}
 
//===================================================
//rain code refenced from Dan Shiffman's Purple Rain Coding Challenge
class Drop {
 float x = random(width);
 float y = random(0, height);
 float yspeed = 11;
 float len = 15;
 
 void fall(){
   y += yspeed;
 
  if (y > height) {
   y = 0;
   yspeed = 11;
  }
 
   }
 
 void show(){
  stroke(10, 95, 223);
  strokeWeight(2);
  line(x, y, x, y+len);
 }
}
 
//===================================================
float FlatTopWindow (float x) {
  //easing functions from Pattern Master https://github.com/golanlevin/Pattern_Master
  // http://en.wikipedia.org/wiki/Window_function 
 
  final float a0 = 1.000;
  final float a1 = 1.930;
  final float a2 = 1.290;
  final float a3 = 0.388;
  final float a4 = 0.032;
 
  float pix = PI*x;
  float y = a0 - a1*cos(2*pix) + a2*cos(4*pix) - a3*cos(6*pix) + a4*cos(8*pix);
  y /= (a0 + a1 + a2 + a3 + a4); 
 
  return y;
}
 
float PennerEaseOutExpo(float t) {
  return (t==1) ? 1 : (-pow(2, -10 * t) + 1);
}

dinkolas-Scope

Design PDF: dinkolas-praxinoscope-output

About

I wanted to create a caterpillar-like creature for my praxinoscope. Most of the motion was simple enough, just sines and cosines with offset phases took care of it. However, it took some tricks that I'm pretty happy with to implement knees and a ground. The location of the knees is found by intersecting two circles, one centered at the hip and the other centered at the foot (thanks to johannesvalks on Stack Exchange for the equations for circle intersection). In order for there to be a ground, the feet move in a circle except the y coordinate is clipped to a minimum value. The code is pretty rough, but it works!

Code

I used Golan's Java Processing template. Here's my code in drawArtFrame():

void drawArtFrame (int whichFrame) { 
  // Draw the artwork for a generic frame of the Praxinoscope, 
  // given the framenumber (whichFrame) out of nFrames.
  // NOTE #1: The "origin" for the frame is in the center of the wedge.
  // NOTE #2: Remember that everything will appear upside-down!
 
  //Intersection of two circles from johannesvalks on Stack Exchange
  stroke(128);
  strokeWeight(1);
  line(50,-29,-50,-29);
  stroke(0);
  strokeWeight(2);
  fill(0);
  float t = map(whichFrame, 0, nFrames, 0, 1); 
  float segments = 6;
  for (float i = 0; i &lt; segments; i++)
  {
    float x1 = map(i,0,segments-1,-35,35);
    x1 += map(-sin(t*TWO_PI-i+.75),-1,1,-1,1);
    float y1 = map(cos(t*TWO_PI-i),-1,1,-10,0);
    ellipse(x1,y1,15,15);
    float x2 = map(-cos(t*TWO_PI-i),-1,1,x1+5,x1-5);
    float y2 = map(sin(t*TWO_PI-i),-1,1,-30,-20);
    y1 -= 15/2;
    y2 = max(y2,-27);
    float mult = sqrt(2*20/(pow((x1-x2),2)+pow((y1-y2),2)));
    float kneexLoc = ((x1+x2)+mult*(y2-y1))/2;
    float kneeyLoc = ((y1+y2)+mult*(x1-x2))/2;
    line(x1,y1,kneexLoc,kneeyLoc);
    line(kneexLoc,kneeyLoc,x2,y2);
    rect(x2-4,y2,4,1);
 
    x2 = map(-cos(t*TWO_PI-i+2.5),-1,1,x1+5,x1-5);
    y2 = map(sin(t*TWO_PI-i+2.5),-1,1,-30,-20);
    y2 = max(y2,-27);
    kneexLoc = ((x1+x2)+mult*(y2-y1))/2;
    kneeyLoc = ((y1+y2)+mult*(x1-x2))/2;
    line(x1,y1,kneexLoc,kneeyLoc);
    line(kneexLoc,kneeyLoc,x2,y2);
    rect(x2-4,y2,4,1);
  }
 
}

 

Sepho-Scope

For my loop I chose to do a pretty simple pulse design that travels around the ring. I made it using a line connecting multiple vertices.

Sepho – Animated Loop

I was really fascinated with the method of using circular motion over Perlin noise to get random yet looping 1-D graph. For my GIF I decided to expand on this by using two 1-D graphs as x and y positions for points to get a random motion that looped. I then made many points that moved in random loops and created shapes from them, resulting in crazy, abstract, looping shapes. Overall this project was a fun exploration of how math and graphs can translate to motion and visa versa.

Visualization of point movement:

 

breep-AnimatedLoop

I ended up using the double exponential sigmoid as I wanted the squares to be able to tend toward the centre point from both sides. I initially had blocked out the squares on rectangles which highlighted where the squares would appear and re-appear, but I didn't like how busy they made the image. Once this had been laid out, I had to adjust the percentage amounts of each square on the sigmoid so that they all paused at that common central point. Once this was done I wanted to incorporate some form of spiral motion, but I wanted it to also be somewhat bi-parted as well to go with the squares, hence the two spiralling arms.

I felt I succeeded with the sigmoid nature of the squares, and am pretty proud of how they as a unit follow a curve, but also individually do. I still think the image is a bit too busy, and I still would've liked to incorporate more circular motion but it would have oversaturated the gif with content I think. The squares don't line up as well as I would like, given that I had to adjust the percentages of the sigmoids by eye so a more mathematic approach to that would make it appear that bit more fluid.

 
// Template used from 60-212 Website 
 
// This is a template for creating a looping animation in p5.js (JavaScript). 
// When you press the 'F' key, this program will export a series of images into
// your default Downloads folder. These can then be made into an animated gif. 
// This code is known to work with p5.js version 0.6.0
// Prof. Golan Levin, 28 January 2018
 
// INSTRUCTIONS FOR EXPORTING FRAMES (from which to make a GIF): 
// 1. Run a local server, using instructions from here:
//    https://github.com/processing/p5.js/wiki/Local-server
// 2. Set the bEnableExport variable to true.
// 3. Set the myNickname variable to your name.
// 4. Run the program from Chrome, press 'f'. 
//    Look in your 'Downloads' folder for the generated frames.
// 5. Note: Retina screens may export frames at twice the resolution.
 
 
//===================================================
// User-modifiable global variables. 
var myNickname = "nickname";
var nFramesInLoop = 120;
var bEnableExport = false;
 
// Other global variables you don't need to touch.
var nElapsedFrames;
var bRecording;
var theCanvas;
 
 
 
//===================================================
function setup() {
  theCanvas = createCanvas(640, 640);
  bRecording = false;
  nElapsedFrames = 0;
  frameRate(120); 
}
 
//===================================================
function keyTyped() {
  if (bEnableExport) {
    if ((key === 'f') || (key === 'F')) {
      bRecording = true;
      nElapsedFrames = 0;
    }
  }
}
 
//===================================================
 
function draw() {
 
  // Compute a percentage (0...1) representing where we are in the loop.
  var percentCompleteFraction = 0;
  if (bRecording) {
    percentCompleteFraction = float(nElapsedFrames) / float(nFramesInLoop);
  } else {
    percentCompleteFraction = float(frameCount % nFramesInLoop) / float(nFramesInLoop);
  }
 
  // Render the design, based on that percentage. 
  // This function renderMyDesign() is the one for you to change. 
  renderMyDesign (percentCompleteFraction);
 
  // If we're recording the output, save the frame to a file. 
  // Note that the output images may be 2x large if you have a Retina mac. 
  // You can compile these frames into an animated GIF using a tool like: 
  if (bRecording && bEnableExport) {
    var frameOutputFilename = myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png";
    print("Saving output image: " + frameOutputFilename);
    saveCanvas(theCanvas, frameOutputFilename, 'png');
    nElapsedFrames++;
 
    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
    }
  }
} 
 
//===================================================
function renderMyDesign (percent) {
  //
  // THIS IS WHERE YOUR ART GOES. 
  // This is an example of a function that renders a temporally looping design. 
  // It takes a "percent", between 0 and 1, indicating where we are in the loop. 
  // Use, modify, or delete whatever you prefer from this example. 
  // This example uses several different graphical techniques. 
  // Remember to SKETCH FIRST!
 
  //----------------------
  // here, I set the background and some other graphical properties
  background(0);
  smooth();
  stroke(0, 0, 0);
  strokeWeight(0);
 
  //----------------------
  // Here, I assign some handy variables. 
  var cx = 100;
  var cy = 100;
 
  //----------------------
 
  // COLOUR NUMBERS FROM BOTTOM
 
  // ORANGE 1 
  //strokeWeight(0);
  //fill(60, 170, 230);
  //rect(192, 576, 384, 32);   
 
  var eased = doubleExponentialSigmoid (percent, 0.9); 
  eased = (eased + 0.319)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 192, 544); 
  fill (253, 106, 2); 
  rect (xPosition1, 576, 32, 32);     
 
  // ORANGE 2 
  //strokeWeight(0);
  //fill(60, 170, 230);
  //rect(160, 512, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.8); 
  eased = (eased + 0.45)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 160, 480); 
  fill (253, 106, 20); 
  rect (xPosition1, 512, 32, 32);  
 
  // ORANGE 3 
  //strokeWeight(0);
  //fill(60, 170, 230);
  //rect(128, 448, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.6); 
  eased = (eased + 0.5)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 128, 480); 
  fill (253, 106, 2); 
  rect (xPosition1, 448, 32, 32);    
 
  // ORANGE 4 
  //strokeWeight(0);
  //fill(60, 170, 230);
  //rect(192, 384, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.4); 
  eased = (eased + 0.3)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 192, 544); 
  fill (253, 106, 20); 
  rect (xPosition1, 384, 32, 32);     
 
  // ORANGE 5 
  //strokeWeight(0);
  //fill(60, 170, 230);
  //rect(64, 320, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.2); 
  eased = (eased + 0.66)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 64, 416); 
  fill (253, 106, 20); 
  rect (xPosition1, 320, 32, 32);  
 
  // ORANGE 6 
  //strokeWeight(0);
  //fill(60, 170, 230);
  //rect(128, 256, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.2); 
  eased = (eased + 0.47)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 128, 480); 
  fill (253, 106, 20); 
  rect (xPosition1, 256, 32, 32);  
 
  // ORANGE 7 
  //strokeWeight(0);
  //fill(60, 170, 230);
  //rect(224, 192, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.4); 
  eased = (eased + 0.19)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 224, 576); 
  fill (253, 106, 20); 
  rect (xPosition1, 192, 32, 32);  
 
  // ORANGE 8
  //strokeWeight(0);
  //fill(60, 170, 230);
  //rect(156, 128, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.6); 
  eased = (eased + 0.39)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 156, 508); 
  fill (253, 106, 20); 
  rect (xPosition1, 128, 32, 32);   
 
  // ORANGE 9
  //strokeWeight(0);
  //fill(60, 170, 230);
  //rect(32, 64, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.8); 
  eased = (eased + 0.73)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 32, 384); 
  fill (253, 106, 20); 
  rect (xPosition1, 64, 32, 32);   
 
  // ORANGE 10
  //strokeWeight(0);
  //fill(60, 170, 230);
  //rect(128, 0, 384, 32); 
 
  var eased = doubleExponentialSigmoid (percent, 0.9); 
  eased = (eased + 0.47)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 128, 480); 
  fill (253, 106, 2); 
  rect (xPosition1, 0, 32, 32);        
 
  push();
  translate(320, 320);
  var orange = true; 
  for (var squareX = 0; squareX <= width; squareX += 32){
 
   // Here's a linearly-moving square
   var eased = doubleExponentialSigmoid (percent, 0.5); 
   eased = (eased)%1.0; // shifted by a half-loop, for fun
   var yPosition = map(eased, 0, 1, topY, botY);    
 
   var rotatingSquareAngle = percent * TWO_PI
   rotate(rotatingSquareAngle);  
 
   var squareSize = 20;
   var topY = 0 - squareSize - 2;
   var botY = height + 2;
   var sPercent = (percent)%1.0; // shifted by a half-loop
   // var yPosition = map(sPercent, 0, 1, topY, botY);
 
   if (orange == true){
     fill(253, 106, 2);
     orange = false;}
   else{
    fill(60, 170, 230);
    orange = true;}
 
   rect(squareX, yPosition, 20, 20);}   
  pop();  
 
 
  // BLUE 1 
  // strokeWeight(0);
  // fill(120, 253, 106, 2);
  // rect(128, 608, 384, 32); 
 
  var eased = doubleExponentialSigmoid (percent, 0.2); 
  eased = (eased + 0.5)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 128, 480); 
  fill (60, 170, 230); 
  rect (xPosition1, 608, 32, 32);   
 
  // BLUE 2 
  //strokeWeight(0);
  //fill(253, 106, 2);
  //rect(64, 544, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.4); 
  eased = (eased + 0.682)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 64, 416); 
  fill (60, 170, 230); 
  rect (xPosition1, 544, 32, 32);     
 
  // BLUE 3 
  //strokeWeight(0);
  //fill(253, 106, 2);
  //rect(224, 480, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.6); 
  eased = (eased + 0.23)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 224, 576); 
  fill (60, 170, 230); 
  rect (xPosition1, 480, 32, 32);  
 
  // BLUE 4 
  //strokeWeight(0);
  //fill(253, 106, 2);
  //rect(32, 416, 384, 32);    
 
  var eased = doubleExponentialSigmoid (percent, 0.8); 
  eased = (eased + 0.77)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 32, 384); 
  fill (60, 170, 230); 
  rect (xPosition1, 416, 32, 32);    
 
  // BLUE 5 
  //strokeWeight(0);
  //fill(253, 106, 2);
  //rect(160, 352, 384, 32); 
 
  var eased = doubleExponentialSigmoid (percent, 0.9); 
  eased = (eased + 0.39)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 160, 512); 
  fill (60, 170, 230); 
  rect (xPosition1, 352, 32, 32);     
 
  // BLUE 6 
  //strokeWeight(0);
  //fill(253, 106, 2);
  //rect(256, 288, 384, 32); 
 
  var eased = doubleExponentialSigmoid (percent, 0.9); 
  eased = (eased + 0.115)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 256, 608); 
  fill (60, 170, 230); 
  rect (xPosition1, 288, 32, 32);    
 
  // BLUE 7 
  //strokeWeight(0);
  //fill(253, 106, 2);
  //rect(96, 224, 384, 32); 
 
  var eased = doubleExponentialSigmoid (percent, 0.8); 
  eased = (eased + 0.56)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 96, 448); 
  fill (60, 170, 230); 
  rect (xPosition1, 224, 32, 32);    
 
 
  // BLUE 8
  //strokeWeight(0);
  //fill(253, 106, 2);
  //rect(64, 160, 384, 32); 
 
  var eased = doubleExponentialSigmoid (percent, 0.6); 
  eased = (eased + 0.65)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 64, 416); 
  fill (60, 170, 230); 
  rect (xPosition1, 160, 32, 32);    
 
  // BLUE 9 
  //strokeWeight(0);
  //fill(253, 106, 2);
  // rect(192, 96, 384, 32); 
 
  var eased = doubleExponentialSigmoid (percent, 0.4); 
  eased = (eased + 0.284)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 192, 544); 
  fill (60, 170, 230); 
  rect (xPosition1, 96, 32, 32);    
 
 
  // BLUE 10
  //strokeWeight(0);
  //fill(253, 106, 2);
  //rect(192, 32, 384, 32);     
 
  var eased = doubleExponentialSigmoid (percent, 0.2); 
  eased = (eased + 0.27)%1.0; // shifted by a half-loop, for fun
  var xPosition1 = map(eased, 0, 1, 192, 544); 
  fill (60, 170, 230); 
  rect (xPosition1, 32, 32, 32);   
 
 
 
  push();
  translate(320, 320);
  var orange = true; 
  for (var squareX = 0; squareX <= width; squareX += 32){
 
   // Here's a linearly-moving square
   var eased = doubleExponentialSigmoid (percent, 0.5); 
   eased = (eased)%1.0; // shifted by a half-loop, for fun
   var yPosition = map(eased, 0, 1, topY, botY);    
 
   var rotatingSquareAngle = percent * TWO_PI
   rotate(-rotatingSquareAngle);  
 
   var squareSize = 20;
   var topY = 0 - squareSize - 2;
   var botY = height + 2;
   var sPercent = (percent)%1.0; // shifted by a half-loop
   // var yPosition = map(sPercent, 0, 1, topY, botY);
 
   if (orange == true){
     fill(253, 106, 2);
     orange = false;}
   else{
    fill(60, 170, 230);
    orange = true;}
 
   rect(squareX, yPosition, 20, 20);}   
  pop();      
 
}
 
// Symmetric double-element sigmoid function ('_a' is the slope)
// See https://github.com/IDMNYU/p5.js-func/blob/master/lib/p5.func.js
// From: https://idmnyu.github.io/p5.js-func/
//===================================================
function doubleExponentialSigmoid (_x, _a){
  if(!_a) _a = 0.75; // default
 
  var min_param_a = 0.0 + Number.EPSILON;
  var max_param_a = 1.0 - Number.EPSILON;
  _a = constrain(_a, min_param_a, max_param_a);
  _a = 1-_a;
 
  var _y = 0;
  if (_x<=0.5){
    _y = (pow(2.0*_x, 1.0/_a))/2.0;
  }
  else {
    _y = 1.0 - (pow(2.0*(1.0-_x), 1.0/_a))/2.0;
  }
  return(_y);
}

 

 

harsh – AnimatedLoop

I started off with inspiration from D'arcy Thompson's "Theory of Transformations" - where he uses grid transformations to morph between different species of animals. I found his idea of being able to use simple mathematical equations to create transformations quite fascinating. So, I began to implement some of these systems, which included shear, scale, polar co-ordinate and exponential-scaling transformations. First, I tested this theory out on something simple - like a circle, before moving on to more complex geometries. I decided to keep my objects constructed as point clouds - as this kept my process of iteration quick, and the system scalable. By a sheer stroke of luck, I tried a polar coordinate transform before transforming it into a cartesian system - this created a really cool, point dissipation effect. I then began playing with these transformations, eventually closing in on using Mattise's Dancers - I thought the notion of an emergent composition from a seemingly random point cloud was quite interesting. It also connotated notions of the Gestalt and the border between familiarity and familiarity of a representation. I also thought that it made an interesting take on composition, referencing Mattise's methods in a digital medium. This was probably one the most iterations I have gone through in a week-long project, tweaking transformations, easing functions and point clouds numerous times before arriving at this result. I used the exponentialInOut and quinticInOut easing functions - both primarily to increase the time spent at the actual image and to speed up the transformation process. The process of crafting these transformations, both with morphing and easing functions was quite a novel one for me, having never really thought about computational craft before. Overall, I'm happy with the effect, and I think the easing and transformation functions work well. If I had more time on this, I would increase the complexity of the animation to emphasize this idea of composition - trying to recreate each object like pieces of paper cut by Mattise.

For this project, I did not really keep "sketches" - instead relying on Thompson's illustrations and descriptions as a means of generation:

 
 
 
//===================================================
// User-modifiable global variables.
var myNickname = "Harsh";
var nFramesInLoop = 120;
var bEnableExport = true;
 
 
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
 
 
var circVals = [];
for(var i=0; i<pointsVals.length; i++){
  var curPointVals = pointsVals[i];
  circVals.push([curPointVals[0],curPointVals[1]*-1]);
}
 
 
var polarVals = []
for(var i=0; i<circVals.length; i++){
  var polarRadius = 30;
  var curPointVals = circVals[i];
  var cosX = polarRadius*Math.cos(curPointVals[0]);
  var sinY = polarRadius*Math.sin(curPointVals[1])
  polarVals.push([cosX,sinY]);
}
 
 
var shearVals = [];
for(var i=0; i<circVals.length; i++){
  var curPointVals = polarVals[i];
  var newX = curPointVals[0]-200;
  var newY = curPointVals[1]+200;
  shearVals.push([newX,newY]);
}
 
var shearDifferencesCartesian = []
for(var i=0; i<pointsVals.length; i++){
  var curCartValx = circVals[i][0];
  var curCartValy = circVals[i][1];
  var curShearValx = shearVals[i][0];
  var curShearValy = shearVals[i][1];
  var curTotalDifferenceX = curShearValx-curCartValx;
  var curTotalDifferenceY = curShearValy-curCartValy;
  shearDifferencesCartesian.push([curTotalDifferenceX,curTotalDifferenceY]);
}
 
var shearDifferencesPolar = []
for(var i=0; i<pointsVals.length; i++){
  var curPolarValx = polarVals[i][0];
  var curPolarValy = polarVals[i][1];
  var curShearValx = shearVals[i][0];
  var curShearValy = shearVals[i][1];
  var curTotalDifferenceX = curPolarValx-curShearValx;
  var curTotalDifferenceY = curPolarValy-curShearValy;
  shearDifferencesPolar.push([curTotalDifferenceX,curTotalDifferenceY]);
}
 
var cartesianDifferences = []
for(var i=0; i<pointsVals.length; i++){
  var curCartValx = circVals[i][0];
  var curCartValy = circVals[i][1];
  var curPolarValx = polarVals[i][0];
  var curPolarValy = polarVals[i][1];
  var curTotalDifferenceX = curPolarValx-curCartValx;
  var curTotalDifferenceY = curPolarValy-curCartValy;
  cartesianDifferences.push([curTotalDifferenceX,curTotalDifferenceY]);
}
 
var repVals = [];
for(var i=0; i<pointsVals.length; i++){
  var curPointVals = pointsVals[i];
  repVals.push([curPointVals[0],curPointVals[1]]);
}
 
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
 
var circValsHead = [];
for(var i=0; i<headVals.length; i++){
  var curPointVals = headVals[i];
  circValsHead.push([curPointVals[0],curPointVals[1]*-1]);
}
 
 
var polarValsHead = []
for(var i=0; i<circValsHead.length; i++){
  var polarRadius = 30;
  var curPointVals = circValsHead[i];
  var cosX = polarRadius*Math.cos(curPointVals[0]);
  var sinY = polarRadius*Math.sin(curPointVals[1])
  polarValsHead.push([cosX,sinY]);
}
 
 
var shearValsHead = [];
for(var i=0; i<circValsHead.length; i++){
  var curPointVals = polarValsHead[i];
  var newX = curPointVals[0]+200;
  var newY = curPointVals[1]+200;
  shearValsHead.push([newX,newY]);
}
 
var shearDifferencesCartesianHead = []
for(var i=0; i<circValsHead.length; i++){
  var curCartValx = circValsHead[i][0];
  var curCartValy = circValsHead[i][1];
  var curShearValx = shearValsHead[i][0];
  var curShearValy = shearValsHead[i][1];
  var curTotalDifferenceX = curShearValx-curCartValx;
  var curTotalDifferenceY = curShearValy-curCartValy;
  shearDifferencesCartesianHead.push([curTotalDifferenceX,curTotalDifferenceY]);
}
 
var shearDifferencesPolarHead = []
for(var i=0; i<circValsHead.length; i++){
  var curPolarValx = polarValsHead[i][0];
  var curPolarValy = polarValsHead[i][1];
  var curShearValx = shearValsHead[i][0];
  var curShearValy = shearValsHead[i][1];
  var curTotalDifferenceX = curPolarValx-curShearValx;
  var curTotalDifferenceY = curPolarValy-curShearValy;
  shearDifferencesPolarHead.push([curTotalDifferenceX,curTotalDifferenceY]);
}
 
var cartesianDifferencesHead = []
for(var i=0; i<circValsHead.length; i++){
  var curCartValx = circValsHead[i][0];
  var curCartValy = circValsHead[i][1];
  var curPolarValx = polarValsHead[i][0];
  var curPolarValy = polarValsHead[i][1];
  var curTotalDifferenceX = curPolarValx-curCartValx;
  var curTotalDifferenceY = curPolarValy-curCartValy;
  cartesianDifferencesHead.push([curTotalDifferenceX,curTotalDifferenceY]);
}
 
var repValsHead = [];
for(var i=0; i<circValsHead.length; i++){
  var curPointVals = headVals[i];
  repValsHead.push([curPointVals[0],curPointVals[1]]);
}
 
 
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
 
 
// Other global variables you don't need to touch.
var nElapsedFrames;
var bRecording;
var theCanvas;
//===================================================
function setup() {
  theCanvas = createCanvas(900, 900);
  bRecording = false;
  nElapsedFrames = 0;
}
//===================================================
function keyTyped() {
  if (bEnableExport) {
    if ((key === 'f') || (key === 'F')) {
      bRecording = true;
      nElapsedFrames = 0;
    }
  }
}
//===================================================
function draw() {
  // Compute a percentage (0...1) representing where we are in the loop.
  var percentCompleteFraction = 0;
  if (bRecording) {
    percentCompleteFraction = float(nElapsedFrames) / float(nFramesInLoop);
  } else {
    percentCompleteFraction = float(frameCount % nFramesInLoop) / float(nFramesInLoop);
  }
  // Render the design, based on that percentage.
  // This function renderMyDesign() is the one for you to change.
  renderMyDesign (percentCompleteFraction);
  // If we're recording the output, save the frame to a file.
  // Note that the output images may be 2x large if you have a Retina mac.
  // You can compile these frames into an animated GIF using a tool like:
  if (bRecording && bEnableExport) {
    var frameOutputFilename = myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png";
    print("Saving output image: " + frameOutputFilename);
    saveCanvas(theCanvas, frameOutputFilename, 'png');
    nElapsedFrames++;
 
    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
    }
  }
}
//===================================================
function renderMyDesign (percent) {
 
  background(13, 60, 85);
  smooth();
  stroke(0, 0, 0);
  strokeWeight(2);
 
  //----------------------
  var e = new p5.Ease(); // easing function object
 
  // percentSig = e.quadraticInOut(percent);
  // console.log(percentSig);
 
  percentSig = percent;
  if(0<=percentSig && percentSig<0.1){
 
    for(var i=0; i<circVals.length; i++){
      var curVal = polarVals[i];
      var curX = curVal[0];
      var curY = curVal[1];
      var curDifferenceX = shearDifferencesPolar[i][0];
      var curDifferenceY = shearDifferencesPolar[i][1];
      var newPerc = map(percent,0,0.1,0,1);
      var e = new p5.Ease(); // easing function object
      newPerc = e.exponentialInOut(newPerc);
      var newX = -1*((curDifferenceX*newPerc)-curX)
      var newY = -1*((curDifferenceY*newPerc)-curY)
      repVals[i] = [newX,newY];
    }
 
 
    for(var i=0; i<circValsHead.length; i++){
      var curVal = polarValsHead[i];
      var curX = curVal[0];
      var curY = curVal[1];
      var curDifferenceX = shearDifferencesPolarHead[i][0];
      var curDifferenceY = shearDifferencesPolarHead[i][1];
      var newPerc = map(percent,0,0.1,0,1);
      var e = new p5.Ease(); // easing function object
      newPerc = e.exponentialInOut(newPerc);
      var newX = -1*((curDifferenceX*newPerc)-curX)
      var newY = -1*((curDifferenceY*newPerc)-curY)
      repValsHead[i] = [newX,newY];
    }
 
  }
 
  else if(.1<=percentSig&& percentSig<0.66){
 
    for(var i=0; i<circVals.length; i++){
      var curVal = shearVals[i];
      var curX = curVal[0];
      var curY = curVal[1];
      var curDifferenceX = shearDifferencesCartesian[i][0];
      var curDifferenceY = shearDifferencesCartesian[i][1];
      var newPerc = map(percent,0.1,0.66,0,1)
      var e = new p5.Ease(); // easing function object
      newPerc = e.quinticInOut(newPerc);
      var newX = -1*((curDifferenceX*newPerc) - curX)
      var newY =  -1*((curDifferenceY*newPerc) - curY)
      repVals[i] = [newX,newY];
    }
 
 
    for(var i=0; i<circValsHead.length; i++){
      var curVal = shearValsHead[i];
      var curX = curVal[0];
      var curY = curVal[1];
      var curDifferenceX = shearDifferencesCartesianHead[i][0];
      var curDifferenceY = shearDifferencesCartesianHead[i][1];
      var newPerc = map(percent,0.1,0.66,0,1)
      var e = new p5.Ease(); // easing function object
      newPerc = e.quinticInOut(newPerc);
      var newX = -1*((curDifferenceX*newPerc) - curX)
      var newY =  -1*((curDifferenceY*newPerc) - curY)
      repValsHead[i] = [newX,newY];
    }
  }
 
  else if(.66<=percentSig&& percentSig<1){
 
    for(var i=0; i<circVals.length; i++){
      var curVal = circVals[i];
      var curX = curVal[0];
      var curY = curVal[1];
      var curDifferenceX = cartesianDifferences[i][0];
      var curDifferenceY = cartesianDifferences[i][1];
      var newPerc = map(percent,0.66,1,0,1)
      var e = new p5.Ease(); // easing function object
      newPerc = e.quadraticInOut(newPerc);
      var newX = (curDifferenceX*newPerc) + curX
      var newY =  (curDifferenceY*newPerc) + curY
      repVals[i] = [newX,newY];
    }
 
    for(var i=0; i<circValsHead.length; i++){
      var curVal = circValsHead[i];
      var curX = curVal[0];
      var curY = curVal[1];
      var curDifferenceX = cartesianDifferencesHead[i][0];
      var curDifferenceY = cartesianDifferencesHead[i][1];
      var newPerc = map(percent,0.66,1,0,1)
      var e = new p5.Ease(); // easing function object
      newPerc = e.quadraticInOut(newPerc);
      var newX = (curDifferenceX*newPerc) + curX
      var newY =  (curDifferenceY*newPerc) + curY
      repValsHead[i] = [newX,newY];
    }
  }
 
 
  for(var i=0; i<repVals.length; i++){
    //console.log((oldVals[i][0]+50),(oldVals[i][1]+50))
    push();
    fill(19, 149, 186);
    stroke(19, 149, 186);
    ellipse((repVals[i][0]+450),(repVals[i][1]+450),2,2);
    pop();
  }
 
 
  for(var i=0; i<repValsHead.length; i++){
    //console.log((oldVals[i][0]+50),(oldVals[i][1]+50))
    push();
    fill(241, 108, 32);
    stroke(241, 108, 32);
    ellipse((repValsHead[i][0]+450),(repValsHead[i][1]+450),5,5);
    pop();
  }
 
  //----------------------
  // Include some visual feedback.
  // fill(255, 0, 0);
  // noStroke();
  // textAlign(CENTER);
  // var percentDisplayString = "" + nf(percent, 1, 3);
  // text(percentDisplayString, 100, 200 - 15);
}

chromsan-Scope

PDF: praxinoscope-output

This design is essentially four triangles whose top point moves towards the center. I made it as a practice for my main GIF, which uses this same effect. I experimented with coloring the triangles, but in the end settled for a black and white combination, which sort of gives the illusion of a 3D space. The triangles move with an easing function so they close faster than they open, which gives a more interesting movement than just a constant open and close.

The relevant code, inserted into the praxinoscope template:

void drawArtFrame (int whichFrame) { 
 
  int height = 40;
  int width = 40;
  translate(-20, -20);
 
  if (whichFrame &lt;= nFrames/2){
    // close quickly 
    float eased = function_DoubleExponentialSigmoid(map(whichFrame, 0, nFrames/2, 0, 1), 0.7);
    fill(50);
    triangle(0, height, width, height, width/2, map(eased, 0, 1, height, height/2 ));
    fill(100);
    triangle(0, 0, 0, height, width/2 * map(eased, 0, 1, 0, 1), height/2);
    fill(150);
    triangle(0, 0, width, 0, width/2, height/2 * map(eased, 0, 1, 0, 1));
    fill(200);
    triangle(width, 0, width, height, map(eased, 0, 1, width, width/2), height/2); 
 
  } else {
    // open slowly
    float eased = function_DoubleExponentialSigmoid(map(whichFrame, nFrames/2, nFrames, 0, 1), 0.05);
    fill(50);
    triangle(0, height, width, height, width/2, map(eased, 0, 1, height/2, height ));
    fill(100);
    triangle(0, 0, 0, height, width/2 * map(eased, 0, 1, 1, 0), height/2);
    fill(150);
    triangle(0, 0, width, 0, width/2, height/2 * map(eased, 0, 1, 1, 0));
    fill(200);
    triangle(width, 0, width, height, map(eased, 0, 1, width/2, width), height/2); 
  }
}
 
// Taken from https://github.com/golanlevin/Pattern_Master
float function_DoubleExponentialSigmoid (float x, float a) {
  // functionName = "Double-Exponential Sigmoid";
 
  float min_param_a = 0.0 + EPSILON;
  float max_param_a = 1.0 - EPSILON;
  a = constrain(a, min_param_a, max_param_a); 
  a = 1-a;
 
  float y = 0;
  if (x&lt;=0.5) {
    y = (pow(2.0*x, 1.0/a))/2.0;
  } else {
    y = 1.0 - (pow(2.0*(1.0-x), 1.0/a))/2.0;
  }
  return y;
}