├── README.markdown
├── css
└── style.css
├── index.html
└── js
├── lib
├── DAT.GUI.min.js
└── RAF.js
└── recursion.js
/README.markdown:
--------------------------------------------------------------------------------
1 | # Recursion Toy
2 | Revisiting one of my favorite topics - **recursion**. This tool allows you to explore branching algorithms and rendering techniques, as well as save a snapshot of your creations.
3 |
4 | You can play with it live here: http://soulwire.co.uk/recursion-toy/
5 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | font-family: "Helvetica Neue", Arial, sans-serif;
3 | background-color: #f2f2f2;
4 | overflow: hidden;
5 | padding: 0;
6 | margin: 0;
7 | height: 100%;
8 | }
9 |
10 | #output {
11 | white-space: nowrap;
12 | text-transform: uppercase;
13 | font-size: 10px;
14 | position: absolute;
15 | padding: 0;
16 | margin: 0;
17 | color: #FFF;
18 | }
19 |
20 | #output li {
21 | background: rgba(20,20,20,0.9);
22 | list-style: none;
23 | margin-right: 1px;
24 | line-height: 22px;
25 | padding: 0px 10px;
26 | display: block;
27 | float: left;
28 | }
29 |
30 | #output li a {
31 | border-bottom: 1px dotted #999;
32 | text-decoration: none;
33 | color: #FFF;
34 | }
35 |
36 | .guidat {
37 | margin-right: 0px !important;
38 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Recursion Toy
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | - Click to spawn
14 |
15 | - Get the Code
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/js/lib/DAT.GUI.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * dat.gui Javascript Controller Library
3 | * http://dataarts.github.com/dat.gui
4 | *
5 | * Copyright 2011 Data Arts Team, Google Creative Lab
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | */
13 | var DAT=DAT||{};
14 | DAT.GUI=function(a){a==void 0&&(a={});var b=!1;a.height==void 0?a.height=300:b=!0;var d=[],c=[],i=!0,f,h,j=this,g=!0,e=280;if(a.width!=void 0)e=a.width;var q=!1,k,p,n=0,r;this.domElement=document.createElement("div");this.domElement.setAttribute("class","guidat");this.domElement.style.width=e+"px";var l=a.height,m=document.createElement("div");m.setAttribute("class","guidat-controllers");m.style.height=l+"px";m.addEventListener("DOMMouseScroll",function(a){var b=this.scrollTop;a.wheelDelta?b+=a.wheelDelta:
15 | a.detail&&(b+=a.detail);a.preventDefault&&a.preventDefault();a.returnValue=!1;m.scrollTop=b},!1);var o=document.createElement("a");o.setAttribute("class","guidat-toggle");o.setAttribute("href","#");o.innerHTML=g?"Close Controls":"Open Controls";var t=!1,C=0,x=0,u=!1,v,y,w,z,D=function(a){y=v;z=w;v=a.pageY;w=a.pageX;a=v-y;if(!g)if(a>0)g=!0,l=k=1,o.innerHTML=p||"Close Controls";else return;var b=z-w;if(a>0&&l>h){var d=DAT.GUI.map(l,h,h+100,1,0);a*=d}t=!0;C+=a;k+=a;l+=a;m.style.height=k+"px";x+=b;e+=
16 | b;e=DAT.GUI.constrain(e,240,500);j.domElement.style.width=e+"px";A()};o.addEventListener("mousedown",function(a){y=v=a.pageY;z=w=a.pageX;u=!0;a.preventDefault();C=x=0;document.addEventListener("mousemove",D,!1);return!1},!1);o.addEventListener("click",function(a){a.preventDefault();return!1},!1);document.addEventListener("mouseup",function(a){u&&!t&&j.toggle();if(u&&t)if(x==0&&B(),k>h)clearTimeout(r),k=n=h,s();else if(m.children.length>=1){var b=m.children[0].offsetHeight;clearTimeout(r);n=Math.round(l/
17 | b)*b-1;n<=0?(j.close(),k=b*2):(k=n,s())}document.removeEventListener("mousemove",D,!1);a.preventDefault();return u=t=!1},!1);this.domElement.appendChild(m);this.domElement.appendChild(o);if(a.domElement)a.domElement.appendChild(this.domElement);else if(DAT.GUI.autoPlace){if(DAT.GUI.autoPlaceContainer==null)DAT.GUI.autoPlaceContainer=document.createElement("div"),DAT.GUI.autoPlaceContainer.setAttribute("id","guidat"),document.body.appendChild(DAT.GUI.autoPlaceContainer);DAT.GUI.autoPlaceContainer.appendChild(this.domElement)}this.autoListenIntervalTime=
18 | 1E3/60;var E=function(){f=setInterval(function(){j.listen()},this.autoListenIntervalTime)};this.__defineSetter__("autoListen",function(a){(i=a)?c.length>0&&E():clearInterval(f)});this.__defineGetter__("autoListen",function(){return i});this.listenTo=function(a){c.length==0&&E();c.push(a)};this.unlistenTo=function(a){for(var b=0;bk?"auto":"hidden"},G={number:DAT.GUI.ControllerNumber,string:DAT.GUI.ControllerString,"boolean":DAT.GUI.ControllerBoolean,"function":DAT.GUI.ControllerFunction};this.reset=function(){for(var a=0,b=DAT.GUI.allControllers.length;a-1)document.body.scrollTop=DAT.GUI.scrollTop;n=k;this.open()}DAT.GUI.guiIndex++}DAT.GUI.allGuis.push(this);if(DAT.GUI.allGuis.length==1&&(window.addEventListener("keyup",function(a){!DAT.GUI.supressHotKeys&&a.keyCode==72&&DAT.GUI.toggleHide()},!1),
24 | DAT.GUI.inlineCSS))a=document.createElement("style"),a.setAttribute("type","text/css"),a.innerHTML=DAT.GUI.inlineCSS,document.head.insertBefore(a,document.head.firstChild)};DAT.GUI.hidden=!1;DAT.GUI.autoPlace=!0;DAT.GUI.autoPlaceContainer=null;DAT.GUI.allControllers=[];DAT.GUI.allGuis=[];DAT.GUI.supressHotKeys=!1;DAT.GUI.toggleHide=function(){DAT.GUI.hidden?DAT.GUI.open():DAT.GUI.close()};
25 | DAT.GUI.open=function(){DAT.GUI.hidden=!1;for(var a in DAT.GUI.allGuis)DAT.GUI.allGuis[a].domElement.style.display="block"};DAT.GUI.close=function(){DAT.GUI.hidden=!0;for(var a in DAT.GUI.allGuis)DAT.GUI.allGuis[a].domElement.style.display="none"};DAT.GUI.saveURL=function(){var a=DAT.GUI.replaceGetVar("saveString",DAT.GUI.getSaveString());window.location=a};DAT.GUI.scrollTop=-1;
26 | DAT.GUI.load=function(a){var a=a.split(","),b=parseInt(a[0]);DAT.GUI.scrollTop=parseInt(a[1]);for(var d=0;dd&&(a=d);return a};DAT.GUI.error=function(a){typeof console.error=="function"&&console.error("[DAT.GUI ERROR] "+a)};DAT.GUI.roundToDecimal=function(a,b){var d=Math.pow(10,b);return Math.round(a*d)/d};DAT.GUI.extendController=function(a){a.prototype=new DAT.GUI.Controller;a.prototype.constructor=a};DAT.GUI.addClass=function(a,b){DAT.GUI.hasClass(a,b)||(a.className+=" "+b)};
32 | DAT.GUI.hasClass=function(a,b){return a.className.indexOf(b)!=-1};DAT.GUI.removeClass=function(a,b){a.className=a.className.replace(RegExp(" "+b,"g"),"")};DAT.GUI.getVarFromURL("saveString")!=null&&DAT.GUI.load(DAT.GUI.getVarFromURL("saveString"));
33 | DAT.GUI.Controller=function(){this.parent=arguments[0];this.object=arguments[1];this.propertyName=arguments[2];if(arguments.length>0)this.initialValue=this.object[this.propertyName];this.domElement=document.createElement("div");this.domElement.setAttribute("class","guidat-controller "+this.type);this.propertyNameElement=document.createElement("span");this.propertyNameElement.setAttribute("class","guidat-propertyname");this.name(this.propertyName);this.domElement.appendChild(this.propertyNameElement);
34 | DAT.GUI.makeUnselectable(this.domElement)};DAT.GUI.Controller.prototype.changeFunction=null;DAT.GUI.Controller.prototype.finishChangeFunction=null;DAT.GUI.Controller.prototype.name=function(a){this.propertyNameElement.innerHTML=a;return this};DAT.GUI.Controller.prototype.reset=function(){this.setValue(this.initialValue);return this};DAT.GUI.Controller.prototype.listen=function(){this.parent.listenTo(this);return this};DAT.GUI.Controller.prototype.unlisten=function(){this.parent.unlistenTo(this);return this};
35 | DAT.GUI.Controller.prototype.setValue=function(a){if(this.object[this.propertyName]!=void 0)this.object[this.propertyName]=a;else{var b={};b[this.propertyName]=a;this.object.set(b)}this.changeFunction!=null&&this.changeFunction.call(this,a);this.updateDisplay();return this};DAT.GUI.Controller.prototype.getValue=function(){var a=this.object[this.propertyName];a==void 0&&(a=this.object.get(this.propertyName));return a};DAT.GUI.Controller.prototype.updateDisplay=function(){};
36 | DAT.GUI.Controller.prototype.onChange=function(a){this.changeFunction=a;return this};DAT.GUI.Controller.prototype.onFinishChange=function(a){this.finishChangeFunction=a;return this};
37 | DAT.GUI.Controller.prototype.options=function(){var a=this,b=document.createElement("select");if(arguments.length==1){var d=arguments[0],c;for(c in d){var i=document.createElement("option");i.innerHTML=c;i.setAttribute("value",d[c]);if(arguments[c]==this.getValue())i.selected=!0;b.appendChild(i)}}else for(c=0;c=h&&(a=h);return DAT.GUI.Controller.prototype.setValue.call(this,
47 | a)};this.updateDisplay=function(){g.value=DAT.GUI.roundToDecimal(a.getValue(),4);if(e)e.value=a.getValue()}};DAT.GUI.extendController(DAT.GUI.ControllerNumber);
48 | DAT.GUI.ControllerNumberSlider=function(a,b,d,c,i){var f=!1,h=this;this.domElement=document.createElement("div");this.domElement.setAttribute("class","guidat-slider-bg");this.fg=document.createElement("div");this.fg.setAttribute("class","guidat-slider-fg");this.domElement.appendChild(this.fg);var j=function(b){if(f){var c;c=h.domElement;var d=0,g=0;if(c.offsetParent){do d+=c.offsetLeft,g+=c.offsetTop;while(c=c.offsetParent);c=[d,g]}else c=void 0;b=DAT.GUI.map(b.pageX,c[0],c[0]+h.domElement.offsetWidth,
49 | a.getMin(),a.getMax());b=Math.round(b/a.getStep())*a.getStep();a.setValue(b)}};this.domElement.addEventListener("mousedown",function(b){f=!0;DAT.GUI.addClass(a.domElement,"active");j(b);document.addEventListener("mouseup",g,!1)},!1);var g=function(){DAT.GUI.removeClass(a.domElement,"active");f=!1;a.finishChangeFunction!=null&&a.finishChangeFunction.call(this,a.getValue());document.removeEventListener("mouseup",g,!1)};this.__defineSetter__("value",function(b){this.fg.style.width=DAT.GUI.map(b,a.getMin(),
50 | a.getMax(),0,100)+"%"});document.addEventListener("mousemove",j,!1);this.value=i};
51 | DAT.GUI.ControllerString=function(){this.type="string";var a=this;DAT.GUI.Controller.apply(this,arguments);var b=document.createElement("input"),d=this.getValue();b.setAttribute("value",d);b.setAttribute("spellcheck","false");this.domElement.addEventListener("mouseup",function(){b.focus();b.select()},!1);b.addEventListener("keyup",function(c){c.keyCode==13&&a.finishChangeFunction!=null&&(a.finishChangeFunction.call(this,a.getValue()),b.blur());a.setValue(b.value)},!1);b.addEventListener("mousedown",
52 | function(){DAT.GUI.makeSelectable(b)},!1);b.addEventListener("blur",function(){DAT.GUI.supressHotKeys=!1;a.finishChangeFunction!=null&&a.finishChangeFunction.call(this,a.getValue())},!1);b.addEventListener("focus",function(){DAT.GUI.supressHotKeys=!0},!1);this.updateDisplay=function(){b.value=a.getValue()};this.options=function(){a.domElement.removeChild(b);return DAT.GUI.Controller.prototype.options.apply(this,arguments)};this.domElement.appendChild(b)};DAT.GUI.extendController(DAT.GUI.ControllerString);
53 | DAT.GUI.inlineCSS="#guidat { position: fixed; top: 0; right: 0; width: auto; z-index: 1001; text-align: right; } .guidat { color: #fff; opacity: 0.97; text-align: left; float: right; margin-right: 20px; margin-bottom: 20px; background-color: #fff; } .guidat, .guidat input { font: 9.5px Lucida Grande, sans-serif; } .guidat-controllers { height: 300px; overflow-y: auto; overflow-x: hidden; background-color: rgba(0, 0, 0, 0.1); } a.guidat-toggle:link, a.guidat-toggle:visited, a.guidat-toggle:active { text-decoration: none; cursor: pointer; color: #fff; background-color: #222; text-align: center; display: block; padding: 5px; } a.guidat-toggle:hover { background-color: #000; } .guidat-controller { padding: 3px; height: 25px; clear: left; border-bottom: 1px solid #222; background-color: #111; } .guidat-controller, .guidat-controller input, .guidat-slider-bg, .guidat-slider-fg { -moz-transition: background-color 0.15s linear; -webkit-transition: background-color 0.15s linear; transition: background-color 0.15s linear; } .guidat-controller.boolean:hover, .guidat-controller.function:hover { background-color: #000; } .guidat-controller input { float: right; outline: none; border: 0; padding: 4px; margin-top: 2px; background-color: #222; } .guidat-controller select { margin-top: 4px; float: right; } .guidat-controller input:hover { background-color: #444; } .guidat-controller input:focus, .guidat-controller.active input { background-color: #555; color: #fff; } .guidat-controller.number { border-left: 5px solid #00aeff; } .guidat-controller.string { border-left: 5px solid #1ed36f; } .guidat-controller.string input { border: 0; color: #1ed36f; margin-right: 2px; width: 148px; } .guidat-controller.boolean { border-left: 5px solid #54396e; } .guidat-controller.function { border-left: 5px solid #e61d5f; } .guidat-controller.number input[type=text] { width: 35px; margin-left: 5px; margin-right: 2px; color: #00aeff; } .guidat .guidat-controller.boolean input { margin-top: 6px; margin-right: 2px; font-size: 20px; } .guidat-controller:last-child { border-bottom: none; -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5); -moz-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5); box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5); } .guidat-propertyname { padding: 5px; padding-top: 7px; cursor: default; display: inline-block; } .guidat-controller .guidat-slider-bg:hover, .guidat-controller.active .guidat-slider-bg { background-color: #444; } .guidat-controller .guidat-slider-bg .guidat-slider-fg:hover, .guidat-controller.active .guidat-slider-bg .guidat-slider-fg { background-color: #52c8ff; } .guidat-slider-bg { background-color: #222; cursor: ew-resize; width: 40%; margin-top: 2px; float: right; height: 21px; } .guidat-slider-fg { cursor: ew-resize; background-color: #00aeff; height: 21px; } ";
54 |
--------------------------------------------------------------------------------
/js/lib/RAF.js:
--------------------------------------------------------------------------------
1 | // Shim courtesy of Paul Irish & Jerome Etienne
2 | window.cancelRequestAnimFrame = ( function() {
3 | return window.cancelAnimationFrame ||
4 | window.webkitCancelRequestAnimationFrame ||
5 | window.mozCancelRequestAnimationFrame ||
6 | window.oCancelRequestAnimationFrame ||
7 | window.msCancelRequestAnimationFrame ||
8 | clearTimeout
9 | } )();
10 |
11 | window.requestAnimFrame = (function(){
12 | return window.requestAnimationFrame ||
13 | window.webkitRequestAnimationFrame ||
14 | window.mozRequestAnimationFrame ||
15 | window.oRequestAnimationFrame ||
16 | window.msRequestAnimationFrame ||
17 | function(/* function */ callback, /* DOMElement */ element){
18 | return window.setTimeout(callback, 1000 / 60);
19 | };
20 | })();
--------------------------------------------------------------------------------
/js/recursion.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Copyright (C) 2011 by Justin Windle
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | * THE SOFTWARE.
22 | */
23 |
24 | /**
25 | * --------------------
26 | * SETTINGS
27 | * --------------------
28 | */
29 |
30 | var CONFIG = {};
31 | var PRESETS = {};
32 | var RENDER_MODES = {
33 | Darkness: 'darkness',
34 | Segmented: 'segmented',
35 | Sketched: 'sketched'
36 | };
37 |
38 | PRESETS['Vines'] = {RENDER_MODE:RENDER_MODES.Darkness,BRANCH_PROBABILITY:0.2572,MAX_CONCURRENT:388,NUM_BRANCHES:4,MIN_RADIUS:0.1,MAX_RADIUS:69,MIN_WANDER_STEP:1.0184,MAX_WANDER_STEP:0.1702,MIN_GROWTH_RATE:10.6214,MAX_GROWTH_RATE:11.8251,MIN_SHRINK_RATE:0.99656,MAX_SHRINK_RATE:0.91265,MIN_DIVERGENCE:0.5101,MAX_DIVERGENCE:0.37466};
39 | PRESETS['Fibrous'] = {RENDER_MODE:RENDER_MODES.Segmented,BRANCH_PROBABILITY:0.05,MAX_CONCURRENT:800,NUM_BRANCHES:3,MIN_RADIUS:0.1,MAX_RADIUS:50,MIN_WANDER_STEP:0.28,MAX_WANDER_STEP:0.7,MIN_GROWTH_RATE:5,MAX_GROWTH_RATE:9,MIN_SHRINK_RATE:0.98,MAX_SHRINK_RATE:0.99,MIN_DIVERGENCE:0.01,MAX_DIVERGENCE:0.05};
40 | PRESETS['Graffiti'] = {RENDER_MODE:RENDER_MODES.Sketched,BRANCH_PROBABILITY:0.05,MAX_CONCURRENT:500,NUM_BRANCHES:6,MIN_RADIUS:0.15,MAX_RADIUS:70,MIN_WANDER_STEP:0.1197,MAX_WANDER_STEP:1.8269,MIN_GROWTH_RATE:13.66,MAX_GROWTH_RATE:17.35,MIN_SHRINK_RATE:0.95,MAX_SHRINK_RATE:0.98,MIN_DIVERGENCE:1.3268,MAX_DIVERGENCE:1.3885};
41 | PRESETS['Knarled'] = {RENDER_MODE:RENDER_MODES.Darkness,BRANCH_PROBABILITY:0.09,MAX_CONCURRENT:500,NUM_BRANCHES:5,MIN_RADIUS:0.1,MAX_RADIUS:100,MIN_WANDER_STEP:0.1,MAX_WANDER_STEP:0.2,MIN_GROWTH_RATE:3.7,MAX_GROWTH_RATE:10,MIN_SHRINK_RATE:0.97,MAX_SHRINK_RATE:0.99,MIN_DIVERGENCE:0.01,MAX_DIVERGENCE:0.05};
42 | PRESETS['Beech Tree'] = {RENDER_MODE:RENDER_MODES.Darkness,BRANCH_PROBABILITY:0.085,MAX_CONCURRENT:500,NUM_BRANCHES:1,MIN_RADIUS:0.1,MAX_RADIUS:40,MIN_WANDER_STEP:0.1599,MAX_WANDER_STEP:0.4,MIN_GROWTH_RATE:8,MAX_GROWTH_RATE:15,MIN_SHRINK_RATE:0.98,MAX_SHRINK_RATE:0.982,MIN_DIVERGENCE:0.31,MAX_DIVERGENCE:0.87};
43 | PRESETS['Frost'] = {RENDER_MODE:RENDER_MODES.Sketched,BRANCH_PROBABILITY:0.09,MAX_CONCURRENT:1000,NUM_BRANCHES:6,MIN_RADIUS:0.1,MAX_RADIUS:40,MIN_WANDER_STEP:0,MAX_WANDER_STEP:0,MIN_GROWTH_RATE:9.2,MAX_GROWTH_RATE:9.8,MIN_SHRINK_RATE:0.97,MAX_SHRINK_RATE:0.97,MIN_DIVERGENCE:0.4,MAX_DIVERGENCE:0.8};
44 | PRESETS['Wooly'] = {RENDER_MODE:RENDER_MODES.Segmented,BRANCH_PROBABILITY:0.07,MAX_CONCURRENT:348,NUM_BRANCHES:9,MIN_RADIUS:1.5,MAX_RADIUS:99,MIN_WANDER_STEP:0.5093,MAX_WANDER_STEP:2.654,MIN_GROWTH_RATE:7.8279,MAX_GROWTH_RATE:18.2956,MIN_SHRINK_RATE:0.94489,MAX_SHRINK_RATE:0.98716,MIN_DIVERGENCE:1.4656,MAX_DIVERGENCE:2.6998};
45 | PRESETS['Vegetable Root'] = {RENDER_MODE:RENDER_MODES.Darkness,BRANCH_PROBABILITY:0.06,MAX_CONCURRENT:437,NUM_BRANCHES:1,MIN_RADIUS:0.1,MAX_RADIUS:100,MIN_WANDER_STEP:0.05,MAX_WANDER_STEP:0.25,MIN_GROWTH_RATE:5,MAX_GROWTH_RATE:9,MIN_SHRINK_RATE:0.98,MAX_SHRINK_RATE:0.99,MIN_DIVERGENCE:0,MAX_DIVERGENCE:0.1};
46 | PRESETS['Hairball'] = {RENDER_MODE:RENDER_MODES.Sketched,BRANCH_PROBABILITY:0.6,MAX_CONCURRENT:800,MAX_DIVERGENCE:2.1,MAX_GROWTH_RATE:4.5,MAX_RADIUS:30,MAX_SHRINK_RATE:0.992,MAX_WANDER_STEP:0.2,MIN_DIVERGENCE:2,MIN_GROWTH_RATE:3.5,MIN_RADIUS:0.5,MIN_SHRINK_RATE:0.992,MIN_WANDER_STEP:0.1,NUM_BRANCHES:7};
47 | PRESETS['Intenstines'] = {RENDER_MODE:RENDER_MODES.Darkness,BRANCH_PROBABILITY:1,MAX_CONCURRENT:350,NUM_BRANCHES:3,MIN_RADIUS:0.1,MAX_RADIUS:100,MIN_WANDER_STEP:0.1,MAX_WANDER_STEP:0.72,MIN_GROWTH_RATE:0.9,MAX_GROWTH_RATE:6.15,MIN_SHRINK_RATE:0.935,MAX_SHRINK_RATE:0.999,MIN_DIVERGENCE:0.01,MAX_DIVERGENCE:0.05};
48 |
49 | function configure(settings) {
50 | for(var prop in settings) {
51 | CONFIG[prop] = settings[prop];
52 | }
53 | }
54 |
55 | configure(PRESETS['Vines']);
56 |
57 | /**
58 | * --------------------
59 | * UTILS
60 | * --------------------
61 | */
62 |
63 | var PI = Math.PI;
64 | var TWO_PI = Math.PI * 2;
65 | var HALF_PI = Math.PI / 2;
66 | var BRANCHES = [];
67 |
68 | function random(min, max) {
69 | return min + Math.random() * (max - min);
70 | }
71 |
72 | /**
73 | * --------------------
74 | * BRANCH
75 | * --------------------
76 | */
77 |
78 | var Branch = function(x, y, theta, radius, scale, generation) {
79 |
80 | this.x = x;
81 | this.y = y;
82 | this.ox = x;
83 | this.oy = y;
84 | this.x1 = NaN;
85 | this.x2 = NaN;
86 | this.y1 = NaN;
87 | this.y2 = NaN;
88 | this.scale = scale || 1.0;
89 | this.theta = theta;
90 | this.oTheta = theta;
91 | this.radius = radius;
92 | this.generation = generation || 1;
93 | this.growing = true;
94 | this.age = 0;
95 |
96 | this.wanderStep = random(CONFIG.MIN_WANDER_STEP, CONFIG.MAX_WANDER_STEP);
97 | this.growthRate = random(CONFIG.MIN_GROWTH_RATE, CONFIG.MAX_GROWTH_RATE);
98 | this.shrinkRate = random(CONFIG.MIN_SHRINK_RATE, CONFIG.MAX_SHRINK_RATE);
99 | }
100 |
101 | Branch.prototype = {
102 |
103 | update: function() {
104 |
105 | if(this.growing) {
106 |
107 | this.ox = this.x;
108 | this.oy = this.y;
109 | this.oTheta = this.theta;
110 |
111 | this.theta += random(-this.wanderStep, this.wanderStep);
112 |
113 | this.x += Math.cos(this.theta) * this.growthRate * this.scale;
114 | this.y += Math.sin(this.theta) * this.growthRate * this.scale;
115 |
116 | this.scale *= this.shrinkRate;
117 |
118 | // Branch
119 | if(BRANCHES.length < CONFIG.MAX_CONCURRENT && Math.random() < CONFIG.BRANCH_PROBABILITY) {
120 |
121 | var offset = random(CONFIG.MIN_DIVERGENCE, CONFIG.MAX_DIVERGENCE);
122 | var theta = this.theta + offset * (Math.random() < 0.5 ? 1 : -1);
123 | var scale = this.scale * 0.95;
124 | var radius = this.radius * scale;
125 | var branch = new Branch(this.x, this.y, theta, radius, scale);
126 |
127 | branch.generation = this.generation + 1;
128 |
129 | BRANCHES.push(branch);
130 | }
131 |
132 | // Limit
133 | if(this.radius * this.scale <= CONFIG.MIN_RADIUS) {
134 | this.growing = false;
135 | }
136 |
137 | this.age++;
138 | }
139 | },
140 |
141 | render: function(context) {
142 |
143 | if(this.growing) {
144 |
145 | var x1, x2, y1, y2;
146 | var scale = this.scale;
147 | var radius = this.radius * scale;
148 |
149 | context.save();
150 |
151 | switch(CONFIG.RENDER_MODE) {
152 |
153 | case RENDER_MODES.Segmented :
154 |
155 | // Draw outline
156 | context.beginPath();
157 | context.moveTo(this.ox, this.oy);
158 | context.lineTo(this.x, this.y);
159 |
160 | if(radius > 5.0) {
161 | context.shadowOffsetX = 1;
162 | context.shadowOffsetY = 1;
163 | context.shadowBlur = scale;
164 | context.shadowColor = 'rgba(0,0,0,0.05)';
165 | }
166 |
167 | context.lineWidth = radius + scale;
168 | context.strokeStyle = '#000';
169 | context.lineCap = 'round';
170 | context.stroke();
171 | context.closePath();
172 |
173 | // Draw fill
174 | context.beginPath();
175 | context.moveTo(this.ox, this.oy);
176 | context.lineTo(this.x, this.y);
177 |
178 | context.lineWidth = radius;
179 | context.strokeStyle = '#FFF';
180 | context.lineCap = 'round';
181 | context.stroke();
182 |
183 | context.closePath();
184 |
185 | break;
186 |
187 | case RENDER_MODES.Sketched :
188 |
189 | radius *= 0.5;
190 | radius += 0.5;
191 |
192 | x1 = this.x + Math.cos(this.theta - HALF_PI) * radius;
193 | x2 = this.x + Math.cos(this.theta + HALF_PI) * radius;
194 |
195 | y1 = this.y + Math.sin(this.theta - HALF_PI) * radius;
196 | y2 = this.y + Math.sin(this.theta + HALF_PI) * radius;
197 |
198 | context.lineWidth = 0.5 + scale;
199 | context.strokeStyle = '#000';
200 | context.fillStyle = '#FFF';
201 | context.lineCap = 'round';
202 |
203 | // Starting point
204 | if(this.generation === 1 && this.age === 1) {
205 | context.beginPath();
206 | context.arc(this.x, this.y, radius, 0, TWO_PI);
207 | context.stroke();
208 | context.fill();
209 | }
210 |
211 | // Draw sides
212 | if(this.age > 1) {
213 | context.beginPath();
214 | context.moveTo(this.x1, this.y1);
215 | context.lineTo(x1, y1);
216 | context.moveTo(this.x2, this.y2);
217 | context.lineTo(x2, y2);
218 | context.stroke();
219 | }
220 |
221 | // Draw ribbon
222 | context.beginPath();
223 | context.moveTo(this.x1, this.y1);
224 | context.lineTo(x1, y1);
225 | context.lineTo(x2, y2);
226 | context.lineTo(this.x2, this.y2);
227 | context.closePath();
228 | context.fill();
229 |
230 | this.x1 = x1;
231 | this.x2 = x2;
232 |
233 | this.y1 = y1;
234 | this.y2 = y2;
235 |
236 | break;
237 |
238 | case RENDER_MODES.Darkness :
239 |
240 | radius *= 0.5;
241 |
242 | x1 = this.x + Math.cos(this.theta - HALF_PI) * radius;
243 | x2 = this.x + Math.cos(this.theta + HALF_PI) * radius;
244 |
245 | y1 = this.y + Math.sin(this.theta - HALF_PI) * radius;
246 | y2 = this.y + Math.sin(this.theta + HALF_PI) * radius;
247 |
248 | context.lineWidth = scale;
249 | context.strokeStyle = 'rgba(255,255,255,0.9)';
250 | context.lineCap = 'round';
251 | context.fillStyle = '#111';
252 |
253 | // Starting point
254 | if(this.generation === 1 && this.age === 1) {
255 | context.beginPath();
256 | context.arc(this.x, this.y, radius, 0, TWO_PI);
257 | context.closePath();
258 | context.fill();
259 | context.stroke();
260 | }
261 |
262 | // Shadow
263 | if(scale > 0.05) {
264 | context.shadowOffsetX = scale;
265 | context.shadowOffsetY = scale;
266 | context.shadowBlur = scale;
267 | context.shadowColor = '#111';
268 | }
269 |
270 | // Draw ribbon
271 | context.beginPath();
272 | context.moveTo(this.x1, this.y1);
273 | context.lineTo(x1, y1);
274 | context.lineTo(x2, y2);
275 | context.lineTo(this.x2, this.y2);
276 | context.closePath();
277 | context.fill();
278 |
279 | // Draw sides
280 | if(this.age > 1 && scale > 0.1) {
281 | context.beginPath();
282 | context.moveTo(this.x1, this.y1);
283 | context.lineTo(x1, y1);
284 | context.moveTo(this.x2, this.y2);
285 | context.lineTo(x2, y2);
286 | context.stroke();
287 | }
288 |
289 | this.x1 = x1;
290 | this.x2 = x2;
291 |
292 | this.y1 = y1;
293 | this.y2 = y2;
294 |
295 | break;
296 | }
297 |
298 | context.restore();
299 | }
300 | },
301 |
302 | destroy: function() {
303 |
304 | }
305 | };
306 |
307 | /**
308 | * --------------------
309 | * SKETCH
310 | * --------------------
311 | */
312 |
313 | var Recursion = new function() {
314 |
315 | var started = false;
316 | var $canvas = $('#canvas');
317 | var $branchCount = $('#output .branchCount');
318 | var canvas = $canvas[0];
319 | var context = canvas.getContext('2d');
320 |
321 | function spawn(x, y) {
322 |
323 | var theta, radius;
324 |
325 | for(var i = 0; i < CONFIG.NUM_BRANCHES; i++) {
326 | theta = (i / CONFIG.NUM_BRANCHES) * TWO_PI;
327 | radius = CONFIG.MAX_RADIUS;
328 | BRANCHES.push(new Branch(x, y, theta - HALF_PI, radius));
329 | }
330 | }
331 |
332 | function update() {
333 |
334 | //cancelRequestAnimFrame(update);
335 | requestAnimFrame(update);
336 |
337 | var i, n, branch;
338 |
339 | for(i = 0, n = BRANCHES.length; i < n; i++) {
340 | branch = BRANCHES[i];
341 | branch.update();
342 | branch.render(context);
343 | }
344 |
345 | // Strip dead branches
346 | for(i = BRANCHES.length - 1; i >= 0; i--) {
347 | if(!BRANCHES[i].growing) {
348 | BRANCHES.splice(i,1);
349 | }
350 | }
351 |
352 | var count = BRANCHES.length.toString();
353 | while(count.length < 3) { count = '0' + count; }
354 | $branchCount.text('Branch count: ' + count);
355 | }
356 |
357 | function onClick(e) {
358 |
359 | Recursion.reset();
360 | spawn(e.offsetX, e.offsetY);
361 | }
362 |
363 | function onResize(e) {
364 |
365 | var width = window.innerWidth;
366 | var height = window.innerHeight;
367 | var scale = window.devicePixelRatio || 1;
368 | canvas.width = width * scale;
369 | canvas.height = height * scale;
370 | canvas.style.width = width + 'px';
371 | canvas.style.height = height + 'px';
372 | context.scale(scale, scale);
373 |
374 | Recursion.reset();
375 | spawn(window.innerWidth / 2, window.innerHeight / 2);
376 | }
377 |
378 | return {
379 |
380 | init: function() {
381 |
382 | onResize();
383 |
384 | if(!started) {
385 | started = true;
386 | $(window).resize(onResize);
387 | $canvas.click(onClick);
388 | update();
389 | }
390 | },
391 |
392 | reset: function() {
393 |
394 | for(var i = 0, n = BRANCHES.length; i < n; i++) {
395 | BRANCHES[i].destroy();
396 | }
397 |
398 | BRANCHES = [];
399 | },
400 |
401 | save: function() {
402 |
403 | var image = canvas.toDataURL('image/png');
404 |
405 | var win = window.open('about:blank', '_blank', 'width=1000,height=700');
406 |
407 | var html = $('');
408 | var head = $('');
409 | var body = $('');
410 |
411 | body.css({
412 | background: '#f2f2f2',
413 | padding: 0,
414 | margin: 0
415 | });
416 |
417 | head.append($('Recursion » Right Click & Save the Image Below'));
418 | body.append($('
'));
419 |
420 | html.append(head);
421 | html.append(body);
422 |
423 | win.document.write('' + html.html());
424 | win.document.close();
425 | },
426 |
427 | clear: function() {
428 | context.clearRect(0, 0, canvas.width, canvas.height);
429 | }
430 | };
431 | }
432 |
433 | /**
434 | * --------------------
435 | * GUI
436 | * --------------------
437 | */
438 |
439 | function saveConfig() {
440 | var config = [];
441 | for(var i in CONFIG) { config.push(i + ':' + CONFIG[i]); }
442 | console.log("PRESETS['__name__'] = {" + config.join(',') + "};");
443 | }
444 |
445 | // Build preset map for GUI
446 | var preset = {key:''}, keys = {};
447 | for(var i in PRESETS) { keys[i] = i; }
448 |
449 | function randomise() {
450 | CONFIG.BRANCH_PROBABILITY = random(0.01,1.0);
451 | CONFIG.MAX_CONCURRENT = random(10,1000);
452 | CONFIG.NUM_BRANCHES = random(1,20);
453 | CONFIG.MIN_RADIUS = random(0.1,2.0);
454 | CONFIG.MAX_RADIUS = random(CONFIG.MIN_RADIUS,100);
455 | CONFIG.MIN_WANDER_STEP = random(0.1,PI);
456 | CONFIG.MAX_WANDER_STEP = random(CONFIG.MIN_WANDER_STEP,PI);
457 | CONFIG.MIN_GROWTH_RATE = random(0.1,20);
458 | CONFIG.MAX_GROWTH_RATE = random(CONFIG.MIN_GROWTH_RATE,20);
459 | CONFIG.MIN_SHRINK_RATE = random(0.9,0.999);
460 | CONFIG.MAX_SHRINK_RATE = random(CONFIG.MIN_SHRINK_RATE,0.999);
461 | CONFIG.MIN_DIVERGENCE = random(0.0,PI);
462 | CONFIG.MAX_DIVERGENCE = random(CONFIG.MIN_DIVERGENCE,PI);
463 | Recursion.init();
464 | GUI.listenAll();
465 | }
466 |
467 | var GUI = new DAT.GUI({width: 340});
468 | GUI.name('Recursion Settings');
469 |
470 | // Config
471 | GUI.add(CONFIG, 'NUM_BRANCHES').name('Trunk Count').min(1).max(20).step(1);
472 | GUI.add(CONFIG, 'MAX_CONCURRENT').name('Max Concurrent').min(10).max(1000).step(1);
473 | GUI.add(CONFIG, 'BRANCH_PROBABILITY').name('Branch Probability').min(0.01).max(1.0).step(0.01);
474 | GUI.add(CONFIG, 'MIN_RADIUS').name('Radius (Min)').min(0.1).max(100);
475 | GUI.add(CONFIG, 'MAX_RADIUS').name('Radius (Max)').min(0.1).max(100);
476 | GUI.add(CONFIG, 'MIN_WANDER_STEP').name('Wander (Min)').min(0.0).max(PI).step(0.01);
477 | GUI.add(CONFIG, 'MAX_WANDER_STEP').name('Wander (Max)').min(0.0).max(PI).step(0.01);
478 | GUI.add(CONFIG, 'MIN_GROWTH_RATE').name('Growth (Min)').min(0.1).max(20).step(0.1);
479 | GUI.add(CONFIG, 'MAX_GROWTH_RATE').name('Growth (Max)').min(0.1).max(20).step(0.1);
480 | GUI.add(CONFIG, 'MIN_SHRINK_RATE').name('Shrink (Min)').min(0.9).max(0.999).step(0.005);
481 | GUI.add(CONFIG, 'MAX_SHRINK_RATE').name('Shrink (Max)').min(0.9).max(0.999).step(0.005);
482 | GUI.add(CONFIG, 'MIN_DIVERGENCE').name('Divergeence (Min)').min(0.0).max(PI).step(0.01);
483 | GUI.add(CONFIG, 'MAX_DIVERGENCE').name('Divergeence (Max)').min(0.0).max(PI).step(0.01);
484 |
485 | GUI.add(preset, 'key').name('Preset Behaviors').options(keys).onChange(function(){
486 | configure(PRESETS[preset.key]);
487 | Recursion.init();
488 | GUI.listenAll();
489 | });
490 | GUI.add(CONFIG, 'RENDER_MODE').name('Render Style').options(RENDER_MODES).onChange(Recursion.init);
491 | GUI.add(Recursion, 'save').name('Save as PNG');
492 | GUI.add(Recursion, 'clear').name('Clear').onFire(Recursion.reset);
493 | GUI.add(Recursion, 'init').name('Clear & Regenerate');
494 | GUI.close();
495 |
496 | //GUI.add(this, 'randomise').name('Randomise');
497 | //GUI.add(this, 'saveConfig').name('Save Config');
498 |
499 | /**
500 | * --------------------
501 | * INIT
502 | * --------------------
503 | */
504 |
505 | Recursion.init();
506 |
--------------------------------------------------------------------------------