Pattern...

edited March 2018 in How To...

Dear all interested readers,

What would be a good method to do this? I guess the best way to start is to make a vertex and somehow turn it into an object. What should I do to change the values of the vertex, to get to this animated field?

Could you give me a start? I don't have much experience with these shapes.

Thanks in advance

Answers

  • Link please?

    I can't see the Animation here

  • It is all the same repeating pattern that moves with offset in time. I would use shape to create a square with four verticies and just move them like on the gif.

  • Thank you. I agree. But how would I be able to create an offset in time?

  • There are basically two tricks to making this work:

    • You can draw any quadrilateral in Processing by calling beginShape(), calling vertex(x, y,) four times, and then calling endShape(CLOSE)

    • You animate one of four vertices so that a square becomes a triangle or a triangle becomes a square while doing some "trickery" with the vertices once the "square step" completes

    In the codes below that "trickery" is the numbering of a square during each "square step". Originally, in clock-wise order, the square is numbered 0, 1, 2, 3 starting from the top-left position. The next time a square is formed the numbering is 1, 2, 3, 0 (again from the top-left in clockwise order). After that it is 2, 3, 0, 1. After that it is 3, 0, 1, 2. Finally it returns to 0, 1, 2, 3:

    PVector[] vertices;
    int state;
    
    void setup() {
      size(400, 400);
      fill(0);
    
      // Originally a square
      vertices = new PVector[4];
      vertices[0] = new PVector(100.0, 100.0);
      vertices[1] = new PVector(300.0, 100.0);
      vertices[2] = new PVector(300.0, 300.0);
      vertices[3] = new PVector(100.0, 300.0);
    
      // Initialize to first of eight possible states
      state = 0;
    }
    
    void draw() {
      background(255);
    
      tween();
    
      beginShape();
      for (int i = 0; i < 4; i++)
        vertex(vertices[i].x, vertices[i].y);
      endShape(CLOSE);
    }
    
    void tween() {
      switch (state) {
      case 0:
        println("State: " + 0);
        vertices[0].x++;
        if (vertices[0].x >= 300.0) {
          vertices[0].x = 300.0;
          vertices[1].y = 300.0;
          vertices[2].x = 100.0;
          state = 1;
        }
        break;
      case 1:
        println("State: " + 1);
        vertices[3].y--;
        if (vertices[3].y <= 100.0) {
          vertices[3].y = 100.0;
          state = 2;
        }
        break;
      case 2:
        println("State: " + 2);
        vertices[0].y++;
        if (vertices[0].y >= 300.0) {
          vertices[0].y = 300.0;
          vertices[1].x = 100.0;
          vertices[2].y = 100.0;
          state = 3;
        }
        break;
      case 3:
        println("State: " + 3);
        vertices[3].x++;
        if (vertices[3].x >= 300.0) {
          vertices[3].x = 300.0;
          state = 4;
        }
        break;
      case 4:
        println("State: " + 4);
        vertices[0].x--;
        if (vertices[0].x <= 100.0) {
          vertices[0].x = 100.0;
          vertices[1].y = 100.0;
          vertices[2].x = 300.0;
          state = 5;
        }
        break;
      case 5:
        println("State: " + 5);
        vertices[3].y++;
        if (vertices[3].y >= 300.0) {
          vertices[3].y = 300.0;
          state = 6;
        }
        break;
      case 6:
        println("State: " + 6);
        vertices[0].y--;
        if (vertices[0].y <= 100.0) {
          vertices[0].y = 100.0;
          vertices[1].x = 300.0;
          vertices[2].y = 300.0;
          state = 7;
        }
        break;
      case 7:
        println("State: " + 7);
        vertices[3].x--;
        if (vertices[3].x <= 100.0) {
          vertices[3].x = 100.0;
          state = 0;
        }
        break;
      }
    }
    

    If you make sense of why this works then making several instances of this at different points in time should be no problem

  • Thank you very much.

    I don't yet fully understand, how to use this in a for loop, but I might figure it out. Thank you for the explanation.

  • Answer ✓

    Note, I am just making this up, I am sure there are other ways to do it

    Say you wanted a 10 x 10 grid of these things which is 100 things. You would have to count from 0 to 99 to place them (lets say you are using one for loop to simplify the explanation). For each iteration of i:

    • Divide i by 12.5 (because there are 8 possible states and 100.0 / 12.5 is 8.0)

    • Bin each value you get into being state 0, state 1, state 2, etc. by rounding down

    • Place the initial positions of each vertex based on the state (the 0, 1, 2, 3 from top-left, or 1, 2, 3, 0 from top-left, or 2, 3, 0, 1 from top-left, etc.). Note that you have both four kinds of square numbering and four kinds of triangle numbering

    • After having setup the initial positions of each vertex you are going to want to move one of them based on the state as well to get "progress" in that state

    Lets say i had an original value of 42. 42 / 12.5 = 3.36 an so it is state 3 which is a triangle. In state 3 you have to figure out how much to move vertices[3] to the right. If you round 3.36 down you get 3.0 and 3.36 - 3.0 is 0.36 so vertices[3] needs to be placed 36% of the way to the right as its initial position

  • Thank you for your response. I still don't seem to understand how to write the functions in a loop, with the explanation above.

  • The code I posted only had one of these things instead of a grid of them so I hard-coded a lot of numbers because that was easy to do. The first step towards making a grid I think is turning these things into instances of a class, I'll call it BoxTriangle

    All of the 100.0 I hard-coded have become this.left or this.top and the 300.0 have become this.left + blockWidth or this.top + blockWidth. That way tween() works regardless of where a BoxTriangle instance is placed

    The code below is still incomplete because the floating point state I described above has not been calculated in the for loop in setup(). Additionally, when that is calculated the constructor for BoxTriangle will have to set the initial values of the vertices based on the floating point state. In the above example 3.36 was some floating point state value (based on i) and so this.state (for that instance) would be 3 and the vertices would have to be set appropriately for state 3 + 36% of the way to state 4

    final float blockWidth = 50.0;
    BoxTriangle[] boxTriangles;
    
    void setup() {
      size(800, 800);
      fill(0);
    
      boxTriangles = new BoxTriangle[100];
    
      // Calculating the floating point state like I described in my
      // explanation above is a little more straight-forward with a single
      // loop that just has a variable i
      for (int i = 0; i < 100; i++) {
        // i gives enough information to determine the top-left position of
        // each BoxTriangle instance (in combination with blockWidth)
        final float left = i % 10 * (blockWidth * 2.0) + (blockWidth * 0.5);
        final float top  = i / 10 * (blockWidth * 2.0) + (blockWidth * 0.5);
    
        // Notice that each instance is being passed 0 for its state. This
        // is no good, the idea is to calculate a floating point state. In
        // my explanation above an example floating point state value was
        // 3.36 which is state 3 already 36% of the way to being state 4
        boxTriangles[i] = new BoxTriangle(0, left, top);
      }
    }
    
    void draw() {
      background(255);
    
      // Every BoxTriangle instance does the same thing assuming their
      // initial state and vertex positions are correctly calculated
      // during setup()
      for (BoxTriangle boxTriangle : boxTriangles) {
        boxTriangle.tween();
        boxTriangle.display();
      }
    }
    
    class BoxTriangle {
      int state;
      float left, top;
      PVector[] vertices;
    
      BoxTriangle(final float state, final float left, final float top) {
        // This rounds the floating point state down
        this.state = (int)state;
    
        this.left = left;
        this.top = top;
    
        // This is still wrong, the original positions of the vertices has
        // to be changed based on the floating point state. Right now they
        // are hard-coded for state 0 with no additional offset
        this.vertices = new PVector[4];
        this.vertices[0] = new PVector(left, top);
        this.vertices[1] = new PVector(left + blockWidth, top);
        this.vertices[2] = new PVector(left + blockWidth, top + blockWidth);
        this.vertices[3] = new PVector(left, top + blockWidth);
      }
    
      void display() {
        beginShape();
        for (int i = 0; i < 4; i++)
          vertex(this.vertices[i].x, this.vertices[i].y);
        endShape(CLOSE);
      }
    
      void tween() {
        switch (this.state) {
        case 0:
          this.vertices[0].x++;
          if (this.vertices[0].x >= this.left + blockWidth) {
            this.vertices[0].x = this.left + blockWidth;
            this.vertices[1].y = this.top + blockWidth;
            this.vertices[2].x = this.left;
            this.state = 1;
          }
          break;
        case 1:
          this.vertices[3].y--;
          if (this.vertices[3].y <= this.top) {
            this.vertices[3].y = this.top;
            this.state = 2;
          }
          break;
        case 2:
          this.vertices[0].y++;
          if (this.vertices[0].y >= this.top + blockWidth) {
            this.vertices[0].y = this.top + blockWidth;
            this.vertices[1].x = this.left;
            this.vertices[2].y = this.top;
            this.state = 3;
          }
          break;
        case 3:
          this.vertices[3].x++;
          if (this.vertices[3].x >= this.left + blockWidth) {
            this.vertices[3].x = this.left + blockWidth;
            this.state = 4;
          }
          break;
        case 4:
          this.vertices[0].x--;
          if (this.vertices[0].x <= this.left) {
            this.vertices[0].x = this.left;
            this.vertices[1].y = this.top;
            this.vertices[2].x = this.left + blockWidth;
            this.state = 5;
          }
          break;
        case 5:
          this.vertices[3].y++;
          if (this.vertices[3].y >= this.top + blockWidth) {
            this.vertices[3].y = this.top + blockWidth;
            this.state = 6;
          }
          break;
        case 6:
          this.vertices[0].y--;
          if (this.vertices[0].y <= this.top) {
            this.vertices[0].y = this.top;
            this.vertices[1].x = this.left + blockWidth;
            this.vertices[2].y = this.top + blockWidth;
            this.state = 7;
          }
          break;
        case 7:
          this.vertices[3].x--;
          if (this.vertices[3].x <= this.left) {
            this.vertices[3].x = this.left;
            this.state = 0;
          }
          break;
        }
      }
    }
    
  • Thank you so much, but it wasn't clear for me how to solve it. How can I put in the animation? Hope you can advise me. Thank you very much

  • edited March 2018

    @joshuakoomen

    Not Found: The requested URL /animation.gif was not found on this server.

    Is this supposed to be the animation that you are asking how to create? You linked to it earlier, but it is gone now.

  • So this isn't an animated gif -- instead, each square is a moment in the animation?

  • edited March 2018

    One approach:

    1. create an array of vector locations for each point in the quad.
    2. interpolate the vectors to animate each list of points. Now you have four independent points tracing paths over the course of 64 frames.
    3. each frame use those four points to draw a quad PShape. (When two points overlap they will naturally form a triangle.)

    For more on how to interpolate a list of vectors, see LerpVectorsExample:

    One thing to notice is that you could define your animation vector data on a unit square -- (0,0), (0,1), (1,1), (1,0). Then you can easily scale the output however you like by multiplying it by the target width and height.

  • So this isn't an animated gif -- instead, each square is a moment in the animation?

    the original was. an 8 x 8 grid where each square did the same thing but they were out of phase of each other. the posted image shows a range of those phases, it isn't a set of all frames.

  • edited March 2018

    ...got it. And the grid is displayed in a back-and-forth, boustrophedon style?

    edit

    ah, no. It looks like the offset animations are running across the rows, maybe...? Based on the bottom corner I suspect that the animation states that aren't revealed in the still image would move the top two points (symmetrically to how the bottom two are shown moving).

  • A B
    C D
    

    Line starts between A and B and the right hand edge moves down until it's A to D. Then the other end moves until it's B to D. Then B to C, C to D, D to A, C to A, C to B, back to A to B

  • Except the posted example is rotating anti clockwise...

  • edited March 2018 Answer ✓

    Here is a quick demo of an animated quad outline that is shifting counter-clockwise. It is based on a single animation path which is followed (at different time offsets) by four points -- two sliding points and two anchor points (which shift instantly).

    Screen Shot 2018-03-12 at 11.00.37 AM

    /**
     * LerpVectorPathShape
     * Jeremy Douglass 2018-03-12 Processing 3.3.6
     **/
    
    PVector vecs[];
    PVector slider0;
    PVector slider1;
    PVector anchor0;
    PVector anchor1;
    float SCALING = 180;
    
    void setup() {
      size(200, 200);
      rectMode(CENTER);
      vecs = new PVector[9];
      vecs[0] = new PVector(0,0);
      vecs[1] = new PVector(0,0);
      vecs[2] = new PVector(0,1);
      vecs[3] = new PVector(0,1);
      vecs[4] = new PVector(1,1);
      vecs[5] = new PVector(1,1);
      vecs[6] = new PVector(1,0);
      vecs[7] = new PVector(1,0);
      vecs[8] = new PVector(0,0);
      for(PVector v : vecs){
        v.mult(SCALING);
      }
    } 
    
    void draw() {
      background(255);
      translate(10,10);
      // draw the path
      stroke(192);
      drawPath(vecs);
      stroke(0);
      // define current anchor points and sliding points
      anchor0 = vecs[floor((frameCount+700)%800/800.0 * (vecs.length-1))];
      anchor1 = vecs[floor((frameCount+500)%800/800.0 * (vecs.length-1))];
      slider0 = lerpVectors(frameCount%800/800.0, vecs);
      slider1 = lerpVectors((frameCount+300)%800/800.0, vecs);
    
      // draw points on the path
      fill(255, 255, 0);
      rect(anchor0.x, anchor0.y, 14, 14);
      fill(0, 255, 0);
      rect(anchor1.x, anchor1.y, 14, 14);
      fill(255, 0, 0);
      ellipse(slider0.x, slider0.y, 10, 10);
      fill(0, 0, 255);
      ellipse(slider1.x, slider1.y, 10, 10);
    
      // fill shape
      pushStyle();
      noStroke();
      fill(128,64);
      quad(slider0.x, slider0.y, slider1.x, slider1.y,
           anchor1.x, anchor1.y, anchor0.x, anchor0.y);
      popStyle();
    
      // outline shape
      line(slider0.x, slider0.y, slider1.x, slider1.y);
      line(slider1.x, slider1.y, anchor1.x, anchor1.y);
      line(anchor1.x, anchor1.y, anchor0.x, anchor0.y);
      line(anchor0.x, anchor0.y, slider0.x, slider0.y);
    }
    
    void drawPath(PVector... vecs) {
      for (int i=1; i<vecs.length; i++) {
        line(vecs[i].x, vecs[i].y, vecs[i-1].x, vecs[i-1].y);
      }
    }
    
    PVector lerpVectors(float amt, PVector... vecs) {
      if (vecs.length==1) { 
        return vecs[0];
      }
      float cunit = 1.0/(vecs.length-1);
      return PVector.lerp(vecs[floor(amt / cunit)], vecs[ceil(amt / cunit)], amt%cunit/cunit);
    }
    

    The unit vectors of the path are all defined in setup, and they are scaled (with mult()) from 1.0 up to a screen size defined in the global variable SCALING.

    The series of vectors defines a looped animation path, which is why there are repeat entries (to create pauses in the animation) and why the last entry wraps to the first (to close the path).

    One way to approach a problem like this is to describe the problem:

    1. the shape is an animated quadrilateral
    2. it is defined by four points moving on a box path.
    3. two of the points slide on the path, two jump on the path

    ...and then break it down into parts:

    1. define a timed box path using vertices
    2. animate two sliding points on the box path
    3. animate two jumping points on the box path
    4. connect the four points.

    This code could be refactored to make it more compact -- I left it loose and explicit (if a bit repetitive) to make it easier to play around individual timing values logics and colors, add / drop / reorder points, or add / substitute quad() or beginShape as an approach etc.

Sign In or Register to comment.