Skip to content

Instantly share code, notes, and snippets.

@JustinSDK
Last active September 7, 2022 07:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JustinSDK/6a7399d0401ac0c77b0db67ae9a07b91 to your computer and use it in GitHub Desktop.
Save JustinSDK/6a7399d0401ac0c77b0db67ae9a07b91 to your computer and use it in GitHub Desktop.
Differential line growth with p5.js
// Differential line growth with p5.js
// refactored from http://www.codeplastic.com/2017/07/22/differential-line-growth-with-processing/
const maxNodeNumbers = 400;
const nodesStart = 15;
const rayStart = 15;
let diffLine;
function setup() {
createCanvas(300, 300);
stroke(0);
fill(0, 255, 255);
diffLine = new DifferentialLine(nodesStart, rayStart, createVector(width / 2, height / 2));
}
function draw() {
background(200);
diffLine.update();
render(diffLine.nodes);
if(diffLine.nodes.length >= maxNodeNumbers) {
noLoop();
}
}
function render(nodes) {
beginShape();
for(let node of nodes) {
const {x, y} = node.coordinate;
vertex(x, y);
}
endShape(CLOSE);
}
class Node {
constructor(coordinate, velocity, maxSpeed = 0.5, maxForce = 1) {
this.coordinate = coordinate;
this.velocity = velocity;
this.maxSpeed = maxSpeed;
this.maxForce = maxForce;
}
applyForce(force) {
this.velocity.add(force);
this.velocity.limit(this.maxSpeed);
}
updateCoordinate() {
this.coordinate.add(this.velocity);
}
cohesion(other) {
const desired = p5.Vector.sub(other, this.coordinate).setMag(
this.maxSpeed
);
return p5.Vector.sub(desired, this.velocity).limit(this.maxForce);
}
seperate(other, separationDistance = 15) {
const v = p5.Vector.sub(this.coordinate, other.coordinate);
const dist = v.mag();
if(dist < separationDistance) {
return v.div(dist * dist);
}
return v.mult(0); // 零向量
}
}
class DifferentialLine {
constructor(nodesStart, rayStart, center) {
const aStep = TWO_PI / nodesStart;
const nodes = [];
for (let a = 0; a < TWO_PI; a += aStep) {
nodes.push(
new Node(
createVector(rayStart, 0).rotate(a).add(center),
p5.Vector.random2D()
)
);
}
this.nodes = nodes;
}
update() {
this.differentiate();
this.grow();
}
differentiate(separationCohesionRatio = 1.5) {
const allSeperation = [];
for(let i = 0; i < this.nodes.length; i++) {
allSeperation.push(createVector());
}
for(let i = 0; i < this.nodes.length; i++) {
const {sep, seps} = seperate(this.nodes, i, allSeperation);
const coh = cohesion(this.nodes, i);
this.nodes[i].applyForce(
sep.mult(separationCohesionRatio).add(coh)
);
this.nodes[i].updateCoordinate();
// 更新 allSeperation
for(let j = i + 1; j < this.nodes.length; j++) {
let sep = seps[j - i - 1];
if(sep.mag() > 0) {
allSeperation[j].sub(seps[j - i - 1]);
}
}
}
}
grow(maxEdgeLength = 15) {
const n = this.nodes.length;
const nodes = [];
for(let i = 0, j = 1; i < n; i++, j++) {
const node = this.nodes[i];
const nxNode = this.nodes[j % n];
nodes.push(node);
const dist = p5.Vector.dist(node.coordinate, nxNode.coordinate);
if(dist > maxEdgeLength) {
const {x, y} = middlePoint(node.coordinate, nxNode.coordinate);
nodes.push(
new Node(
createVector(x, y),
p5.Vector.random2D(),
(node.maxSpeed + nxNode.maxSpeed) / 2,
(node.maxForce + nxNode.maxForce) / 2
)
);
}
}
this.nodes = nodes;
}
}
function seperate(nodes, i, allSeperation) {
const node = nodes[i];
const sep = allSeperation[i];
let seps = [];
for(let j = i + 1; j < nodes.length; j++) {
const sep_j = node.seperate(nodes[j]);
if(sep_j.mag() > 0) {
sep.add(sep_j);
}
seps.push(sep_j);
}
sep.normalize().mult(node.maxSpeed).limit(node.maxForce);
return {sep, seps};
}
function cohesion(nodes, i) {
const n = nodes.length;
return nodes[i].cohesion(
middlePoint(
nodes[(n + i - 1) % n].coordinate,
nodes[(i + 1) % n].coordinate
)
);
}
function middlePoint(p1, p2) {
return p5.Vector.add(p1, p2).div(2);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment