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;
}