sketch_210203b

ShapeFactory.pde

PVector computeOffset(PVector[] points) {
  float ymax = 0.0;
  float ymin = 0.0;
  for (PVector point : points) {
    ymax = max(point.y, ymax);
    ymin = min(point.y, ymin);
  }
  return new PVector(0.0, ((1.0 - abs(ymax) + abs(ymin) - 1.0)) / 2.0);
}

PVector computeOffset(PShape s) {
  PShape ss = createShape();
  PVector[] points = new PVector[s.getVertexCount()];
  ss.beginShape();
  for (int k = 0; k < s.getVertexCount(); k++) {
    points[k] = s.getVertex(k);
  }
  ss.endShape(CLOSE);
  return computeOffset(points);
}

PShape normalizeShape(PShape s) {
  float w = s.width;
  float h = s.height;
  PShape ss = createShape();
  ss.beginShape();
  for (int k = 0; k < s.getVertexCount(); k++) {
    PVector v = s.getVertex(k);
    ss.vertex(v.x / w, v.y / h);
  }
  ss.endShape(CLOSE);
  return ss;
}

PShape applyOffset(PShape s, PVector offset) {
  PShape ss = createShape();
  ss.beginShape();
  for (int k = 0; k < s.getVertexCount(); k++) {
    PVector v = s.getVertex(k);
    ss.vertex(v.x + offset.x, v.y + offset.y);
  }
  ss.endShape(CLOSE);
  return ss;
}

abstract class AbstractShapeFactory {
  abstract PShape create();
}

class HeartShapeFactory extends AbstractShapeFactory {
  PShape create() {
    PShape s = createShape();
    s.beginShape();
    for (int k = 0; k < N_POINTS; k++) {
        float t = TWO_PI * k / (float)N_POINTS;
        float x = 16 * pow(sin(t), 3);
        float y = -(13 * cos(t) - 5*cos(2*t) - 2*cos(3*t) - cos(4*t));
        s.vertex(x, y);
    }
    s.endShape(CLOSE);
    
    PShape s_normalized = normalizeShape(s);
    PVector offset = computeOffset(s_normalized);
    
    return applyOffset(s_normalized, offset);
  }
}

class CircleShapeFactory extends AbstractShapeFactory {
  PShape create() {
    PShape s = createShape();
    s.beginShape();
    for (int k = 0; k < N_POINTS; k++) {
      float t = TWO_PI * k / (float)N_POINTS - HALF_PI;
      float x = cos(t);
      float y = sin(t);
      s.vertex(x, y);
    }
    s.endShape(CLOSE);
    return s;
  }
}

class RegularPolygonShapeFactory extends AbstractShapeFactory {
  
  int N;
  
  RegularPolygonShapeFactory(int N) {
    this.N = N;
  }
  
  PShape create() {
    
    PVector[] points = new PVector[this.N];
    for (int k = 0; k < this.N; k++) {
      float t = TWO_PI * k / (float)this.N - HALF_PI;
      float x = cos(t);
      float y = sin(t);
      points[k] = new PVector(x, y);
    }
    
    PVector offset = computeOffset(points);
    
    PShape s = createShape();
    s.beginShape();
    for (int k = 0; k < N_POINTS; k++) {
      float t = k / (float)N_POINTS;
      int n = (int)(t * this.N);
      PVector p = PVector.lerp(
        points[n % this.N],
        points[(n + 1) % this.N],
        (t % (1.0 / this.N)) * this.N
      );
      s.vertex(p.x + offset.x, p.y + offset.y);
    }
    s.endShape(CLOSE);
    return s;
  }
}

class StarShapeFactory extends AbstractShapeFactory {
  
  int N;
  int TWO_N;
  
  StarShapeFactory(int N) {
    this.N = N;
    this.TWO_N = N * 2;
  }
  
  PShape create() {
    
    PVector[] points = new PVector[this.TWO_N];
    for (int k = 0; k < this.TWO_N; k++) {
      float t = TWO_PI * k / (float)this.TWO_N - HALF_PI;
      float r = k % 2 == 0 ? 1.0 : 0.5;
      float x = r * cos(t);
      float y = r * sin(t);
      points[k] = new PVector(x, y);
    }
    
    PVector offset = computeOffset(points);
    
    PShape s = createShape();
    s.beginShape();
    int n_on_edge = N_POINTS / this.TWO_N;
    for (int n = 0; n < this.TWO_N; n++) {
      for (int k = 0; k < n_on_edge; k++) {
        float t = k / (float)n_on_edge;
        PVector p = PVector.lerp(
          points[n % this.TWO_N],
          points[(n + 1) % this.TWO_N],
          t
        );
        s.vertex(p.x + offset.x, p.y + offset.y);
      }
    }
    s.endShape(CLOSE);
    
    return s;
  }
}

index.html

<html>
  <head>
    <meta charset="UTF-8">
    <script src="https://cdn.jsdelivr.net/npm/p5@1.1.9/lib/p5.js"></script>
    <script src="/G4P5js/public_html/G4P5js.js"></script>
    <script src="sketch_210203b.js"></script>
  </head>
  <body style="background-color:#99CCFF;">
  <div id='settings'></div>
  </body>
</html>

sketch_210203b.pde


final int N = 8;
final int N_POINTS = 200;

PShape[] shapes;
float interval = 0.5;

void setup() {
  size(800, 800);
  
  shapes = new PShape[4];
  shapes[0] = (new CircleShapeFactory()).create();
  shapes[1] = (new HeartShapeFactory()).create();
  shapes[2] = (new RegularPolygonShapeFactory(5)).create();
  shapes[3] = (new StarShapeFactory(5)).create();
}

void draw() {
  background(0);
  translate(width / 2.0, height / 2.0);
  
  float time = millis() / 1000.0;
  
  PShape s1, s2;
  
  s1 = shapes[0];
  s2 = shapes[1];
  
  for (int k = 1; k < shapes.length; k++) {
    float t = time % (interval * shapes.length) + 1e-6;
    if (interval * k < t && t < interval * (k + 1)) {
      s1 = shapes[k];
      s2 = shapes[(k + 1) % shapes.length];
      break;
    }
  }
  
  scale(width / shapes[0].width * 0.5, width / shapes[0].height * 0.5);
  
  float t = (time % interval) / interval;
  PShape s = getSmoothedShape(s1, s2, linearstep(0.1, 0.5, t));
  shape(s);
}

float linearstep(float edge0, float edge1, float t) {
  return min(max((t - edge0) / (edge1 - edge0), 0.0), 1.0);
}

PShape getSmoothedShape(PShape s1, PShape s2, float t) {
  PShape s = createShape();
  s.beginShape();
  s.noStroke();
  for (int k = 0; k < s1.getVertexCount(); k++) {
    PVector v1 = s1.getVertex(k);
    PVector v2 = s2.getVertex(k);
    float x = lerp(v1.x, v2.x, t);
    float y = lerp(v1.y, v2.y, t);
    
    s.vertex(x, y);
  }
  s.endShape(CLOSE);
  
  return s;
}