notes/sandbox/simulating_vines/app2.js
2023-04-22 00:41:54 -07:00

342 lines
9.5 KiB
JavaScript

(function () {
var canvas = document.getElementById("vines");
var context = canvas.getContext("2d");
var canvasb = document.getElementById("leaves");
var contextb = canvasb.getContext("2d");
var leaves = [];
// Resize canvas
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvasb.width = window.innerWidth;
canvasb.height = window.innerHeight;
// Clear canvas
context.fillStyle = "#89f1d7";
context.fillRect(0, 0, canvas.width, canvas.height);
// Calculate distance from point to line
function distancePointToLine(point, line) {
var L = Math.sqrt(
Math.pow(line[1].x - line[0].x, 2) + Math.pow(line[1].y - line[0].y, 2)
);
var r =
((point.x - line[0].x) * (line[1].x - line[0].x) +
(point.y - line[0].y) * (line[1].y - line[0].y)) /
Math.pow(L, 2);
var s =
((line[0].y - point.y) * (line[1].x - line[0].x) -
(line[0].x - point.x) * (line[1].y - line[0].y)) /
Math.pow(L, 2);
if (r >= 0 && r <= 1) {
return Math.abs(s) * L;
} else {
return Math.min(
Math.sqrt(
Math.pow(point.x - line[0].x, 2) + Math.pow(point.y - line[0].y, 2)
),
Math.sqrt(
Math.pow(point.x - line[1].x, 2) + Math.pow(point.y - line[1].y, 2)
)
);
}
}
// Draw leaf
function drawLeaf(leaf) {
leaf.size = Math.max(10, leaf.size);
contextb.save();
contextb.translate(leaf.x, leaf.y);
contextb.rotate(leaf.angle);
contextb.translate(-leaf.x, -leaf.y);
contextb.fillStyle = "#ff0078";
contextb.beginPath();
contextb.moveTo(leaf.x, leaf.y);
contextb.bezierCurveTo(
leaf.x + leaf.size,
leaf.y,
leaf.x + leaf.size * 2,
leaf.y + leaf.size,
leaf.x,
leaf.y + leaf.size * 4
);
contextb.bezierCurveTo(
leaf.x - leaf.size * 2,
leaf.y + leaf.size,
leaf.x - leaf.size,
leaf.y,
leaf.x,
leaf.y
);
contextb.closePath();
contextb.fill();
contextb.restore();
}
// Draw vine
function drawVinesWithLattice(
lattice,
seeds,
interations,
sort,
prune,
minLength,
maxLength
) {
var t = 1;
var maxLineWidth = 10;
// Create initial branches
var branches = [];
for (var i in seeds) {
branches.push({
points: [
{ x: seeds[i].x, y: seeds[i].y },
{ x: seeds[i].x, y: seeds[i].y },
{ x: seeds[i].x, y: seeds[i].y },
{ x: seeds[i].x, y: seeds[i].y },
],
angle: 0,
distanceToLattice: 1000,
lineWidth: 6,
active: true,
leaf: false,
});
}
// Animation function
function tick() {
// Draw branches
contextb.clearRect(0, 0, canvasb.width, canvasb.height);
for (var i in branches) {
var ax =
(-branches[i].points[0].x +
3 * branches[i].points[1].x -
3 * branches[i].points[2].x +
branches[i].points[3].x) /
6;
var ay =
(-branches[i].points[0].y +
3 * branches[i].points[1].y -
3 * branches[i].points[2].y +
branches[i].points[3].y) /
6;
var bx =
(branches[i].points[0].x -
2 * branches[i].points[1].x +
branches[i].points[2].x) /
2;
var by =
(branches[i].points[0].y -
2 * branches[i].points[1].y +
branches[i].points[2].y) /
2;
var cx = (-branches[i].points[0].x + branches[i].points[2].x) / 2;
var cy = (-branches[i].points[0].y + branches[i].points[2].y) / 2;
var dx =
(branches[i].points[0].x +
4 * branches[i].points[1].x +
branches[i].points[2].x) /
6;
var dy =
(branches[i].points[0].y +
4 * branches[i].points[1].y +
branches[i].points[2].y) /
6;
context.lineWidth = branches[i].lineWidth;
context.beginPath();
context.moveTo(
ax * Math.pow(t, 3) + bx * Math.pow(t, 2) + cx * t + dx,
ay * Math.pow(t, 3) + by * Math.pow(t, 2) + cy * t + dy
);
context.lineTo(
ax * Math.pow(t + 0.1, 3) +
bx * Math.pow(t + 0.1, 2) +
cx * (t + 0.1) +
dx,
ay * Math.pow(t + 0.1, 3) +
by * Math.pow(t + 0.1, 2) +
cy * (t + 0.1) +
dy
);
context.stroke();
context.closePath();
// Draw leaf
if (branches[i].leaf) {
if (branches[i].active) {
var x1 = ax * Math.pow(t, 3) + bx * Math.pow(t, 2) + cx * t + dx;
var y1 = ay * Math.pow(t, 3) + by * Math.pow(t, 2) + cy * t + dy;
var x2 =
ax * Math.pow(t + 0.1, 3) +
bx * Math.pow(t + 0.1, 2) +
cx * (t + 0.1) +
dx;
var y2 =
ay * Math.pow(t + 0.1, 3) +
by * Math.pow(t + 0.1, 2) +
cy * (t + 0.1) +
dy;
branches[i].leaf.x = x2;
branches[i].leaf.y = y2;
branches[i].leaf.angle = Math.atan2(y2 - y1, x2 - x1) - Math.PI / 2;
branches[i].leaf.size = branches[i].lineWidth * 2;
} else {
branches[i].leaf.size = (branches[i].lineWidth + t) * 2;
}
drawLeaf(branches[i].leaf);
}
}
// Draw leaves
for (var i in leaves) {
drawLeaf(leaves[i]);
}
// When finished drawing splines, create a new set of branches
t += 0.02;
if (t >= 1) {
var new_branches = [];
for (var j = branches.length - 1; j >= 0; j--) {
if (branches[j].active) {
// Split branch into two
for (var k = 0; k < 2; k++) {
// Generate random deviation from previous angle
var angle = branches[j].angle - (Math.random() * 180 - 90);
// Determine closest lattice point
var distance = 100000;
for (var l in lattice) {
var result = distancePointToLine(
branches[j].points[3],
lattice[l]
);
if (result < distance) distance = result;
}
// Generate random length
var length = Math.random() * (maxLength - minLength) + minLength;
// Calculate new point
var x2 =
branches[j].points[3].x +
Math.sin((Math.PI * angle) / 180) * length;
var y2 =
branches[j].points[3].y -
Math.cos((Math.PI * angle) / 180) * length;
// Add to new branch array
new_branches.push({
points: [
branches[j].points[1],
branches[j].points[2],
branches[j].points[3],
{ x: x2, y: y2 },
],
angle: angle,
distanceToLattice: distance,
lineWidth: 6,
active: true,
leaf: {
x: x2,
y: y2,
angle: 0,
},
parent: branches[j],
});
// "Deactivate" branch
branches[j].active = false;
}
// Grow branch
} else {
branches[j].lineWidth++;
if (branches[j].lineWidth > maxLineWidth) {
leaves.push(branches[j].leaf);
branches.splice(j, 1);
}
}
}
// Sort branches by distance to lattice
new_branches.sort(function (a, b) {
return a.distanceToLattice - b.distanceToLattice;
});
// If over 10 branches, prune the branches furthest from the lattice
if (prune) {
if (sort) {
while (new_branches.length > 20) new_branches.pop();
} else {
while (new_branches.length > 20) {
new_branches.splice(
Math.floor(Math.random() * new_branches.length),
1
);
}
}
}
// Remove leaves from parent
for (var i = 0; i < new_branches.length; i++) {
new_branches[i].parent.leaf = false;
}
// Replace old branch array with new
branches = branches.concat(new_branches);
interations--;
t = 0;
}
// Keep on animating
if (interations > 0) {
requestAnimationFrame(tick);
}
}
tick();
}
// Setup lattice
var space = canvas.height / 3;
var lattice = [];
for (var y = -space * 4; y < space * 4; y += space) {
lattice.push([
{ x: 0, y: y + canvas.height },
{ x: canvas.width, y: y },
]);
}
// Draw lattice
context.strokeStyle = "rgba(0, 0, 0, 0.1)";
context.lineWidth = canvas.height * 0.05;
context.lineCap = "square";
for (var i in lattice) {
context.beginPath();
context.moveTo(lattice[i][0].x, lattice[i][0].y);
context.lineTo(lattice[i][1].x, lattice[i][1].y);
context.stroke();
}
context.strokeStyle = "#ff0078";
// Create vines
var minLength = canvas.width * 0.05;
var maxLength = canvas.width * 0.1;
var iterations = 20;
var seeds = [
{ x: -5, y: space },
{ x: space, y: -5 },
{ x: -5, y: space * 3 },
{ x: canvas.width + 5, y: space },
{ x: space * 3, y: canvas.height + 5 },
];
drawVinesWithLattice(
lattice,
seeds,
iterations,
true,
true,
minLength,
maxLength
);
})();