Skip to content

Instantly share code, notes, and snippets.

@nch3ng
Created August 3, 2015 22:15
Show Gist options
  • Save nch3ng/8fb071182e74724fd03d to your computer and use it in GitHub Desktop.
Save nch3ng/8fb071182e74724fd03d to your computer and use it in GitHub Desktop.
infinite tree of life
<canvas id="canvas"></canvas>
<div id="text">
<div id="clic" nowrap>infinite tree of life<br>this pen is mouse/touch interactive!</div>
</div>

infinite tree of life

The recursive concept you are thinking about is causing your brain to run slowly. If you continue to think about this, your brain may become unresponsive. Do you want to think about something else?

A Pen by Gerard Ferrandez on CodePen.

License.

// The recursive concept you are thinking about is causing your brain to run slowly. If you continue to think about this, your brain may become unresponsive. Do you want to think about something else?
! function () {
"use strict";
// branch constructor
function Branch (parent, level, hue, x, y) {
this.parent = parent;
this.branches = [];
this.hue = hue;
this.p0 = parent ? parent.p1 : new Point(x, y, 0);
this.p1 = new Point(
x,
y,
parent === root ?
0
:
(
parent ?
parent.p1.z + (
level ?
Math.random() * 10 - 5
:
0
)
:
0
)
);
this.level = level;
this.life = 0;
this.angle = 0;
this.vx = 0;
this.vy = 0;
}
// grow branch
Branch.prototype.grow = function () {
// z move
this.p1.z--;
// 3D projection
this.p1.project();
// recursively grow children branches
for (var i = 0; i < this.branches.length; i++) {
var b = this.branches[i];
b.grow();
b.draw();
}
// grow
if (this.life-- > 1) {
this.p1.x += this.vx;
this.p1.y += this.vy;
}
// done - push more children branches
if (this.life === 1 && this.level > 0) {
this.branches.push(newBranch(this));
this.branches.push(newBranch(this));
// maybe another one
if (Math.random() <= freq) this.branches.push(newBranch(this));
this.life--;
}
// cut the tree
if (this.p0.z <= -250) {
this.parent = null;
}
}
// draw the branch
Branch.prototype.draw = function () {
var width = this.level === 1 ?
1
:
((this.level + 1) * (this.level + 1)) * 0.5 * this.p1.scale * scale
;
var color = 5 + Math.abs(this.p0.z * 0.35);
ctx.lineWidth = this.level ? width : width * leafSize;
ctx.strokeStyle = this.level ?
"hsl(" + (this.hue % 360) + ", 14%,"+ color + "%)" // branch
:
"hsl(" + ((this.hue + 180) % 360) + ", 70%, 50%)"; // leaf
ctx.beginPath();
ctx.lineCap = "round";
ctx.moveTo(canvas.centerX + this.p0.xp, canvas.centerY + this.p0.yp);
ctx.lineTo(canvas.centerX + this.p1.xp, canvas.centerY + this.p1.yp);
ctx.stroke();
}
// 3D point constructor
function Point (x, y, z) {
this.x = x;
this.y = y;
this.z = z;
this.scale = 0;
this.xp = 0;
this.yp = 0;
}
// 3D point projection
Point.prototype.project = function () {
this.scale = 265 / (265 + this.z);
this.xp = (this.x - canvas.width * 0.5) * this.scale;
this.yp = (this.y - canvas.height * 0.5) * this.scale;
}
// new branch factory
function newBranch (parent) {
var branch = new Branch (parent, parent.level - 1, hue, parent.p1.x, parent.p1.y);
branch.angle = Math.atan2(
parent.p1.y - parent.p0.y,
parent.p1.x - parent.p0.x
) + (branch.level ?
(Math.random() * maxAngle - (maxAngle * 0.5))
:
0
);
branch.vx = Math.cos(branch.angle) * growSpeed;
branch.vy = Math.sin(branch.angle) * growSpeed;
branch.life = branch.level ?
Math.round(scale * Math.random() * branch.level * branchLength) + 1
:
2
;
return branch;
}
// animate the tree
function tree () {
// clear screen
ctx.clearRect(0, 0, canvas.width, canvas.height);
// pointer trail
if (pointer.moveDistance > 20) {
pointer.moveDistance = 0;
// main trunk
var branch = new Branch (
root,
root.level,
hue,
root.p1.x,
root.p1.y
);
// add another branch
if (Math.random() <= freq) root.branches.push(
newBranch(root)
);
// new root
root = branch;
root.p1.x = pointer.x;
root.p1.y = pointer.y;
}
// increment color
hue++;
// traverse the tree
var trunk = root;
while (trunk) {
trunk.grow();
trunk.draw();
trunk = trunk.parent;
}
}
// prepare the canvas
var canvas = {
elem: document.getElementById('canvas'),
resize: function () {
this.width = this.elem.width = this.elem.offsetWidth;
this.height = this.elem.height = this.elem.offsetHeight;
this.centerX = this.width * 0.5;
this.centerY = this.height * 0.5;
scale = Math.max(this.width, this.height) / 1440;
}
}
var ctx = canvas.elem.getContext("2d");
canvas.elem.onselectstart = function() { return false; }
canvas.elem.ondragstart = function() { return false; }
window.addEventListener('resize', canvas.resize.bind(canvas), false);
canvas.resize();
// pointer events
var pointer = {
x: 0,
y: 0,
px: 0,
py: 0,
moveDistance: 0,
move: function (e) {
e.preventDefault();
var pointer = e.targetTouches ? e.targetTouches[0] : e;
// stop automove
if (automove) {
automove = false;
document.getElementById("clic").innerHTML = "";
}
this.x = pointer.clientX;
this.y = pointer.clientY;
this.distance();
// render tree
requestAnimationFrame(tree);
},
distance: function () {
var dx = this.x - this.px;
var dy = this.y - this.py;
this.moveDistance += Math.sqrt(dx * dx + dy * dy);
// speed limit
if (!automove && this.moveDistance > 40) {
this.x = this.px + dx * 0.1;
this.y = this.py + dy * 0.1;
}
this.px = this.x;
this.py = this.y;
}
}
window.addEventListener("mousemove", pointer.move.bind(pointer), false );
canvas.elem.addEventListener("touchmove", pointer.move.bind(pointer), false );
// variables
var root = null;
var hue = 0;
var automove = true;
var angleX = 0;
var angleY = 0;
/////////////////////////
var maxLevels = 6;
var branchLength = 16;
var leafSize = 14;
var growSpeed = 2;
var maxAngle = 1.2;
var freq = 0.3;
var scale = 1;
/////////////////////////
// auto start
! function auto () {
automove && requestAnimationFrame(auto);
// lissajou
pointer.x = canvas.centerX + canvas.centerX * Math.cos(angleX += 0.02) * 0.20;
pointer.y = canvas.centerY + canvas.centerY * Math.sin(angleY += 0.04) * 0.25;
pointer.distance();
// create the first branch
if (!root) {
root = new Branch (false, maxLevels, hue, pointer.x, pointer.y);
root.p0 = root.p1;
root.p0.project();
}
// render tree
tree();
}();
}();
@import url(http://fonts.googleapis.com/css?family=Poiret+One);
html {
overflow: hidden;
touch-action: none;
content-zooming: none;
}
body {
position: absolute;
margin: 0;
padding: 0;
background: #eee;
width: 100%;
height: 100%;
}
#canvas {
width: 100%;
height: 100%;
background: #eee;
position: absolute;
}
#text {
position:absolute;
left:0;
top:50%;
width:100%;
pointer-events: none;
}
#text div {
position:absolute;
color:#888;
left:0;
width:100%;
text-align:center;
top:-48px;
font-family: 'Poiret One', cursive;
font-size:48px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment