├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── css └── main.css ├── fractal.js ├── index.html ├── parentA.png ├── parentB.png ├── screenshot.png └── tutorial.gif /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: victorqribeiro 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Victor Ribeiro 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crossing Fractals 2 | 3 | Crossing fractal using principles from genetic algorithm. 4 | 5 |  6 | 7 | [live version](https://victorribeiro.com/randomFractal) 8 | 9 | ## About 10 | 11 | Every once in a while someone asks me about the tree on the background of my homepage, this project tries to tell a little bit about it. 12 | 13 | Back in college I got really interested in fractals and didn't took me long enough to find Keith Peters's book - [Playing with chaos](http://www.playingwithchaos.net/). I bought the e-book and fell in love with it right away. Not also it explains a lot of fractals, it does so using JavaScript and Canvas, two things I really love. I still learn a lot from that book. Anyway, about the tree: as soon as I saw that beautiful siluete of a tree forming with a couple of rules, I decided to use it on my homepage. If you wanna know more about fractals I highly suggest you to buy the e-book aswell. 14 | 15 | ## The Rules used in this project 16 | 17 | Parent A 18 |  19 | 20 | Parent B 21 |  22 | 23 | Since I cannot create my own rules, I took the rules I used to create the tree and a rule to create a leaf and decided to cross them, as you'd do with a plant if you wanna make a new specie. So, every time you click / touch the screen, a new fractal emerge from that crossing. 24 | 25 | ## UPDATE 26 | 27 | This project has taken an expected turn and I'll update the README later. For now, here's how its working: 28 | 29 | On the left you have 9 fractals generated from the crossing of the two parents on the left. You can click to select / deselect a fractal you like. When you click evolve, the fractal(s) you selected will become a parent for the next generation, having a chance of being selected to the next crossing. 30 | 31 | I have pushed a feature where you can view, adjust the zoom and the resolution of the selected fractals, learn more about it [here](https://youtu.be/DY5Me5hiQOc) 32 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | height: 100%; 5 | } 6 | 7 | canvas { 8 | box-sizing: border-box; 9 | border: solid 1px black; 10 | } 11 | 12 | #main { 13 | width: 100%; 14 | height: 100%; 15 | display: flex; 16 | } 17 | 18 | #area { 19 | width: 80%; 20 | height: 100%; 21 | overflow: auto; 22 | display: flex; 23 | flex-wrap: wrap; 24 | align-items: center; 25 | justify-content: space-around; 26 | } 27 | #toolbox { 28 | width: 20%; 29 | height: 100%; 30 | display: flex; 31 | flex-direction: column; 32 | align-items: center; 33 | justify-content: center; 34 | } 35 | 36 | #parents { 37 | text-align: center; 38 | overflow: auto; 39 | height: 50%; 40 | } 41 | 42 | #bg { 43 | position: fixed; 44 | background-color: rgba(0,0,0,0.3); 45 | width: 100%; 46 | height: 100%; 47 | left: 0; 48 | top: 0; 49 | display: flex; 50 | align-items: center; 51 | justify-content: center; 52 | } 53 | 54 | #window { 55 | width: 90%; 56 | height: 90%; 57 | border: 1px black solid; 58 | display: flex; 59 | flex-direction: column; 60 | background-color: white; 61 | } 62 | 63 | #toolbar { 64 | width: 100%; 65 | } 66 | -------------------------------------------------------------------------------- /fractal.js: -------------------------------------------------------------------------------- 1 | 2 | const $ = _ => document.querySelector(_) 3 | 4 | const $c = _ => document.createElement(_) 5 | 6 | const URL = window.URL || window.webkitURL 7 | 8 | let id = 0, previewID = 0, selected = {} 9 | 10 | const population = Array(9) 11 | 12 | const keys = ['a', 'b', 'c', 'd', 'tx', 'ty', 'w'] 13 | 14 | const parents = [ 15 | [ 16 | 0.85,0.04,-0.04,0.85,0,1.6,0.85, 17 | -0.15,0.28,0.26,0.24,0,0.44,0.07, 18 | 0.2,-0.26,0.23,0.22,0,1.6,0.07, 19 | 0,0,0,0.16,0,0,0.01 20 | ], 21 | [ 22 | 0.05,0,0,0.6,0,0,0.17, 23 | 0.05,0,0,-0.5,0,1,0.17, 24 | 0.46,-0.321,0.386,0.383,0,0.6,0.17, 25 | 0.47,-0.154,0.171,0.423,0,1.1,0.17, 26 | 0.433,0.275,-0.25,0.476,0,1,0.16, 27 | 0.421,0.257,-0.353,0.306,0,0.7,0.16 28 | ], 29 | [ 30 | 0.1400,0.0100,0.0000,0.5100,-0.0800,-1.3100,0.1, 31 | 0.4300,0.5200,-0.4500,0.5000,1.4900,-0.7500,0.15, 32 | 0.4500,-0.4900,0.4700,0.4700,-1.6200,-0.7400,0.15, 33 | 0.4900,0.0000,0.0000,0.5100,0.0200,1.6200,0.6 34 | ] 35 | ] 36 | 37 | const createCanvasContext = (w,h,population = true) => { 38 | const canvas = $c("canvas") 39 | canvas.width = w 40 | canvas.height = h 41 | if(population){ 42 | canvas.id = id++ 43 | canvas.onclick = () => { 44 | if( canvas.id in selected ){ 45 | delete selected[canvas.id] 46 | canvas.style.border = "solid 1px black" 47 | }else{ 48 | selected[canvas.id] = true 49 | canvas.style.border = "solid 2px red" 50 | } 51 | } 52 | }else{ 53 | canvas.id = previewID++ 54 | canvas.onclick = () => showPreview(canvas.id) 55 | } 56 | const c = canvas.getContext("2d") 57 | c.translate(w/2, h) 58 | return c 59 | } 60 | 61 | const crossover = (a,b) => { 62 | const child = Array( Math.random() > 0.5 ? a.length : b.length ).fill(0) 63 | for(let i = 0; i < child.length; i++) 64 | child[i] = Math.random() > 0.5 ? a[i%a.length] : b[i%b.length] 65 | return child 66 | } 67 | 68 | const arrayToRule = arr => { 69 | rules = Array( arr.length/keys.length ).fill().map( _ => { return {} } ) 70 | for(let i = 0; i < arr.length; i++) 71 | rules[Math.floor( i / keys.length )][keys[i % keys.length]] = arr[i] 72 | return rules 73 | } 74 | 75 | const refresh = () => { 76 | const c = createCanvasContext((innerWidth*0.7)/3,(innerHeight*0.98)/3) 77 | $('#area').appendChild( c.canvas ) 78 | const parentA = Math.floor(Math.random() * parents.length) 79 | let parentB = Math.floor(Math.random() * parents.length) 80 | while( parentB == parentA ) 81 | parentB = Math.floor(Math.random() * parents.length) 82 | const child = crossover( parents[parentA], parents[parentB] ) 83 | rules = arrayToRule( child ) 84 | iterate(10000,rules,c,60) 85 | return child 86 | } 87 | 88 | const iterate = (points, rules, c, zoom) => { 89 | let sum = 0 90 | for(let i = 0; i < rules.length; i++) 91 | sum += rules[i].w 92 | let x = Math.random() 93 | let y = Math.random() 94 | c.clearRect(-c.canvas.width/2,-c.canvas.height,c.canvas.width,c.canvas.height) 95 | for(let i = 0; i < points; i++){ 96 | const rule = getRule(sum) 97 | x = x * rule.a + y * rule.b + rule.tx 98 | y = x * rule.c + y * rule.d + rule.ty 99 | c.fillRect(x * zoom, -y * zoom, 1, 1) 100 | } 101 | } 102 | 103 | const getRule = sum => { 104 | let rand = Math.random() * sum; 105 | for(let i = 0; i < rules.length; i++){ 106 | const rule = rules[i] 107 | if(rand < rule.w) 108 | return rule 109 | rand -= rule.w 110 | } 111 | } 112 | 113 | const createPreview = arr => { 114 | const rules = arrayToRule( arr ) 115 | const c = createCanvasContext((innerWidth*0.7)/3*0.5,(innerHeight*0.98)/3*0.5,false) 116 | iterate(500,rules,c,15) 117 | return c 118 | } 119 | 120 | const showPreview = id => { 121 | const canvas = $c('canvas') 122 | canvas.width = innerWidth * 0.8 123 | canvas.height = innerHeight * 0.8 124 | const ctx = canvas.getContext('2d') 125 | ctx.translate(canvas.width/2, canvas.height) 126 | const rules = arrayToRule(parents[id]) 127 | 128 | const bg = $c('div') 129 | bg.id = "bg" 130 | const window = $c('div') 131 | window.id = "window" 132 | const toolbar = $c('div') 133 | toolbar.id = "toolbar" 134 | const close = $c('button') 135 | close.innerText = "close" 136 | close.onclick = () => bg.remove() 137 | const zoom = $c('input') 138 | zoom.type = "number" 139 | zoom.value = 100 140 | const zoomLabel = $c('label') 141 | zoomLabel.innerText = "zoom:" 142 | const points = $c('input') 143 | points.type = "number" 144 | points.value = "100000" 145 | const pointsLabel = $c('label') 146 | pointsLabel.innerText = "points:" 147 | const render = $c('button') 148 | render.innerText = "render" 149 | render.onclick = () => { 150 | ctx.clearRect( -canvas.width/2, -canvas.height, canvas.width, canvas.height ) 151 | iterate( points.value, rules, ctx, zoom.value ) 152 | } 153 | const png = $c('button') 154 | png.innerText = "PNG" 155 | png.onclick = () => { 156 | canvas.toBlob( blob => { 157 | const link = document.createElement('a') 158 | link.href = URL.createObjectURL(blob) 159 | link.download = 'fractal.png' 160 | link.click() 161 | }) 162 | } 163 | const json = $c('button') 164 | json.innerText = "JSON" 165 | json.onclick = () => { 166 | let blob = new Blob([JSON.stringify(rules)], {type: 'text/json'}) 167 | let link = $c('a') 168 | link.href = URL.createObjectURL(blob) 169 | link.download = 'fractal.json' 170 | link.click() 171 | } 172 | toolbar.appendChild( pointsLabel ) 173 | toolbar.appendChild( points ) 174 | toolbar.appendChild( zoomLabel ) 175 | toolbar.appendChild( zoom ) 176 | toolbar.appendChild( render ) 177 | toolbar.appendChild( png ) 178 | toolbar.appendChild( json ) 179 | toolbar.appendChild( close ) 180 | window.appendChild( toolbar ) 181 | window.appendChild( canvas ) 182 | iterate( 10000, rules, ctx, 100) 183 | bg.appendChild( window ) 184 | document.body.appendChild( bg ) 185 | } 186 | 187 | for(i = 0; i < parents.length; i++) 188 | $('#parents').appendChild(createPreview(parents[i]).canvas) 189 | 190 | for(let i = 0; i < 9; i++) 191 | population[i] = refresh() 192 | 193 | $('#evolve').onclick = e => { 194 | selected = Object.keys(selected) 195 | if(selected){ 196 | for(s of selected){ 197 | parents.push( population[s] ) 198 | $('#parents').appendChild(createPreview(population[s]).canvas) 199 | } 200 | } 201 | $('#area').innerHTML = "" 202 | id = 0 203 | for(let i = 0; i < 9; i++) 204 | population[i] = refresh() 205 | selected = {} 206 | } 207 | 208 | $('#save').onclick = e => { 209 | let blob = new Blob([JSON.stringify(parents)], {type: 'text/json'}) 210 | let link = document.createElement('a') 211 | link.href = URL.createObjectURL(blob) 212 | link.download = 'fractals.json' 213 | link.click() 214 | } 215 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |