Public
Edited
Mar 19, 2024
Insert cell
Insert cell
p5(s=>{
const PHI = (1 + Math.sqrt(5)) / 2;
const PHISQ = PHI*PHI;
const OBTSUE_H = Math.sqrt(1 - PHISQ/4);
const ACUTE_H = Math.sqrt(1 - 1 / (4 * PHISQ));
class Triangle {
// triangles in the subdivision are similar isosceles triangles, so here we use the location of apex and size to difine them
constructor(x, y, rotation, size, type, direction, level){
this.x = x;
this.y = y;
this.rotation = rotation;
this.size = size;
this.d = direction;
this.type = type; // 0: obtuse 1: acute
this.level = level;
this.divided = false;
}

divide(){
this.divided = true;
let res = [];
if (this.type === 1) {
//acute, divide to three:
let newSz = this.size / PHISQ;
let r = - 3 * Math.PI/5;
let newRotation = this.d === 1 ? this.rotation + r : this.rotation - r;
let ang = this.d === 1 ? this.rotation + Math.PI/10 : this.rotation - Math.PI/10;
let nx = this.x + newSz * Math.cos(ang);
let ny = this.y + newSz * Math.sin(ang);
res.push(new Triangle(nx, ny, newRotation, newSz, 0, this.d, this.level + 1));

newSz = this.size / PHI;
r = 3 * Math.PI / 5;
newRotation = this.d === 1 ? this.rotation - r : this.rotation + r;
ang = this.d === 1 ? this.rotation + Math.PI/10 : this.rotation - Math.PI/10;
nx = this.x + this.size * Math.cos(ang);
ny = this.y + this.size * Math.sin(ang);
res.push(new Triangle(nx, ny, newRotation, newSz, 1, this.d, this.level + 1));

r = 4 * Math.PI / 5;
newRotation = this.d === 1 ? this.rotation - r : this.rotation + r;
ang = this.d === 1 ? this.rotation + Math.PI/10 : this.rotation - Math.PI / 10;
nx = this.x + this.size * Math.cos(ang);
ny = this.y + this.size * Math.sin(ang);
res.push(new Triangle(nx, ny, newRotation, newSz, 1, (this.d + 1) % 2, this.level + 1));
} else {
//obtuse, divide to two
let newSz = this.size;
let r = 3 * Math.PI / 5;
let newRotation = this.d === 1 ? this.rotation + r : this.rotation - r;
let ang = this.d === 1 ? this.rotation - 3 * Math.PI / 10 : this.rotation + 3 * Math.PI / 10;
let nx = this.x + this.size * Math.cos(ang);
let ny = this.y + this.size * Math.sin(ang);
res.push(new Triangle(nx, ny, newRotation, newSz, 1, (this.d + 1) % 2, this.level + 1));

newSz = this.size / PHI;
r = 4 * Math.PI / 5;
newRotation = this.d === 1 ? this.rotation + r : this.rotation - r;
ang = this.d === 1 ? this.rotation + Math.PI / 10 : this.rotation - Math.PI / 10;
nx = this.x + newSz * Math.cos(ang);
ny = this.y + newSz * Math.sin(ang);
res.push(new Triangle(nx, ny, newRotation, newSz, 0, this.d, this.level + 1));
}
return res;
}

render(){
let ds = this.size;
s.push();
s.translate(this.x, this.y);
s.scale(ds);
s.rotate(this.rotation);
s.strokeWeight(1/ds); // make sure strokeweight looks the same in different scale;
if (this.type === 0){
s.triangle(OBTSUE_H, - PHI/2, 0, 0, OBTSUE_H, PHI/2);
} else {
s.triangle(ACUTE_H, - 1 / (PHI * 2), 0, 0, ACUTE_H, 1 / (PHI * 2));
}
s.pop();
}
}
s.setup = function(){
s.createCanvas(400,400);
s.noFill();
s.stroke(0);
let tris = [];

let size = s.width/2;
for (let i = 0; i < 5; i ++) {
let ang = i * 2 * Math.PI / 5;
tris.push(new Triangle(s.width/2, s.height/2, ang, size, 1, 0, 0));
tris.push(new Triangle(s.width/2, s.height/2, ang + Math.PI/5, size, 1, 1, 0));
}

for (let i = 0; i < tris.length; ++i) {
let t = tris[i]
if (t.level < 5 && Math.random() * (t.level + 1) > 0.2) tris.push(... t.divide());
}

tris = tris.filter(t => !t.divided);

tris.forEach(t => t.render());
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class Polygon{
//vertices should be in order (cw or ccw);
constructor(vertices, level){
this.level = level;
this.vertices = vertices.slice();
this.n = vertices.length / 2;
this.divided = false;
this.center = getCenterAvg(vertices);
//this.center = getCentroid(this.vertices); // try using the mathmethic centroid instead
}

divide(a = 0.2,b = 0.8){
let center = this.center, // center of the polygon
controlPoints1 = [], // points on the polygon edge
controlPoints2 = []; // points inside the polygon
for (let i = 0; i < this.vertices.length; i += 2) {
let j = i > 0 ? i - 2 : this.vertices.length - 2;
let rnd1 = random(a, b), rnd2 = random(a, b);
controlPoints1.push(this.vertices[j] + (this.vertices[i] - this.vertices[j]) * rnd1, this.vertices[j + 1] + (this.vertices[i + 1] - this.vertices[j + 1]) * rnd1);
controlPoints2.push(controlPoints1[i] + (center[0] - controlPoints1[i]) * rnd2, controlPoints1[i + 1] + (center[1] - controlPoints1[i + 1]) * rnd2);
}
let res = [];
res.push(new Polygon(controlPoints2, this.level + 1));
for (let i = 0; i < this.n; ++i) {
let j = (i + 1) % this.n;
let vs = [this.vertices[i*2], this.vertices[i*2+1], controlPoints1[j*2], controlPoints1[j*2+1], controlPoints2[j*2], controlPoints2[j*2+1],controlPoints2[i*2],controlPoints2[i*2+1],controlPoints1[i*2],controlPoints1[i*2+1]];
res.push(new Polygon(vs, this.level + 1));
}
this.divided = true;
return res;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
p5(s =>{
s.setup = function(){
s.createCanvas(800,800);
s.noFill();
s.stroke(0);
let polys = [];

polys.push(new Polygon([10,10,790,10,790,790,10,790], 0));

for (let i = 0; i < polys.length; ++i) {
let p = polys[i];
let noiseValue = s.noise(p.center[0] * 0.005, p.center[1] * 0.005);
noiseValue *= noiseValue > .5 ? 1.1 : 0.9;
if (p.level < 5 && noiseValue > 0.15 * p.level) polys.push(...p.divide());
}

polys = polys.filter(p => !p.divided);

polys.forEach(p => {
s.beginShape();
s.strokeWeight(0.2);
for (let i = 0; i < p.vertices.length; i += 2) {
s.vertex(p.vertices[i], p.vertices[i+1]);
}
s.endShape(s.CLOSE);
})
}
})
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more