├── LICENSE ├── README.md ├── algCL.js ├── algCL_params.js ├── algCS.js ├── algCS_params.js ├── algDots.js ├── algDots_params.js ├── algGP.js ├── algGP_params.js ├── algVD.js ├── algVD_params.js ├── algVines.js ├── algVines_params.js ├── index.html ├── index.js ├── readme_images ├── algCL.gif ├── algCS.gif ├── algDots.gif ├── algGP.gif ├── algVD.gif ├── algVines.gif └── web_interface.png ├── styles.css ├── thumbnails ├── algCL.png ├── algCS.png ├── algDots.png ├── algGP.png ├── algVD.png └── algVines.png └── video_preview ├── image1.png └── image2.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Algorithmically-Generated-Artwork 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 | # AlgoArt Creator Studio (Simplified Version) 2 | 3 | ## Introduction 4 | [AlgoArt.org](https://algoart.org/studio.html) is a digital platform that brings together Artists and Technologists to create algorithmically generated visual designs and artworks. This repository contains a simplified and light-weight version of our Creator Studio that you can use to develop your own drawing algorithms and generate your own unique artworks! 5 | 6 | *Try out our public demo @* https://algoart.org/studio.html 7 | 8 | ## Videos and Tutorials 9 | 10 | *Artwork Video* 11 | 12 | [![Artwork Video](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/blob/main/video_preview/image1.png)](https://www.youtube.com/watch?v=SGNbv__s0rI) 13 | 14 | *Talk & Tutorial* 15 | 16 | [![Talk & Tutorial Video](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/blob/main/video_preview/image2.png)](https://www.youtube.com/watch?v=0Ts7Sa_z69Y&t=46s) 17 | 18 | ## How Does It Work? 19 | 20 | **Drawing Algorithms:** We designed a simple framework for creating drawing algorithms. These algorithms, coded in JavaScript, are built from procedural processes that draw lines and basic shapes onto an [HTML Canvas](https://www.w3schools.com/html/html5_canvas.asp). Our example procedural processes were inspired by mathematical concepts, natural phenomena, and artistic trends. 21 | 22 | **Algorithm Structure:** Each algorithm has two JavaScript files. One file is for the drawing procedure and the other is for parameter configuration. The drawing procedure is declared as a dictionary object and includes functions called *start*, *pause*, *reset*, *initialize*, and *drawOneStep*. 23 | 24 | **Web Interface:** The web interface is used to run your drawing algorithms. Using the web interface, a user can start, pause, or reset a drawing algorithm. In addition, a user can also clear the canvas and save the current state of the digital canvas as an image file. 25 | 26 | ![Web Interface Screenshot](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/blob/main/readme_images/web_interface.png) 27 | 28 | ## Open Source Algorithms 29 | 30 | 31 | | Animation | Name | Author | Description | 32 | |-----------|------|--------|-------------| 33 | | ![algDots Animation](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/blob/main/readme_images/algDots.gif) | Dots (algDots) | Michael Wehar | This is a sample drawing algorithm that is meant for viewers to understand the programming framework that is used to create a new drawing algorithm. We encourage you to clone our repo and try modifying this code to build your own drawing algorithm. If you do so, **please consider sharing your resulting artworks with us** and [submit a pull request](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/pulls)! | 34 | | ![algGP Animation](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/blob/main/readme_images/algGP.gif) | [Geometric Patterns](https://michaelwehar.wordpress.com/2022/06/16/algorithmically-generated-visual-designs-geometric-patterns/) (algGP) | Michael Wehar | This drawing program takes in over 30 different parameters representing line length, line thickness, rotation angles, color variations, background color blending, line sparsity, and whether intersecting lines are allowed. Based on these parameters and a random number generator, the drawing program generates different kinds of repeated geometric patterns. All of the generated patterns are drawn line by line on a digital canvas. | 35 | | ![algVines Animation](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/blob/main/readme_images/algVines.gif) | [Vines](https://michaelwehar.wordpress.com/2022/07/23/algorithmically-generated-visual-designs-5-vines/) (algVines) | Alyssa Zhang | This algorithm, called "Vines", operates by drawing a variety of shapes across a canvas following certain kinds of paths. These paths that the vines follow are determined by various mathematical formulas. In particular, there are four kinds of paths: a random path, a linear path, a spiral path, and a path determined by a sine function. | 36 | | ![algCS Animation](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/blob/main/readme_images/algCS.gif) | [Constellations](https://michaelwehar.wordpress.com/2025/03/11/algoart-15-consellations/) (algCS) | Jhovani Gallardo Moreno | This algorithm, called "Constellations", reminds me of vivid memories of lying on the grass in the front yard, surrounded by my family, and looking up at the night sky. This was fresh in my mind so I decided to try to emulate constellations and create similar patterns. | 37 | | ![algVD Animation](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/blob/main/readme_images/algVD.gif) | [Voronoi](https://michaelwehar.wordpress.com/2025/03/12/algoart-16-voronoi/) (algVD) | Jhovani Gallardo Moreno | From a top-down view, a set of plant cells arranged together side-by-side appears like a tiling of a plane. After reading about different methods that can be used to create intriguing patterns / tilings, I discovered Voronoi diagrams which were originally conceptualized by the mathematician Georgy Voronoy. This is what inspired the drawing algorithm called "Voronoi". | 38 | | ![algCL Animation](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/blob/main/readme_images/algCL.gif) | [Collisions](https://michaelwehar.wordpress.com/2025/03/08/algoart-14-collisions/) (algCL) | Omar Khan | I used to play a lot of video games which involved projectiles, and it was up to the player to track their trajectory and move accordingly. I always thought it would be easier if the paths of the ball could be drawn and varied. This motivation led me to develop the Collisions drawing algorithm. | 39 | 40 | ## Extended List of Algorithms and Blog Posts 41 | 42 | - [Wrapping Paper](https://michaelwehar.wordpress.com/2022/06/14/algorithmically-generated-visual-designs-wrapping-paper/) by A. Zhang 43 | - [Geometric Patterns](https://michaelwehar.wordpress.com/2022/06/16/algorithmically-generated-visual-designs-geometric-patterns/) by M. Wehar 44 | - [Overlapping Tiles](https://michaelwehar.wordpress.com/2022/07/07/algorithmically-generated-visual-designs-3-overlapping-tiles/) by M. Wehar 45 | - [Spiderwebs](https://michaelwehar.wordpress.com/2022/07/10/algorithmically-generated-visual-designs-4-spiderwebs/) by M. Newman-Toker 46 | - [Vines](https://michaelwehar.wordpress.com/2022/07/23/algorithmically-generated-visual-designs-5-vines/) by A. Zhang 47 | - [Fractals](https://michaelwehar.wordpress.com/2022/08/10/algorithmically-generated-visual-designs-6-fractals/) by M. Newman-Toker and M. Wehar 48 | - [70's Funk](https://michaelwehar.wordpress.com/2022/08/10/algorithmically-generated-visual-designs-7-70s-funk/) by M. Wehar and A. Zhang 49 | - [Game of Life](https://michaelwehar.wordpress.com/2022/08/10/algorithmically-generated-visual-designs-8-game-of-life/) by J. Mancini and M. Wehar 50 | - [Trees](https://michaelwehar.wordpress.com/2022/08/19/algorithmically-generated-visual-designs-9-trees/) by M. Wehar 51 | - [Spirals](https://michaelwehar.wordpress.com/2024/08/02/algoart-10-spirals/) by L. Suresh 52 | - [Heat Map](https://michaelwehar.wordpress.com/2024/08/02/algoart-11-heat-map/) by V. Sumano 53 | - [Stickers](https://michaelwehar.wordpress.com/2024/10/22/algoart-12-stickers/) by M. Wehar 54 | - [Composition](https://michaelwehar.wordpress.com/2024/10/28/algoart-13-composition/) by X. Dong and M. Wehar 55 | - [Collisions](https://michaelwehar.wordpress.com/2025/03/08/algoart-14-collisions/) by O. Khan 56 | - [Constellations](https://michaelwehar.wordpress.com/2025/03/11/algoart-15-consellations/) by J. Gallardo Moreno 57 | - [Voronoi](https://michaelwehar.wordpress.com/2025/03/12/algoart-16-voronoi/) by J. Gallardo Moreno 58 | 59 | ## Publications 60 | 61 | *Academic Papers & Posters* 62 | 63 | - Creating Variation When Building Image Generation Datasets by J. Gallardo Moreno, O. Khan, and M. Wehar (To Appear, Bridges 2025) 64 | - [Students Teaching Students Computer Art and Graphics](https://diglib.eg.org/items/107b62b8-4c4f-4c17-af21-cac792069f21) by M. Wehar (Eurographics 2025) 65 | - [Creating Patterns with Distance Functions & Voronoi Diagrams](https://dl.acm.org/doi/10.1145/3722564.3728384) by J. Gallardo Moreno and M. Wehar (I3D 2025) 66 | 67 | *Art Exhibitions* 68 | 69 | - [Generative Clothing Designs (GCD) by AlgoArt (2025)](https://aydelotte.swarthmore.edu/publications/generative-clothing-designs-gcd-by-algoart/) 70 | - [AlgoArt @ Ludington Library (2023)](https://www.imaginary.org/event/algoart-exhibit-ludington-library) 71 | - [AlgoArt Exhibition (2023)](https://aydelotte.swarthmore.edu/publications/algoart-exhibition-at-mccabe-library/) 72 | 73 | *More Articles & Presentations* 74 | 75 | - [Generative Clothing Designs (GCD) by AlgoArt](https://gallery.bridgesmathart.org/exhibitions/2024-bridges-conference-fashion-show/michael-wehar) (Bridges 2024 - Fashion Show) 76 | - [AlgoArt - Nine Drawing Algorithms](https://gallery.bridgesmathart.org/exhibitions/2023-bridges-conference-short-film-festival/michael-wehar) (Bridges 2023 - Short Film Festival) 77 | - [Using Augmented Reality to Show How An Artwork Was Created](https://dac.siggraph.org/sparks/2023-01-spatiality-in-virtual-reality-representation-interpretation-experience/) (SIGGRAPH DAC: SPARKS TALK 2023) 78 | - Algorithmically Generated Visual Design (Lightning Talk @ CCSCNE 2023) 79 | - [Algorithmically Generated Artwork](https://dl.acm.org/doi/10.5555/3580523.3580581) (Poster & Abstract CCSC:EA 2022) 80 | - [A Visualization of Algorithmically Generated Artwork](https://www.youtube.com/watch?v=SGNbv__s0rI) (IFoRE 2022 - STEM Art and Film Festival) 81 | - [AI Generated Digital Painting from Start to Finish](https://www.aiplusinfo.com/blog/ai-generated-digital-painting-from-start-to-finish/) (Artificial Intelligence + Guest Blog) 82 | 83 | 84 | 85 | 86 | 87 | ## Contributors 88 | 89 | *Project Director* 90 | - Michael Wehar 91 | 92 | *Active Contributors* 93 | 94 | - Jhovani Gallardo Moreno 95 | - Omar Khan 96 | - Patrick Tone 97 | 98 | *Past Contributors* 99 | 100 | - Lalith Suresh 101 | - Victor Sumano 102 | - EK Brickner 103 | - Ryan Oet 104 | - Xingyu (Kevin) Dong 105 | - Xinxin Li 106 | - John Mancini 107 | - Maya Newman-Toker 108 | - Alyssa Zhang 109 | 110 | *Special thanks to all friends and collaborators who offered help!* 111 | 112 | ## Collaborations 113 | 114 | During Spring 2023, students from [CS410](https://cs410.net) @ UMass Boston forked our repository and contributed to our open source project adding functionality for 3D Drawing Algorithms. 115 | 116 | *Project Links* 117 | 118 | - Forked Repository: https://github.com/devnamedjean/Drawing-Program 119 | - Public Demo: https://devnamedjean.github.io/Drawing-Program/ 120 | 121 | *Student Developers* 122 | 123 | - Jake Breen 124 | - Jean Gerard 125 | - Prateeksha Bhojaraj 126 | - Richard Chang 127 | 128 | *Special thanks D. Haehn, S. Raikar, and to all of those who offered help!* 129 | 130 | ## Licenses and Libraries 131 | 132 | The code in this repository is made available under MIT License. 133 | 134 | This platform was developed using the following libraries which are available under MIT License. 135 | - [JQuery](https://github.com/jquery/jquery) 136 | - [Bootstrap](https://github.com/twbs/bootstrap) 137 | - [Bootstrap Icons](https://github.com/twbs/icons) 138 | 139 | ## Contact Us 140 | 141 | We are excited to start collaborations and build new partnerships! 142 | 143 | Send an email to [Michael Wehar](http://michaelwehar.com) or [submit a pull request](https://github.com/Algorithmically-Generated-Artwork/Drawing-Program/pulls). 144 | -------------------------------------------------------------------------------- /algCL.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Breakdown of idea: 4 | 5 | simulate a ball colliding with walls and bouncing around 6 | change colors every collision 7 | uniform size ball (change size parameter potentially) 8 | random location on grid for spawn 9 | random direction 10 | uniform velocity (provide range parameter potentially) 11 | 12 | */ 13 | 14 | class Ball { 15 | dx = 0; 16 | dy = 0; 17 | gradientColorOne = ""; 18 | gradientColorTwo = ""; 19 | peakortrough = true; 20 | circleColors = algCL.circleColors; 21 | 22 | colorstep = 0; 23 | 24 | constructor(x, y, radius, color, alpha, velocity, angle){ 25 | this.x = x; 26 | this.y = y; 27 | this.circleRadius = radius; 28 | this.currentSingleColor = color; 29 | this.circleAlpha = alpha; 30 | this.velocity = velocity; 31 | this.angle = angle; 32 | 33 | } 34 | 35 | } 36 | 37 | 38 | var algCL = {}; 39 | 40 | algCL.drawCircle = function(x, y, radius, color, alpha) { 41 | ctx.globalAlpha = alpha; 42 | ctx.beginPath(); 43 | ctx.arc(x, y, radius, 0, 2 * Math.PI); 44 | 45 | if (algCL.fillCircle){ 46 | ctx.fillStyle = color; 47 | ctx.fill(); 48 | } 49 | else{ 50 | ctx.strokeStyle = color; 51 | ctx.lineWidth = 2; 52 | ctx.stroke(); 53 | } 54 | } 55 | 56 | algCL.drawOneStep = function () { 57 | if(algCL.NumOfStepsDone >= algCL.maxNumOfSteps) { 58 | clearInterval(algCL.loop); 59 | return false; 60 | } else { 61 | 62 | if(algCL.noTrail){ 63 | ctx.clearRect(0, 0, 600, 600); 64 | ctx.globalAlpha = 1; 65 | ctx.fillStyle = algCL.backgroundColor; 66 | ctx.fillRect(0, 0, 600, 600); 67 | } 68 | for (let i = 0; i= 600 + ball.circleRadius || ball.x + ball.dx <= 0 - ball.circleRadius){ 88 | if (algCL.wrapAroundStyle != "y"){ 89 | algCL.wrapAround(ball, "x", ball.dx); 90 | } 91 | } 92 | else if (ball.y + ball.dy >= 600 + ball.circleRadius || ball.y + ball.dy <= 0 - ball.circleRadius) { 93 | // wrap around or correct position to be within bound 94 | if (algCL.wrapAroundStyle != "x"){ 95 | algCL.wrapAround(ball, "y", ball.dy); 96 | } 97 | } 98 | else { 99 | if(algCL.ballsCollide){ 100 | algCL.checkBallCollisions(); 101 | } 102 | if (algCL.wrapAroundStyle != "x"){ 103 | ball.y += ball.dy; 104 | } 105 | if (algCL.wrapAroundStyle != "y"){ 106 | ball.x += ball.dx; 107 | } 108 | } 109 | } 110 | 111 | 112 | 113 | if (!algCL.wrapAroundWalls){ 114 | //check for collision, see if edges of ball will hit the walls 115 | if (ball.x + ball.dx >= 600 - ball.circleRadius || ball.x + ball.dx <= ball.circleRadius) { 116 | algCL.rebound(ball, "x"); 117 | // Correct position to be within bounds 118 | if (ball.x + ball.dx >= 600 - ball.circleRadius) { 119 | ball.x = 600 - ball.circleRadius; 120 | } else { 121 | ball.x = ball.circleRadius; 122 | } 123 | if(algCL.velocityChangesOnCollision){ 124 | // console.log("changing velocity"); 125 | if(algCL.speedUpOnCollision){ 126 | ball.velocity+=algCL.velocityIncrement; 127 | // console.log("velocity increase"); 128 | } 129 | else{ 130 | ball.velocity-=algCL.velocityIncrement; 131 | // console.log("velocity decreasing"); 132 | } 133 | } 134 | 135 | } else { 136 | //check for ball-ball collisions 137 | if(algCL.ballsCollide){ 138 | algCL.checkBallCollisions(); 139 | } 140 | ball.x += ball.dx; // Update x position only if no collision 141 | } 142 | 143 | if (ball.y + ball.dy >= 600 - ball.circleRadius || ball.y + ball.dy <= ball.circleRadius) { 144 | 145 | // wrap around or correct position to be within bounds 146 | algCL.rebound(ball, "y"); 147 | if (ball.y + ball.dy >= 600 - ball.circleRadius) { 148 | ball.y = 600 - ball.circleRadius; 149 | } else { 150 | ball.y = ball.circleRadius; 151 | } 152 | if(algCL.velocityChangesOnCollision){ 153 | // console.log("changing velocity"); 154 | if(algCL.speedUpOnCollision){ 155 | ball.velocity+=algCL.velocityIncrement; 156 | // console.log("velocity increase"); 157 | } 158 | else{ 159 | ball.velocity-=algCL.velocityIncrement; 160 | // console.log("velocity decreasing"); 161 | } 162 | } 163 | } 164 | else { 165 | //check for ball-ball collisions 166 | if(algCL.ballsCollide){ 167 | algCL.checkBallCollisions(); 168 | } 169 | ball.y += ball.dy; // Update y position only if no collision 170 | } 171 | } 172 | 173 | if (algCL.gradientMethod == "setlist" || algCL.gradientMethod == "inverse"){ 174 | algCL.colorGradient(ball); 175 | } 176 | 177 | // console.log(ball.currentSingleColor); 178 | 179 | ball.colorstep++; 180 | algCL.drawCircle(ball.x, ball.y, ball.circleRadius, ball.currentSingleColor, ball.circleAlpha); 181 | 182 | //randomly change direction when threshold reached 183 | if (algCL.randomChange){ 184 | if (algCL.NumOfStepsDone == algCL.randomChangeSteps){ 185 | ball.angle = 2 * Math.PI - ball.angle; 186 | } 187 | } 188 | // // console.log(algCL.NumOfStepsDone); 189 | // // console.log(algCL.velocity); 190 | // // console.log("dy:" + algCL.dy); 191 | 192 | } 193 | algCL.NumOfStepsDone++; 194 | } 195 | } 196 | 197 | 198 | algCL.combineOnCollision = function(ball1, ball2) { 199 | 200 | let momentum1 = ball1.circleRadius * ball1.velocity; 201 | let momentum2 = ball2.circleRadius * ball2.velocity; 202 | 203 | //make mass ~ radius 204 | //use momentum as a weight for net forces 205 | 206 | let totalMomentum = momentum1 + momentum2; 207 | 208 | //use momentum as a weighting and average the x and y velocities 209 | 210 | let weightAvgVelX = ((momentum1 * ball1.dx) + (momentum2 * ball2.dx)) / totalMomentum; 211 | let weightAvgVelY = ((momentum1 * ball1.dy) + (momentum2 * ball2.dy)) / totalMomentum; 212 | 213 | let combX = (ball1.x + ball2.x) / 2 214 | let combY = (ball1.y + ball2.y) / 2 215 | 216 | let combR; 217 | 218 | combR = ball1.circleRadius + ball2.circleRadius; 219 | if (combR >= 300){ 220 | combR = Math.max(ball1.circleRadius, ball2.circleRadius) 221 | } 222 | 223 | 224 | 225 | let newVel = Math.sqrt(weightAvgVelX ** 2 + weightAvgVelY ** 2); 226 | let newAngle = Math.atan2(weightAvgVelY, weightAvgVelX); 227 | 228 | let newColor = algCL.rgbToString( 229 | algCL.weightedColorAvg(0.5, ball1.gradientColorOne, ball2.gradientColorTwo) 230 | ); 231 | 232 | let newAlph; 233 | if (algCL.combineAlphaMode == "change"){ 234 | newAlph = (ball1.circleAlpha + ball2.circleAlpha) * algCL.combineAlphaChangeFactor; 235 | } 236 | else if (algCL.combineAlphaMode == "sum"){ 237 | newAlph = ball1.circleAlpha + ball2.circleAlpha; 238 | } 239 | else if (algCL.combineAlphaMode == "min"){ 240 | newAlph = Math.min(ball1.circleAlpha, ball2.circleAlpha); 241 | } 242 | else{ 243 | newAlph = Math.max(ball1.circleAlpha, ball2.circleAlpha); 244 | } 245 | 246 | let newBall = new Ball(combX, combY, combR, newColor, newAlph, newVel, newAngle); 247 | newBall.gradientColorOne = ball1.gradientColorOne; 248 | newBall.gradientColorTwo = ball2.gradientColorTwo; 249 | 250 | let index1 = algCL.ballArray.indexOf(ball1); 251 | let index2 = algCL.ballArray.indexOf(ball2); 252 | if (index1 !== -1) algCL.ballArray.splice(index1, 1); 253 | if (index2 !== -1) algCL.ballArray.splice(index2 - (index1 < index2 ? 1 : 0), 1); 254 | 255 | algCL.ballArray.push(newBall); 256 | 257 | //let ball = new Ball(x, y, radius, algCL.rgbToString(gradientColorOne), 1, velocity, angle); 258 | 259 | } 260 | 261 | //Ball-Ball Collisions 262 | // Check through array for adist_jhat balls that will be colliding 263 | //use dist to see if the distance between adist_jhat is less than their radiuses combined 264 | //reverse angles, offset x and y. 265 | //swap colors? 266 | 267 | algCL.checkBallCollisions = function() { 268 | const overlapScaler = 0.8; // Scales how much balls get pushed away after colliding 269 | 270 | for (let i = algCL.ballArray.length - 1; i >= 0; i--) { 271 | let ball1 = algCL.ballArray[i]; 272 | if (!ball1) continue; // Skip if ball1 is undefined 273 | 274 | for (let j = i - 1; j >= 0; j--) { 275 | let ball2 = algCL.ballArray[j]; 276 | if (!ball2) continue; // Skip if ball2 is undefined 277 | 278 | // Calculate distance between balls 279 | let dx = ball1.x - ball2.x; 280 | let dy = ball1.y - ball2.y; 281 | let dist = Math.sqrt(dx * dx + dy * dy); 282 | let radiusSum = ball1.circleRadius + ball2.circleRadius; 283 | 284 | if (dist < radiusSum) { 285 | // Check if balls should combine 286 | if (algCL.handleCollision === "combine") { 287 | algCL.combineOnCollision(ball1, ball2); 288 | return; 289 | } 290 | 291 | // Check if balls should disappear on collision 292 | else if (algCL.handleCollision === "disappear") { 293 | // console.log("removing 2 balls!"); 294 | algCL.ballArray.splice(i, 1); 295 | algCL.ballArray.splice(j, 1); 296 | break; // Exit inner loop 297 | } 298 | 299 | // Check if balls should change radii 300 | else if (algCL.handleCollision === "change radii") { 301 | ball1.circleRadius *= algCL.ballRadiiCollisionFactor; 302 | ball2.circleRadius *= algCL.ballRadiiCollisionFactor; 303 | } 304 | 305 | // Calculate overlap correction 306 | let overlap = radiusSum - dist; 307 | let dist_ihat = dx / dist; 308 | let dist_jhat = dy / dist; 309 | 310 | // Adjust positions to prevent overlap 311 | ball1.x += overlap * overlapScaler * dist_ihat; 312 | ball1.y += overlap * overlapScaler * dist_jhat; 313 | ball2.x -= overlap * overlapScaler * dist_ihat; 314 | ball2.y -= overlap * overlapScaler * dist_jhat; 315 | 316 | // Call ballRebound 317 | algCL.ballRebound(ball1, ball2, dist_ihat, dist_jhat); 318 | } 319 | } 320 | } 321 | }; 322 | 323 | 324 | 325 | 326 | algCL.ballRebound = function(ball1, ball2, dist_ihat, dist_jhat){ 327 | 328 | 329 | //use the directional unit components to get normal components of 330 | //the balls' velocity (normal along the collision line) 331 | let ball1ogv = ball1.velocity; 332 | let ball2ogv = ball2.velocity; 333 | let ball1oga = ball1.angle; 334 | let ball2oga = ball2.angle; 335 | 336 | let ball1_normvel = ball1.dx * dist_ihat + ball1.dy * dist_jhat; 337 | let ball2_normvel = ball2.dx * dist_ihat + ball2.dy * dist_jhat; 338 | 339 | //tangential components of velocity 340 | let ball1_tanvel = -ball1.dx * dist_jhat + ball1.dy * dist_ihat; 341 | let ball2_tanvel = -ball2.dx * dist_jhat + ball2.dy * dist_ihat; 342 | 343 | // swap the values 344 | [ball1_normvel, ball2_normvel] = [ball2_normvel, ball1_normvel]; 345 | 346 | // Get actual dx and dy 347 | ball1.dx = ball1_normvel * dist_ihat - ball1_tanvel * dist_jhat; 348 | ball1.dy = ball1_normvel * dist_jhat + ball1_tanvel * dist_ihat; 349 | ball2.dx = ball2_normvel * dist_ihat - ball2_tanvel * dist_jhat; 350 | ball2.dy = ball2_normvel * dist_jhat + ball2_tanvel * dist_ihat; 351 | 352 | ball1.velocity = Math.sqrt(ball1.dx ** 2 + ball1.dy ** 2); 353 | ball2.velocity = Math.sqrt(ball2.dx ** 2 + ball2.dy ** 2); 354 | ball1.angle = Math.atan2(ball1.dy, ball1.dx); 355 | ball2.angle = Math.atan2(ball2.dy, ball2.dx); 356 | 357 | if (ball1.velocity == 0 && ball2.velocity == 0){ 358 | ball1.velocity = ball1ogv; 359 | ball2.velocity = ball2ogv; 360 | ball1.angle = ball1oga; 361 | ball2.angle = ball2oga; 362 | } 363 | 364 | } 365 | 366 | 367 | 368 | algCL.wrapAround = function(ball, direction, vel){ 369 | 370 | if (direction == "x"){ 371 | if (ball.x + vel >= 600 - ball.circleRadius){ 372 | ball.x = 1 - ball.circleRadius; 373 | } 374 | else if (ball.x + vel <= ball.circleRadius){ 375 | ball.x = 599 + ball.circleRadius; 376 | } 377 | else{ 378 | return; 379 | } 380 | if (algCL.wrapAroundRandom){ 381 | ball.y = Math.floor(Math.random() * (600 - 2 * ball.circleRadius + 1)) + ball.circleRadius; 382 | } 383 | } 384 | 385 | else if (direction == "y"){ 386 | if (ball.y + vel >= 600 - ball.circleRadius){ 387 | ball.y = 1 - ball.circleRadius; 388 | } 389 | else if (ball.y + vel <= ball.circleRadius){ 390 | ball.y = 599 + ball.circleRadius; 391 | } 392 | else{ 393 | return; 394 | } 395 | if (ball.wrapAroundRandom){ 396 | ball.x = Math.floor(Math.random() * (600 - 2*ball.circleRadius + 1)) + ball.circleRadius; 397 | } 398 | } 399 | 400 | if(algCL.gradientMethod == "inverse" || algCL.gradientMethod == "setlist"){ 401 | algCL.collisionGradient(ball) 402 | } 403 | else{ 404 | let newColors = ball.circleColors.filter(color => color!= ball.currentSingleColor); 405 | ball.currentSingleColor = algCL.rgbToString(newColors[Math.floor(Math.random() * newColors.length)]); 406 | } 407 | 408 | } 409 | 410 | 411 | algCL.rebound = function(ball, direction) { 412 | 413 | // Introduce some randomness into angle, so it doesn't just 414 | // repeat the same pattern 415 | 416 | // console.log(algCL.angle); 417 | if (direction == "x"){ 418 | ball.angle = Math.PI - ball.angle; 419 | // algCL.dx = -algCL.dx; 420 | // console.log("x collision"); 421 | } 422 | else if (direction == "y"){ 423 | ball.angle = -ball.angle; 424 | // algCL.dy = -algCL.dy; 425 | // console.log("y collision"); 426 | } 427 | //randomness intro: 428 | if(algCL.collisionAngleRandomness){ 429 | ball.angle += (Math.random() * algCL.angleRandomnessScaler); // Increase random adjustment 430 | ball.angle %= 2 * Math.PI; 431 | } 432 | 433 | 434 | if(algCL.gradientMethod == "gradient" || algCL.gradientMethod == "setlist"){ 435 | algCL.collisionGradient(ball) 436 | } 437 | else{ 438 | // console.log("changing color!"); 439 | // console.log(ball.currentSingleColor); 440 | let newColors = ball.circleColors.filter(color => color!= ball.currentSingleColor); 441 | ball.currentSingleColor = algCL.rgbToString(newColors[Math.floor(Math.random() * newColors.length)]); 442 | // console.log(ball.currentSingleColor); 443 | } 444 | 445 | if (algCL.ballsDivideOnWallCollision && ball.circleRadius > 1) { 446 | // console.log("Ball splitting!"); 447 | let newRadius = ball.circleRadius / 2; // halve og radius for new balls. 448 | let offset = newRadius / 2; 449 | 450 | 451 | let ball1 = new Ball( 452 | ball.x + offset * Math.cos(ball.angle + Math.PI / 6), 453 | ball.y + offset * Math.sin(ball.angle + Math.PI / 6), 454 | newRadius, 455 | ball.currentSingleColor, 456 | ball.circleAlpha, 457 | ball.velocity, 458 | ball.angle + Math.PI / 6 459 | ); 460 | ball1.gradientColorOne = ball.gradientColorOne; 461 | ball1.gradientColorTwo = ball.gradientColorTwo; 462 | 463 | let ball2 = new Ball( 464 | ball.x + offset * Math.cos(ball.angle - Math.PI / 6), 465 | ball.y + offset * Math.sin(ball.angle - Math.PI / 6), 466 | newRadius, 467 | ball.currentSingleColor, 468 | ball.circleAlpha, 469 | ball.velocity, 470 | ball.angle - Math.PI / 6 471 | ); 472 | 473 | ball2.gradientColorOne = ball.gradientColorOne; 474 | ball2.gradientColorTwo = ball.gradientColorTwo; 475 | 476 | algCL.ballArray.push(ball1); 477 | algCL.ballArray.push(ball2); 478 | 479 | let index = algCL.ballArray.indexOf(ball); 480 | if (index !== -1) algCL.ballArray.splice(index, 1); 481 | } 482 | }; 483 | 484 | 485 | algCL.collisionGradient = function(ball){ 486 | 487 | if (algCL.gradientMethod == "setlist"){ 488 | 489 | ball.gradientColorOne = ball.gradientColorTwo; 490 | // console.log("test1:", ball.gradientColorOne); 491 | let newColors = ball.circleColors.filter(color => color!= ball.gradientColorOne); 492 | ball.gradientColorTwo = algCL.rgbToString(newColors[Math.floor(Math.random() * newColors.length)]); 493 | ball.currentSingleColor = algCL.rgbToString(ball.gradientColorOne); // Set to gradient color one initially 494 | 495 | } 496 | if (algCL.gradientMethod == "inverse"){ 497 | 498 | ball.gradientColorOne = (algCL.randomColor()); 499 | ball.gradientColorTwo = algCL.invertColor(ball.gradientColorOne); 500 | ball.currentSingleColor = algCL.rgbToString(ball.gradientColorOne); 501 | } 502 | } 503 | 504 | 505 | algCL.randomColor = function(){ 506 | let r = Math.floor(256 * Math.random()); 507 | let g = Math.floor(256 * Math.random()); 508 | let b = Math.floor(256 * Math.random()); 509 | return [r, g, b]; 510 | 511 | } 512 | 513 | 514 | algCL.invertColor = function([r,g,b]){ 515 | let r2 = 255-r; 516 | let g2 = 255-g; 517 | let b2 = 255-b; 518 | return [r2, g2, b2]; 519 | } 520 | 521 | algCL.rgbToString = function(rgb) { 522 | return 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')'; 523 | } 524 | 525 | 526 | algCL.colorGradient = function(ball) { 527 | let step = ball.colorstep % algCL.colorCycleLength; 528 | let weight = Math.abs(2 * step / algCL.colorCycleLength - 1); 529 | // console.log(weight); 530 | let rgb = algCL.weightedColorAvg(weight, ball.gradientColorOne, ball.gradientColorTwo); 531 | // console.log(rgb); 532 | ball.currentSingleColor = algCL.rgbToString(rgb); 533 | } 534 | 535 | algCL.weightedColorAvg = function(weight, color1, color2){ 536 | 537 | 538 | if (typeof color1 === "string") { 539 | color1 = color1.slice(4, -1).split(","); 540 | color1 = [parseInt(color1[0]), parseInt(color1[1]), parseInt(color1[2])]; 541 | } 542 | if (typeof color2 === "string") { 543 | color2 = color2.slice(4, -1).split(","); 544 | color2 = [parseInt(color2[0]), parseInt(color2[1]), parseInt(color2[2])]; 545 | } 546 | 547 | // console.log("gradc1:", color1); 548 | // console.log("gradc2:", color2); 549 | 550 | let r1 = color1[0]; 551 | let g1 = color1[1]; 552 | let b1 = color1[2]; 553 | 554 | let r2 = color2[0]; 555 | let g2 = color2[1]; 556 | let b2 = color2[2]; 557 | 558 | let r = weight * r1 + (1 - weight) * r2; 559 | let g = weight * g1 + (1 - weight) * g2; 560 | let b = weight * b1 + (1 - weight) * b2; 561 | let rgb = [Math.floor(r), Math.floor(g), Math.floor(b)]; 562 | // console.log("weighted avg: ", rgb); 563 | return rgb; 564 | } 565 | 566 | algCL.reset = function () { 567 | // Check if already running 568 | algCL.pause(); 569 | // Initialize values 570 | 571 | algCL.ballArray = []; 572 | for (i=0; i< algCL.numBalls; i++ ){ 573 | let x = Math.floor(Math.random() * (600 - 200)) + 100; 574 | let y = Math.floor(Math.random() * (600 - 200)) + 100; 575 | 576 | let angle = Math.random() * 2 * Math.PI; 577 | 578 | let velocity = algCL.minimumVelocity + Math.random() * (algCL.maximumVelocity - algCL.minimumVelocity);; 579 | let radius = algCL.minimumRadius + Math.random() * (algCL.maximumRadius - algCL.minimumRadius); 580 | let alpha = algCL.minimumAlpha + Math.random() * (algCL.maximumAlpha - algCL.minimumAlpha); 581 | 582 | if (algCL.paletteSelect == "default") { 583 | algCL.circleColors = algCL.palettes[0]; 584 | } 585 | else if (algCL.paletteSelect == "pastel") { 586 | algCL.circleColors = algCL.palettes[1]; 587 | } 588 | else if (algCL.paletteSelect == "earth") { 589 | algCL.circleColors = algCL.palettes[2]; 590 | } 591 | else if (algCL.paletteSelect == "cool") { 592 | algCL.circleColors = algCL.palettes[3]; 593 | } 594 | else if (algCL.paletteSelect == "warm") { 595 | algCL.circleColors = algCL.palettes[4]; 596 | } 597 | else if (algCL.paletteSelect == "regal") { 598 | algCL.circleColors = algCL.palettes[5]; 599 | } 600 | else if (algCL.paletteSelect == "greyscale"){ 601 | algCL.circleColors = algCL.palettes[6]; 602 | } 603 | 604 | if (algCL.gradientMethod == "setlist"){ 605 | gradientColorOne = algCL.rgbToString(algCL.circleColors[Math.floor(Math.random() * algCL.circleColors.length)]); 606 | let newColors = algCL.circleColors.filter(color => color != gradientColorOne); 607 | gradientColorTwo = algCL.rgbToString(newColors[Math.floor(Math.random() * newColors.length)]); 608 | currentSingleColor = gradientColorOne; 609 | } 610 | if (algCL.gradientMethod == "inverse"){ 611 | gradientColorOne = algCL.randomColor(); 612 | gradientColorTwo = algCL.invertColor(gradientColorOne); 613 | currentSingleColor = gradientColorOne; 614 | } 615 | else { 616 | currentSingleColor = algCL.rgbToString(algCL.circleColors[Math.floor(Math.random() * algCL.circleColors.length)]); 617 | gradientColorOne = currentSingleColor; 618 | gradientColorTwo = currentSingleColor; 619 | } 620 | 621 | let ball = new Ball(x, y, radius, currentSingleColor, alpha, velocity, angle); 622 | ball.gradientColorOne = gradientColorOne; 623 | ball.gradientColorTwo = gradientColorTwo; 624 | ball.colorstep = 1; 625 | 626 | algCL.ballArray.push(ball); 627 | } 628 | 629 | ctx.clearRect(0,0,600,600) 630 | 631 | algCL.NumOfStepsDone = 0; 632 | algCL.collisionDampener = 0.8; 633 | ctx.globalAlpha = 1; 634 | ctx.lineWidth = 2; 635 | 636 | } 637 | 638 | algCL.initialize = function() { 639 | algCL.reset(); 640 | } 641 | 642 | algCL.pause = function () { 643 | if("loop" in algCL) { 644 | clearInterval(algCL.loop); 645 | } 646 | } 647 | 648 | algCL.start = function () { 649 | //draw initial circle at starting spot, then begin the loop, calculating trajectory 650 | algCL.initialize(); 651 | ctx.globalAlpha = 1; 652 | ctx.fillStyle = algCL.backgroundColor; 653 | ctx.fillRect(0, 0, 600, 600); 654 | algCL.loop = setInterval(algCL.drawOneStep, algCL.speed); 655 | } 656 | -------------------------------------------------------------------------------- /algCL_params.js: -------------------------------------------------------------------------------- 1 | /* Parameters */ 2 | algCL.speed = 0; 3 | algCL.maxNumOfSteps = 1000; 4 | algCL.NumOfStepsDone = 0; 5 | 6 | algCL.backgroundColor = "white"; 7 | 8 | algCL.palettes = [ 9 | // Default 10 | [ 11 | [255, 0, 0], // red 12 | [0, 255, 0], // green 13 | [0, 0, 255], // blue 14 | [255, 192, 203], // pink 15 | [0, 255, 255], // cyan 16 | [255, 255, 0], // yellow 17 | [255, 0, 255], // magenta 18 | [255, 165, 0], // orange 19 | [255, 0, 255], // fuchsia 20 | [64, 224, 208] // turquoise 21 | ], 22 | 23 | // Pastel 24 | [ 25 | [255, 182, 193], // light pink 26 | [173, 216, 230], // light blue 27 | [144, 238, 144], // light green 28 | [255, 240, 245], // lavender blush 29 | [255, 228, 225], // misty rose 30 | [255, 239, 185], // peach 31 | [250, 240, 190], // light goldenrod yellow 32 | [255, 240, 245], // seashell 33 | [216, 191, 216], // thistle 34 | [255, 218, 185] // moccasin 35 | ], 36 | 37 | // Earth Tone 38 | [ 39 | [139, 69, 19], // saddle brown 40 | [160, 82, 45], // sienna 41 | [107, 142, 35], // olive drab 42 | [85, 107, 47], // dark olive green 43 | [222, 184, 135], // burlywood 44 | [139, 137, 112], // taupe 45 | [205, 133, 63], // peru 46 | [169, 169, 169], // dark gray 47 | [160, 160, 160], // gray 48 | [102, 51, 0] // umber 49 | ], 50 | 51 | // Cool Tones 52 | [ 53 | [0, 128, 255], // sky blue 54 | [0, 255, 255], // cyan 55 | [0, 102, 204], // deep blue 56 | [75, 0, 130], // indigo 57 | [138, 43, 226], // blue violet 58 | [72, 61, 139], // dark slate blue 59 | [0, 206, 209], // dark turquoise 60 | [176, 224, 230], // powder blue 61 | [0, 255, 127], // spring green 62 | [32, 178, 170] // light sea green 63 | ], 64 | 65 | // Warm Tones 66 | [ 67 | [255, 69, 0], // red-orange 68 | [255, 140, 0], // dark orange 69 | [255, 165, 0], // orange 70 | [255, 215, 0], // gold 71 | [218, 165, 32], // goldenrod 72 | [205, 92, 92], // indian red 73 | [178, 34, 34], // firebrick 74 | [139, 0, 0], // dark red 75 | [255, 99, 71], // tomato 76 | [240, 128, 128] // light coral 77 | ], 78 | 79 | // Regal 80 | [ 81 | [102, 0, 51], // deep burgundy 82 | [255, 215, 0], // gold 83 | [75, 0, 130], // indigo 84 | [47, 79, 79], // dark slate 85 | [220, 20, 60], // crimson 86 | [184, 134, 11], // antique bronze 87 | [0, 51, 102], // navy blue 88 | [139, 69, 19], // rich brown 89 | [128, 0, 0], // deep red 90 | [160, 82, 45] // mahogany 91 | ], 92 | 93 | 94 | // Grayscale 95 | [ 96 | [0, 0, 0], // black 97 | [32, 32, 32], // very dark gray 98 | [64, 64, 64], // dark gray 99 | [96, 96, 96], // gray 100 | [128, 128, 128], // medium gray 101 | [160, 160, 160], // light gray 102 | [192, 192, 192], // very light gray 103 | [224, 224, 224], // near white 104 | [245, 245, 245], // off white 105 | [255, 255, 255] // pure white 106 | ] 107 | ]; 108 | 109 | algCL.paletteSelect = "default"; //default, pastel, earth, warm, cool, regal, greyscale 110 | algCL.circleColors = algCL.palettes[0]; 111 | 112 | 113 | algCL.numBalls = 10; 114 | algCL.ballArray = []; 115 | 116 | algCL.fillCircle = true; 117 | 118 | algCL.minimumAlpha = 0.2; 119 | algCL.maximumAlpha = 1; 120 | algCL.minimumVelocity = 1; 121 | algCL.maximumVelocity = 10; 122 | algCL.minimumRadius = 25; 123 | algCL.maximumRadius = 50; 124 | 125 | algCL.gradientMethod = "none"; //none, inverse, setlist 126 | 127 | // algCL.gradientOrSolid = false; 128 | // algCL.gradientInverse = false; 129 | // algCL.gradientSetList = false; 130 | algCL.colorCycleLength = 50; 131 | 132 | algCL.wrapAroundWalls = false; 133 | algCL.wrapAroundStyle = "both"; //both, x, y 134 | algCL.wrapAroundRandom = false; 135 | 136 | algCL.randomChangeSteps = 50; 137 | algCL.velocityChangesOnCollision = false; 138 | algCL.speedUpOnCollision = false; 139 | algCL.velocityIncrement = 1; 140 | 141 | 142 | algCL.ballsCollide = false; 143 | algCL.handleCollision = "combine" //none, combine, disappear, change radii 144 | 145 | algCL.ballsDivideOnWallCollision = false; 146 | 147 | algCL.ballRadiiCollisionFactor = 0.8; 148 | algCL.combineAlphaMode = "change"; //change (sum and multiply), sum, min, max for alpha 149 | algCL.combineAlphaChangeFactor = 0.1; 150 | 151 | 152 | algCL.collisionAngleRandomness = false; 153 | algCL.angleRandomnessScaler = 0.1; 154 | algCL.randomBounce = false; 155 | 156 | algCL.sinusoidal = false; 157 | algCL.sinusoidalCycle = 5; 158 | algCL.sinusoidalAngle = Math.PI / 8; 159 | algCL.noTrail = false; 160 | -------------------------------------------------------------------------------- /algCS.js: -------------------------------------------------------------------------------- 1 | var algCS = {}; 2 | algCS.pallettes = { 3 | warm: [ 4 | "#f1c40f", 5 | "#f39c12", 6 | "#e67e22", 7 | "#d35400", 8 | "#e74c3c", 9 | "#c0392b", 10 | ], 11 | cool: [ 12 | "#52be80", 13 | "#45b39d", 14 | "#48c9b0", 15 | "#5dade2", 16 | "#5499c7", 17 | "#a569bd", 18 | ], 19 | reds: [ 20 | "#922b21", 21 | "#a93226", 22 | "#cb4335", 23 | "#c0392b", 24 | "#cd6155", 25 | "#ec7063", 26 | ], 27 | oranges: [ 28 | "#f39c12", 29 | "#e67e22", 30 | "#d35400", 31 | "#f5b041", 32 | "#eb984e", 33 | "#dc7633", 34 | ], 35 | yellows: [ 36 | "#b7950b", 37 | "#d4ac0d", 38 | "#f1c40f", 39 | "#f4d03f", 40 | "#f7dc6f", 41 | "#f9e79f", 42 | ], 43 | greens: [ 44 | "#1e8449", 45 | "#239b56", 46 | "#7dcea0", 47 | "#82e0aa", 48 | "#52be80", 49 | "#58d68d", 50 | ], 51 | blues: [ 52 | "#039be5", 53 | "#4fc3f7", 54 | "#1e88e5", 55 | "#64b5f6", 56 | "#3949ab", 57 | "#5c6bc0", 58 | ], 59 | slate: [ 60 | "#273746", 61 | "#2e4053", 62 | "#34495e", 63 | "#2c3e50", 64 | "#566573", 65 | "#5d6d7e", 66 | ], 67 | purples: [ 68 | "#6c3483", 69 | "#7d3c98", 70 | "#8e44ad", 71 | "#a569bd", 72 | "#bb8fce", 73 | "#d2b4de", 74 | ], 75 | greyscale: [ 76 | "#7b7d7d", 77 | "#979a9a", 78 | "#b3b6b7", 79 | "#f0f3f4", 80 | "#fdfefe", 81 | ], 82 | } 83 | 84 | algCS.backgroundColors = ["#051029", "#1E2B58", "#253569", "#353283", "#48459a"]; 85 | 86 | algCS.lightBackgroundColors = ["#cfecf7", "#a0d9ef", "#62c1e5"] 87 | 88 | // generates random hex color 89 | algCS.randomHexColor = function() { 90 | let hex = ["#"]; 91 | for (let i = 0; i < 6; i++) { 92 | hex.push(Math.floor(Math.random()*16).toString(16)); 93 | } 94 | return hex.join(""); 95 | } 96 | 97 | // sets background color 98 | algCS.setBackgroundColor = function(color) { 99 | ctx.beginPath(); 100 | ctx.fillStyle = color; 101 | ctx.globalAlpha = 1; 102 | ctx.rect(0, 0, width, height); 103 | ctx.fill(); 104 | ctx.closePath(); 105 | } 106 | 107 | // draws star node 108 | algCS.drawStar = function(x, y, radius) { 109 | if (algCS.starColMode === "gradient") { 110 | let color1 = algCS.hexToRGB(algCS.starGradientColorOne); 111 | let color2 = algCS.hexToRGB(algCS.starGradientColorTwo); 112 | 113 | if (algCS.starGradientRandomColors) { 114 | color1 = algCS.randColor(); 115 | color2 = algCS.randColor(); 116 | } 117 | let initial = radius*algCS.colorCycleLengthModifier; 118 | while (radius > 1) { 119 | 120 | let step = radius % initial; 121 | let weight = Math.abs(2 * step / initial - 1); 122 | 123 | let colorArray = algCS.weightedColorAvg(weight, color1, color2); 124 | 125 | let alpha = algCS.randomAlpha ? Math.random() * algCS.randomAlphaMax : algCS.starAlpha; 126 | algCS.makePolygon(x,y,radius,colorArray, alpha, true) 127 | radius--; 128 | } 129 | } else if (algCS.starColMode === "random") { 130 | algCS.makePolygon(x, y, radius, algCS.randColor(), 1.0, true) 131 | } else if (algCS.starColMode === "fixed") { 132 | algCS.makePolygon(x, y, radius, algCS.hexToRGB(algCS.starFixedColor), 1.0, true) 133 | } 134 | } 135 | 136 | // ** Thanks to Michael Wehar (AlgStickers) for star function (modified) below and design 137 | algCS.makePolygon = function(x, y, radius, colorArray, alpha, fillFlag) { 138 | ctx.globalAlpha = alpha; 139 | if(fillFlag) { 140 | ctx.fillStyle = algCS.colorFormat(colorArray); 141 | } else { 142 | ctx.strokeStyle = algCS.colorFormat(colorArray); 143 | } 144 | 145 | let numOfInBetween = algCS.starCurvePoints 146 | numOfPoints = (1+numOfInBetween) * algCS.numOfStarPoints; 147 | let angleOffset = 2 * Math.PI / numOfPoints; 148 | algCS.setExpFactors(numOfInBetween+2) 149 | 150 | ctx.beginPath(); 151 | ctx.moveTo(x, y - radius); 152 | 153 | for (let i = 1; i <= numOfPoints; i++) { 154 | let angle = i * angleOffset - Math.PI / 2; 155 | 156 | let dX = (radius * Math.pow(algCS.innerRadiusScale, algCS.getExpFactor(i, 1+numOfInBetween))) * Math.cos(angle); 157 | let dY = (radius * Math.pow(algCS.innerRadiusScale, algCS.getExpFactor(i, 1+numOfInBetween))) * Math.sin(angle); 158 | 159 | ctx.lineTo(x + dX, y + dY); 160 | } 161 | 162 | ctx.closePath(); 163 | if(fillFlag) { 164 | ctx.fill(); 165 | } else { 166 | ctx.stroke(); 167 | } 168 | } 169 | // makePolygon helpers to define points of a star 170 | algCS.setExpFactors = function(num) { 171 | algCS.curveArray = []; 172 | 173 | for (let i = 0; i < num; i++) { 174 | if (i+1 <= Math.ceil(num/2)) { 175 | algCS.curveArray[i] = i 176 | } else { 177 | algCS.curveArray[i] = num-i-1 178 | } 179 | } 180 | } 181 | algCS.getExpFactor = function(i, num) { 182 | return Math.sqrt(algCS.curveArray[i%num]) 183 | // return Math.log10(algCS.curveArray[i%num]+1) 184 | // return algCS.curveArray[i%num] 185 | } 186 | 187 | // draws bullseye node 188 | algCS.drawBullseye = function(x, y, radius) { 189 | if (algCS.bullseyeMode === "linearGradient") { 190 | 191 | let color1 = algCS.hexToRGB(algCS.linearGradientColorOne); 192 | let color2 = algCS.hexToRGB(algCS.linearGradientColorTwo); 193 | if (algCS.linearGradientRandomColors) { 194 | color1 = algCS.randColor(); 195 | color2 = algCS.randColor(); 196 | } 197 | 198 | let initial = radius*algCS.colorCycleLengthModifier; 199 | 200 | while (radius > 1) { 201 | let step = radius % initial; 202 | let weight = Math.abs(2 * step / initial - 1); 203 | 204 | let colorArray = algCS.weightedColorAvg(weight, color1, color2); 205 | 206 | let alpha = algCS.randomAlpha ? Math.random() * algCS.randomAlphaMax : algCS.bullseyeAlpha; 207 | 208 | algCS.drawCircle(x, y, radius, alpha, algCS.colorFormat(colorArray) ); 209 | radius--; 210 | } 211 | 212 | } else if (algCS.bullseyeMode === "random") { 213 | while (radius > 1) { 214 | let colorArray = algCS.randColor(); 215 | algCS.drawCircle(x, y, radius, 1, algCS.colorFormat(colorArray) ); 216 | radius--; 217 | } 218 | } 219 | } 220 | 221 | // draws glow 222 | algCS.drawGlow = function(x, y, baseRadius, alpha, color, base) { 223 | let radius = baseRadius*(Math.random()*(base-1.5) + 1.5); 224 | while (radius > 1) { 225 | algCS.drawCircle(x, y, radius, alpha, color); 226 | radius--; 227 | } 228 | } 229 | 230 | // draws a circle 231 | algCS.drawCircle = function(x, y, radius, alpha, color) { 232 | ctx.globalAlpha = alpha; 233 | ctx.fillStyle = color; 234 | ctx.beginPath(); 235 | ctx.arc(x, y, radius, 0, 2 * Math.PI); 236 | ctx.fill(); 237 | ctx.closePath(); 238 | } 239 | 240 | // draws line 241 | algCS.drawLine = function (p1,p2) { 242 | ctx.globalAlpha = 0.85; 243 | // ctx.strokeStyle = algCS.circleColors[Math.floor(Math.random() * algCS.circleColors.length)]; 244 | ctx.globalAlpha = algCS.lineAlpha; 245 | ctx.strokeStyle = algCS.lineColor; 246 | ctx.lineWidth = algCS.lineWidthSizeFactor * Math.floor(algCS.calculateDistance(p1,p2)/25); 247 | ctx.beginPath(); 248 | ctx.moveTo(p1[0],p1[1]); 249 | ctx.lineTo(p2[0],p2[1]); 250 | ctx.stroke(); 251 | ctx.closePath(); 252 | } 253 | 254 | // returns radius of large nodes using parameters 255 | algCS.getRadius = function() { 256 | return Math.random() * (algCS.maxRadius - algCS.minRadius) + algCS.minRadius 257 | } 258 | 259 | // helper to convert hex string color to RGB array 260 | algCS.hexToRGB = function(hex) { 261 | const r = parseInt(hex.slice(1, 3), 16); 262 | const g = parseInt(hex.slice(3, 5), 16); 263 | const b = parseInt(hex.slice(5, 7), 16); 264 | return [r, g, b]; 265 | } 266 | 267 | // helper that generates a random color RGB array 268 | algCS.randColor = function() { 269 | return [algCS.randInt(255), algCS.randInt(255), algCS.randInt(255)] 270 | } 271 | 272 | // helper that finds weighted color average of two colors given a weight 273 | algCS.weightedColorAvg = function(weight, colorOne, colorTwo) { // 274 | // Color One 275 | let r1 = colorOne[0]; 276 | let g1 = colorOne[1]; 277 | let b1 = colorOne[2]; 278 | // Color Two 279 | let r2 = colorTwo[0]; 280 | let g2 = colorTwo[1]; 281 | let b2 = colorTwo[2]; 282 | // Averaged Color 283 | let r = weight * r1 + (1 - weight) * r2; 284 | let g = weight * g1 + (1 - weight) * g2; 285 | let b = weight * b1 + (1 - weight) * b2; 286 | return [r, g, b]; 287 | } 288 | 289 | // helper that formats a RGB color array to a string 290 | algCS.colorFormat = function(colorArray) { // 291 | let r = Math.floor(colorArray[0]); 292 | let g = Math.floor(colorArray[1]); 293 | let b = Math.floor(colorArray[2]); 294 | return "rgb(" + r + "," + g + "," + b + ")"; 295 | } 296 | 297 | // helper that calculates euclidean distance 298 | algCS.calculateDistance = function (p1, p2) { // 299 | return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1])); 300 | } 301 | 302 | // helper that generates a whole random number from 0 to max (inclusive) 303 | algCS.randInt = function(max) { 304 | return Math.floor(Math.random() * (max + 1)) 305 | } 306 | 307 | // function that maps value from one range to another 308 | algCS.mapValue = function (value, range1, range2) { 309 | return range2[0] + (((value-range1[0])*(range2[1]-range2[0]))/(range1[1]-range1[0])) 310 | } 311 | 312 | // attemptStep helper function to find candidate coordinates for a node 313 | algCS.findCoordinates = function (existingPoint) { 314 | let x = Math.floor(width * Math.random()); 315 | let y = Math.floor(height * Math.random()); 316 | 317 | if (algCS.placementMode == "grid") { 318 | x = Math.floor(algCS.gridDim * Math.random()) * (width / (algCS.gridDim - 1)); 319 | y = Math.floor(algCS.gridDim * Math.random()) * (height / (algCS.gridDim - 1)); 320 | 321 | } else if (algCS.placementMode == "discreteAngle" && algCS.positions.length > 0) { 322 | // choose start 323 | let temp = algCS.positions[Math.floor(Math.random() * algCS.positions.length)] 324 | existingPoint[0] = temp[0]; 325 | existingPoint[1] = temp[1]; 326 | // Choose base angle 327 | let baseAngle = Math.PI / algCS.angleDenom; 328 | // Choose multiple of angle 329 | let rangeMultiple = Math.floor((2 * Math.PI) / baseAngle); 330 | let angle = baseAngle * (Math.floor(Math.random() * (rangeMultiple) + 1)) 331 | // Length 332 | let tempLength = Math.random() * 0.1 * Math.min(height,width) + 1; 333 | tempLength = Math.max(algCS.minDist, tempLength) 334 | 335 | x = existingPoint[0] + tempLength * Math.cos(angle) 336 | y = existingPoint[1] + tempLength * Math.sin(angle) 337 | 338 | x = Math.max(0, Math.min(width, x)) 339 | y = Math.max(0, Math.min(height, y)) 340 | } 341 | 342 | return {x: x, y: y}; 343 | } 344 | 345 | // attemptStep helper function to validate distance constraint for a node 346 | algCS.validateDistance = function (p) { 347 | if (algCS.minDist && algCS.positions.length > 0) { 348 | for (let i = 0; i < algCS.positions.length; i++) { 349 | let tempDist = algCS.calculateDistance([p.x, p.y], algCS.positions[i]); 350 | if (tempDist < algCS.minDist) { 351 | return false; 352 | } 353 | } 354 | } 355 | return true; 356 | } 357 | 358 | // attemptStep helper function to validate overlapping lines constraint 359 | algCS.validateOverlappingLines = function (p, existingPoint) { 360 | for (let j = 0; j < algCS.edges.length; j++) { 361 | // Credit to Michael Wehar (algGP) for checkIntersection function 362 | if (algGP.checkIntersection([p.x, p.y], existingPoint, algCS.edges[j][0], algCS.edges[j][1])) { 363 | return false; 364 | } 365 | } 366 | 367 | return true; 368 | } 369 | 370 | // attemptStep helper function to place candidate node 371 | algCS.placeNode = function (p, largeRadius, isSmallNode) { 372 | if (!isSmallNode) { 373 | 374 | if (algCS.largeNodeShape === "bullseye") { 375 | algCS.drawBullseye(p.x, p.y, largeRadius); 376 | } else if (algCS.largeNodeShape === "star") { 377 | algCS.drawStar(p.x, p.y, largeRadius) 378 | } else if (algCS.largeNodeShape === "both") { 379 | let tmp = Math.random(); 380 | if (tmp > algCS.bothShapeModeStarProb) { 381 | algCS.drawBullseye(p.x, p.y, largeRadius); 382 | } else { 383 | algCS.drawStar(p.x, p.y, largeRadius); 384 | } 385 | } 386 | 387 | if (algCS.glow) { 388 | algCS.drawGlow(p.x, p.y, largeRadius, 0.01, algCS.randomGlow ? algCS.colorFormat(algCS.randColor()) : algCS.glowColor, algCS.glowLargeRadiusFactor); 389 | } 390 | 391 | } else { 392 | 393 | let smallRadius = Math.floor(Math.random()*3 + 1) 394 | if (algCS.smallCircleMode === "fixed") { 395 | algCS.drawCircle(p.x, p.y, smallRadius, 0.75, algCS.smallCircleColorFixed); 396 | } else if (algCS.smallCircleMode === "random") { 397 | algCS.drawCircle(p.x, p.y, smallRadius, 0.75, algCS.colorFormat(algCS.randColor())); 398 | } 399 | 400 | if (algCS.glowOnSmall) { 401 | algCS.drawGlow(p.x, p.y, smallRadius, 0.01, algCS.randomGlow ? algCS.colorFormat(algCS.randColor()) : algCS.glowColor,algCS.glowSmallRadiusFactor); 402 | } 403 | } 404 | } 405 | 406 | // attempts to place a node 407 | algCS.attemptStep = function () { 408 | let largeRadius = algCS.getRadius(); 409 | let existingPoint = [Infinity, Infinity]; 410 | let p = algCS.findCoordinates(existingPoint); 411 | 412 | if (!algCS.validateDistance(p)) { 413 | return false; 414 | } 415 | 416 | // finds existing node to connect with candidate node 417 | if (algCS.placementMode != "discreteAngle") { 418 | existingPoint = [Infinity, Infinity]; 419 | for (let i = 0; i < algCS.positions.length; i++) { 420 | if (algCS.calculateDistance(algCS.positions[i], [p.x, p.y]) < algCS.calculateDistance(existingPoint, [p.x, p.y])) { 421 | existingPoint = [algCS.positions[i][0], algCS.positions[i][1]]; 422 | } 423 | } 424 | } 425 | 426 | if (!algCS.overlappingLines && !algCS.validateOverlappingLines(p, existingPoint)) { 427 | return false; 428 | } 429 | 430 | // place candidate node 431 | let isSmallNode = Math.floor(Math.random()*algCS.largeNodeCommonFactor); 432 | algCS.placeNode(p, largeRadius, isSmallNode); 433 | 434 | // draw line from current node to an existing node 435 | algCS.drawLine([p.x, p.y], existingPoint); 436 | if (!algCS.overlappingLines) { 437 | algCS.edges.push([[p.x, p.y], existingPoint]) 438 | } 439 | 440 | // decide whether or not to make current node a leaf 441 | if (isSmallNode) { 442 | algCS.positions.push([p.x, p.y]); 443 | } else if (!isSmallNode && !algCS.forceBigNodeLeafs) { 444 | algCS.positions.push([p.x, p.y]); 445 | } 446 | 447 | return true; 448 | } 449 | 450 | algCS.drawOneStep = function () { 451 | if(algCS.numOfSteps > algCS.maxNumOfSteps) { 452 | clearInterval(algCS.loop); 453 | return false; 454 | } else { 455 | let validStep = false; 456 | let count = 0; 457 | while (!validStep && count < algCS.maxAttempsPerStep) { 458 | validStep = algCS.attemptStep(); 459 | count++; 460 | } 461 | algCS.numOfSteps++; 462 | } 463 | } 464 | 465 | algCS.reset = function () { 466 | // Check if already running 467 | algCS.pause(); 468 | // Initialize values 469 | algCS.numOfSteps = 0; 470 | algCS.positions = []; 471 | algCS.edges = []; 472 | algCS.curveArray = []; 473 | } 474 | 475 | algCS.initialize = function() { 476 | algCS.reset(); 477 | } 478 | 479 | algCS.pause = function () { 480 | if("loop" in algCS) { 481 | clearInterval(algCS.loop); 482 | } 483 | } 484 | 485 | algCS.start = function () { 486 | if (algCS.setBackground) { 487 | algCS.setBackgroundColor(algCS.backgroundColor); 488 | } 489 | algCS.loop = setInterval(algCS.drawOneStep, algCS.speed); 490 | algCS.positions = []; 491 | algCS.edges = []; 492 | algCS.curveArray = []; 493 | } 494 | 495 | // helper for randomize that loads default parameters 496 | algCS.loadDefaultParams = function () { 497 | algCS.speed = 0; 498 | algCS.maxNumOfSteps = 500; 499 | algCS.maxRadius = 20; 500 | algCS.minRadius = 10; 501 | algCS.bullseyeMode = "linearGradient" 502 | algCS.linearGradientRandomColors = false; 503 | algCS.linearGradientColorOne = "#FF0000" 504 | algCS.linearGradientColorTwo = "#FFFF00" 505 | algCS.colorCycleLengthModifier = 2; 506 | algCS.smallCircleMode = "fixed" 507 | algCS.smallCircleColorFixed = "yellow"; 508 | algCS.backgroundColor = "#051029"; 509 | algCS.lineColor = "white"; 510 | algCS.minDist = 5; 511 | algCS.forceBigNodeLeafs = true; 512 | algCS.largeNodeCommonFactor = 10; 513 | algCS.lineWidthSizeFactor = 0.25; 514 | algCS.setBackground = true; 515 | algCS.maxAttempsPerStep = 15; 516 | algCS.glow = true; 517 | algCS.glowOnSmall = false; 518 | algCS.glowLargeRadiusFactor = 2; 519 | algCS.glowSmallRadiusFactor = 4; 520 | algCS.glowColor = "yellow"; 521 | algCS.largeNodeShape = "star"; 522 | algCS.starFixedColor = "#0000FF"; 523 | algCS.starColMode = "gradient"; 524 | algCS.starGradientColorOne = "#FF0000" 525 | algCS.starGradientColorTwo = "#FFFF00" 526 | algCS.starGradientRandomColors = false; 527 | algCS.numOfStarPoints = 4; 528 | algCS.innerRadiusScale = 0.5; 529 | algCS.randomGlow = false; 530 | algCS.overlappingLines = true; 531 | algCS.starAlpha = 1.0; 532 | algCS.lineAlpha = 1.0; 533 | algCS.bullseyeAlpha = 1.0; 534 | algCS.randomAlpha = false; 535 | algCS.randomAlphaMax = 0.35; 536 | algCS.bothShapeModeStarProb = 0.75 537 | algCS.placementMode = "random"; 538 | algCS.gridDim = 25; 539 | algCS.starCurvePoints = 4 540 | algCS.angleDenom = 4; 541 | algCS.discreteAngleMaxLength = 50; 542 | } 543 | 544 | // helper for randomize that varies default parameters 545 | algCS.varyClassicParams = function () { 546 | algCS.placementMode = ["random", "grid", "discreteAngle"][Math.floor(Math.random()*3)]; 547 | if (algCS.placementMode === "discreteAngle") { 548 | algCS.overlappingLines = true; 549 | algCS.minDist += Math.floor(Math.random()*15) + 15; 550 | algCS.discreteAngleMaxLength += Math.random()*10 + 45; 551 | } else { 552 | algCS.overlappingLines = Math.floor(Math.random()*2); 553 | algCS.minDist += Math.floor(Math.random()*10); 554 | } 555 | algCS.largeNodeShape = ["star", "bullseye", "both"][Math.floor(Math.random()*3)]; 556 | if (Math.random() < 0.2) { 557 | algCS.largeNodeShape = "star" 558 | } 559 | algCS.starAlpha = Math.random() * 0.5 + 0.5; 560 | algCS.lineAlpha = Math.random() * 0.5 + 0.5; 561 | if (Math.random() < 0.35) { 562 | algCS.maxRadius = 27 563 | algCS.minRadius = 3 564 | algCS.maxRadius += Math.floor(Math.random() * 10); 565 | algCS.minRadius += Math.floor(Math.random() * 9); 566 | if (Math.random() < 0.65) { 567 | algCS.maxRadius = 18 + Math.floor(Math.random()*5) 568 | algCS.minRadius = 8 + Math.floor(Math.random()*5) 569 | } 570 | } else if (Math.random() < 0.25) { 571 | algCS.maxRadius = 12 572 | algCS.minRadius = 3 573 | algCS.maxRadius += Math.floor(Math.random() * 5); 574 | algCS.minRadius += Math.floor(Math.random() * 5); 575 | } else { 576 | algCS.maxRadius = 35 577 | algCS.minRadius = 15 578 | algCS.maxRadius += Math.floor(Math.random() * 5); 579 | algCS.minRadius += Math.floor(Math.random() * 5); 580 | algCS.largeNodeCommonFactor--; 581 | } 582 | 583 | algCS.forceBigNodeLeafs = Math.floor(Math.random()*2); 584 | algCS.largeNodeCommonFactor += Math.random()*2 - 1; 585 | algCS.lineWidthSizeFactor = Math.round(Math.random()*1.5 * 10) / 10; 586 | algCS.glow = Math.floor(Math.random()*2); 587 | algCS.glowOnSmall = Math.floor(Math.random()*2); 588 | algCS.glowLargeRadiusFactor += Math.random()*2 - 1; 589 | algCS.glowSmallRadiusFactor += Math.random()*2 - 1; 590 | algCS.numOfStarPoints = Math.floor(Math.random()*7) + 2; 591 | if (Math.random() < 0.65) { 592 | algCS.innerRadiusScale = 0.35 + Math.random() * 0.3; 593 | } else { 594 | algCS.innerRadiusScale = 0.5 595 | } 596 | algCS.bullseyeAlpha = Math.random() * 0.5 + 0.5; 597 | algCS.randomAlpha = Math.floor(Math.random()*2); 598 | algCS.randomAlphaMax += Math.random()*0.1 - 0.05; 599 | algCS.bothShapeModeStarProb += Math.random()*0.1 - 0.05 600 | algCS.gridDim += Math.floor(Math.random()*11) - 5; 601 | algCS.starCurvePoints = Math.floor(Math.random()*7) + 1 602 | algCS.angleDenom = Math.floor(Math.random()*7) + 2; 603 | } 604 | 605 | // helper for randomize that varies color pallette parameters 606 | algCS.varyColorPallette = function() { 607 | let pallette = algCS.pallettes[Object.keys(algCS.pallettes)[Math.floor(Math.random()*Object.keys(algCS.pallettes).length)]] 608 | 609 | algCS.bullseyeMode = "linearGradient"; 610 | algCS.linearGradientRandomColors = Math.floor(Math.random()*2); 611 | algCS.linearGradientColorOne = pallette[Math.floor(Math.random()*pallette.length)] 612 | algCS.linearGradientColorTwo = pallette[Math.floor(Math.random()*pallette.length)] 613 | algCS.colorCycleLengthModifier = Math.floor(Math.random()*3) + 1; 614 | algCS.smallCircleMode = "fixed"; 615 | algCS.smallCircleColorFixed = pallette[Math.floor(Math.random()*pallette.length)]; 616 | algCS.lineColor = pallette[Math.floor(Math.random()*pallette.length)]; 617 | algCS.glowColor = pallette[Math.floor(Math.random()*pallette.length)]; 618 | algCS.starFixedColor = pallette[Math.floor(Math.random()*pallette.length)]; 619 | algCS.starColMode = ["gradient", "fixed"][Math.floor(Math.random()*2)]; 620 | algCS.starGradientColorOne = pallette[Math.floor(Math.random()*pallette.length)] 621 | algCS.starGradientColorTwo = pallette[Math.floor(Math.random()*pallette.length)] 622 | if (Math.random() < 0.05) { 623 | algCS.backgroundColor = pallette[Math.floor(Math.random()*pallette.length)]; 624 | } 625 | } 626 | 627 | // helper to set params to randomize colors instead of using pallettes 628 | algCS.setRandomColorParams = function () { 629 | algCS.bullseyeMode = ["linearGradient", "random"][Math.floor(Math.random()*2)]; 630 | algCS.linearGradientRandomColors = true; 631 | algCS.smallCircleMode = ["fixed", "random"][Math.floor(Math.random()*2)]; 632 | algCS.lineColor = algCS.colorFormat(algCS.randColor()); 633 | algCS.glowColor = algCS.colorFormat(algCS.randColor()); 634 | algCS.starFixedColor = algCS.colorFormat(algCS.randColor()); 635 | algCS.starColMode = ["gradient", "random", "fixed"][Math.floor(Math.random()*3)]; 636 | algCS.starGradientRandomColors = true; 637 | } 638 | 639 | // helper for randomize that drastically randomizes parameters 640 | algCS.drasticRandomize = function() { 641 | algCS.placementMode = ["random", "grid", "discreteAngle"][Math.floor(Math.random()*3)]; 642 | 643 | // general node placement constraints 644 | algCS.minDist = Math.floor(Math.random()*25) + 5; 645 | algCS.largeNodeCommonFactor = Math.floor(Math.random()*15); 646 | algCS.overlappingLines = Math.random() < 0.5; 647 | algCS.gridDim = Math.floor(Math.random()*35) + 15; 648 | algCS.forceBigNodeLeafs = Math.random() < 0.5; 649 | 650 | // for discreteAngle placement mode 651 | algCS.angleDenom = Math.floor(Math.random()*11) + 2; // factor used to determine angles allowed e.g. 2 = pi/2 = only 90 degrees 652 | algCS.discreteAngleMaxLength = Math.floor(Math.random() * 50) + 25; // max distance that a node can be from another node 653 | 654 | // large node properties 655 | if (Math.random() < 0.25) { 656 | algCS.maxRadius = 30 657 | algCS.minRadius = 3 658 | algCS.maxRadius += Math.floor(Math.random() * 15); 659 | algCS.minRadius += Math.floor(Math.random() * 15); 660 | if (Math.random() < 0.5) { 661 | algCS.maxRadius = 17 + Math.floor(Math.random()*7) 662 | algCS.minRadius = 6 + Math.floor(Math.random()*9) 663 | } 664 | } else if (Math.random() < 0.25) { 665 | algCS.maxRadius = 12 666 | algCS.minRadius = 3 667 | algCS.maxRadius += Math.floor(Math.random() * 5); 668 | algCS.minRadius += Math.floor(Math.random() * 5); 669 | } else { 670 | algCS.maxRadius = 35 671 | algCS.minRadius = 15 672 | algCS.maxRadius += Math.floor(Math.random() * 7); 673 | algCS.minRadius += Math.floor(Math.random() * 7); 674 | } 675 | algCS.largeNodeShape = ["star", "bullseye", "both"][Math.floor(Math.random()*3)]; 676 | if (Math.random() < 0.2) { 677 | algCS.largeNodeShape = "star" 678 | } 679 | algCS.colorCycleLengthModifier = Math.floor(Math.random() * 3) + 1; // for gradient color modes 680 | algCS.randomAlpha = Math.random() < 0.5; 681 | algCS.randomAlphaMax = Math.random() * 0.7 + 0.1; 682 | 683 | // bullseye node shape specific properties 684 | algCS.bullseyeMode = ["linearGradient", "random"][Math.floor(Math.random()*2)]; 685 | algCS.bullseyeAlpha = Math.random() * 0.85 + 0.15; 686 | algCS.linearGradientRandomColors = Math.random() < 0.5; 687 | algCS.linearGradientColorOne = algCS.colorFormat(algCS.randColor()); 688 | algCS.linearGradientColorTwo = algCS.colorFormat(algCS.randColor()); 689 | 690 | // star node shape specific properties 691 | algCS.starColMode = ["gradient", "random", "fixed"][Math.floor(Math.random()*3)]; 692 | algCS.starGradientColorOne = algCS.colorFormat(algCS.randColor()); 693 | algCS.starGradientColorTwo = algCS.colorFormat(algCS.randColor()); 694 | algCS.starGradientRandomColors = Math.random() < 0.5; 695 | algCS.starAlpha = Math.random() * 0.85 + 0.15; 696 | algCS.starFixedColor = algCS.colorFormat(algCS.randColor()); 697 | algCS.starCurvePoints = Math.floor(Math.random()*10) + 1; 698 | algCS.numOfStarPoints = Math.floor(Math.random()*10) + 2; 699 | if (Math.random() < 0.95) { 700 | algCS.innerRadiusScale = 0.25 + Math.random() * 0.5; 701 | } 702 | 703 | // both shape mode 704 | algCS.bothShapeModeStarProb = Math.random() // probability of drawing stars in 705 | 706 | // small node properties 707 | algCS.smallCircleMode = ["fixed", "random"][Math.floor(Math.random()*2)]; 708 | algCS.smallCircleColorFixed = algCS.colorFormat(algCS.randColor()); 709 | 710 | // glow properties 711 | algCS.glow = Math.random() < 0.5; 712 | algCS.glowOnSmall = Math.random() < 0.5; // on small nodes 713 | algCS.randomGlow = Math.random() < 0.5; // color 714 | algCS.glowColor = algCS.colorFormat(algCS.randColor()); // default 715 | algCS.glowLargeRadiusFactor = 1 + Math.floor(Math.random() * 2); // size factor for large node glow 716 | algCS.glowSmallRadiusFactor = 2 + Math.floor(Math.random() * 4); // size factor for small node glow 717 | 718 | // line properties 719 | algCS.lineColor = algCS.colorFormat(algCS.randColor());; 720 | algCS.lineWidthSizeFactor = Math.round(Math.random()*2.25 * 10) / 10; 721 | algCS.lineAlpha = 0.1 + Math.random() * 0.9; 722 | 723 | algCS.placementMode = ["random", "grid", "discreteAngle"][Math.floor(Math.random()*3)]; 724 | if (algCS.placementMode === "discreteAngle") { 725 | algCS.overlappingLines = true; 726 | algCS.minDist += Math.floor(Math.random()*40) + 5; 727 | algCS.discreteAngleMaxLength += Math.random()*15 + 50; 728 | } else { 729 | algCS.overlappingLines = Math.floor(Math.random()*2); 730 | algCS.minDist += Math.floor(Math.random()*10); 731 | } 732 | } 733 | -------------------------------------------------------------------------------- /algCS_params.js: -------------------------------------------------------------------------------- 1 | /* Parameters */ 2 | algCS.speed = 0; 3 | algCS.maxNumOfSteps = 500; 4 | algCS.maxAttempsPerStep = 15; 5 | algCS.setBackground = true; // set background or just place nodes 6 | algCS.backgroundColor = "#051029"; 7 | algCS.placementMode = "random"; // random, grid, discreteAngle 8 | 9 | // general node placement constraints 10 | algCS.minDist = 5; // minium distance to every other node 11 | algCS.largeNodeCommonFactor = 10; // higher: more uncommon large nodes 12 | algCS.overlappingLines = true; 13 | algCS.gridDim = 25; // grid dimension 14 | algCS.forceBigNodeLeafs = true; // large nodes always be leafs 15 | 16 | // for discreteAngle placement mode 17 | algCS.angleDenom = 4; // factor used to determine angles allowed e.g. 2 = pi/2 = only 90 degrees 18 | algCS.discreteAngleMaxLength = 50; // max distance that a node can be from another node 19 | 20 | // large node properties 21 | algCS.maxRadius = 20; 22 | algCS.minRadius = 10; 23 | algCS.largeNodeShape = "star"; // bullseye, star, both 24 | algCS.colorCycleLengthModifier = 2; // for gradient color modes 25 | algCS.randomAlpha = false; 26 | algCS.randomAlphaMax = 0.35; 27 | 28 | // bullseye node shape specific properties 29 | algCS.bullseyeMode = "linearGradient" // linear gradient, random 30 | algCS.bullseyeAlpha = 1.0; 31 | algCS.linearGradientRandomColors = false; 32 | algCS.linearGradientColorOne = "#FF0000" 33 | algCS.linearGradientColorTwo = "#FFFF00" 34 | 35 | // star node shape specific properties 36 | algCS.starColMode = "gradient"; // color mode: gradient, random, fixed 37 | algCS.starGradientColorOne = "#FF0000" 38 | algCS.starGradientColorTwo = "#FFFF00" 39 | algCS.starGradientRandomColors = false; 40 | algCS.starAlpha = 1.0; 41 | algCS.starFixedColor = "#0000FF"; 42 | algCS.starCurvePoints = 4 // points defining curve between star points 43 | algCS.numOfStarPoints = 4; // number of points for star nodes 44 | algCS.innerRadiusScale = 0.5; 45 | 46 | // both shape mode 47 | algCS.bothShapeModeStarProb = 0.75 // probability of drawing stars in 48 | 49 | // small node properties 50 | algCS.smallCircleMode = "fixed" // color: fixed, random 51 | algCS.smallCircleColorFixed = "yellow"; 52 | 53 | // glow properties 54 | algCS.glow = true; 55 | algCS.glowOnSmall = false; // on small nodes 56 | algCS.randomGlow = false; // color 57 | algCS.glowColor = "yellow"; // default 58 | algCS.glowLargeRadiusFactor = 2; // size factor for large node glow 59 | algCS.glowSmallRadiusFactor = 4; // size factor for small node glow 60 | 61 | // line properties 62 | algCS.lineColor = "white"; 63 | algCS.lineWidthSizeFactor = 0.25; // higher = thicker lines 64 | algCS.lineAlpha = 1.0; 65 | -------------------------------------------------------------------------------- /algDots.js: -------------------------------------------------------------------------------- 1 | var algDots = {}; 2 | 3 | algDots.drawCircle = function(x, y, radius, color, alpha) { 4 | ctx.globalAlpha = alpha; 5 | ctx.fillStyle = color; 6 | ctx.beginPath(); 7 | ctx.arc(x, y, radius, 0, 2 * Math.PI); 8 | ctx.fill(); 9 | } 10 | 11 | algDots.drawOneStep = function () { 12 | if(algDots.numOfSteps > algDots.maxNumOfSteps) { 13 | clearInterval(algDots.loop); 14 | return false; 15 | } else { 16 | let x = Math.floor(width * Math.random()); 17 | let y = Math.floor(height * Math.random()); 18 | algDots.drawCircle(x, y, algDots.circleRadius, algDots.circleColor, algDots.circleAlpha); 19 | algDots.numOfSteps++; 20 | } 21 | } 22 | 23 | algDots.reset = function () { 24 | // Check if already running 25 | algDots.pause(); 26 | // Initialize values 27 | algDots.numOfSteps = 0; 28 | } 29 | 30 | algDots.initialize = function() { 31 | algDots.reset(); 32 | } 33 | 34 | algDots.pause = function () { 35 | if("loop" in algDots) { 36 | clearInterval(algDots.loop); 37 | } 38 | } 39 | 40 | algDots.start = function () { 41 | algDots.loop = setInterval(algDots.drawOneStep, algDots.speed); 42 | } 43 | -------------------------------------------------------------------------------- /algDots_params.js: -------------------------------------------------------------------------------- 1 | /* Parameters */ 2 | algDots.speed = 0; 3 | algDots.maxNumOfSteps = 500; 4 | algDots.circleRadius = 15; 5 | algDots.circleColor = "black"; 6 | algDots.circleAlpha = 0.4; 7 | -------------------------------------------------------------------------------- /algGP.js: -------------------------------------------------------------------------------- 1 | var algGP = {}; 2 | 3 | /* Constants */ 4 | var pi = Math.PI; 5 | 6 | /* Helper Functions */ 7 | algGP.distance = function (p1, p2) { 8 | return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1])); 9 | } 10 | 11 | algGP.withinBoundaries = function (p) { 12 | return 0 <= p[0] && p[0] < width && 0 <= p[1] && p[1] < height; 13 | } 14 | 15 | algGP.computeLine = function (p1, p2) { 16 | let slope = (p1[1] - p2[1]) / (p1[0] - p2[0]); 17 | if(slope > 1000000) { 18 | slope = Infinity; 19 | } else if(slope < -1000000) { 20 | slope = -Infinity; 21 | } else if(algGP.isBetween(slope, -0.0000001, 0.0000001)) { 22 | slope = 0; 23 | } 24 | let intercept = p1[1] - slope * p1[0]; 25 | return [slope, intercept]; 26 | } 27 | 28 | algGP.equalPoints = function (p1, p2) { 29 | return p1[0] == p2[0] && p1[1] == p2[1]; 30 | } 31 | 32 | algGP.isBetween = function (x, y, z) { 33 | return (y < x && x < z) || (z < x && x < y); 34 | } 35 | 36 | algGP.checkIntersection = function (p1, p2, q1, q2) { 37 | // Midpoint check 38 | let mid1 = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2]; 39 | let mid2 = [(q1[0] + q2[0]) / 2, (q1[1] + q2[1]) / 2]; 40 | if(algGP.distance(mid1, mid2) < algGP.midpointRadius) { 41 | return true; 42 | } 43 | // Duplicate points 44 | if(algGP.equalPoints(p1, q1) || algGP.equalPoints(p1, q2) || algGP.equalPoints(p2, q1) || algGP.equalPoints(p2, q2)) { 45 | return false; 46 | } 47 | // Intersection check 48 | let line1 = algGP.computeLine(p1, p2); 49 | let line2 = algGP.computeLine(q1, q2); 50 | if(!isFinite(line1[0]) && !isFinite(line2[0])) { 51 | if(p1[0] == q1[0]) { 52 | return algGP.isBetween(p1[1], q1[1], q2[1]) || algGP.isBetween(p2[1], q1[1], q2[1]) || algGP.isBetween(q1[1], p1[1], p2[1]); 53 | } else { 54 | return false; 55 | } 56 | } else if(!isFinite(line1[0]) && line2[0] == 0) { 57 | return algGP.isBetween(p1[0], q1[0], q2[0]) && algGP.isBetween(q1[1], p1[1], p2[1]); 58 | } else if(!isFinite(line2[0]) && line1[0] == 0) { 59 | return algGP.isBetween(q1[0], p1[0], p2[0]) && algGP.isBetween(p1[1], q1[1], q2[1]); 60 | } else if(!isFinite(line1[0])) { 61 | let y = line2[0] * p1[0] + line2[1]; 62 | return algGP.isBetween(y, p1[1], p2[1]) && algGP.isBetween(y, q1[1], q2[1]); 63 | } else if(!isFinite(line2[0])) { 64 | let y = line1[0] * q1[0] + line1[1]; 65 | return algGP.isBetween(y, p1[1], p2[1]) && algGP.isBetween(y, q1[1], q2[1]); 66 | } else if(line1[0] == line2[0] && line1[1] == line2[1]) { 67 | return algGP.isBetween(p1[0], q1[0], q2[0]) || algGP.isBetween(p2[0], q1[0], q2[0]) || algGP.isBetween(q1[0], p1[0], p2[0]); 68 | } else if(line1[0] != line2[0]) { 69 | let x = (line2[1] - line1[1]) / (line1[0] - line2[0]); 70 | return algGP.isBetween(x, p1[0], p2[0]) && algGP.isBetween(x, q1[0], q2[0]); 71 | } else { 72 | return false; 73 | } 74 | } 75 | 76 | algGP.checkValid = function (p1, p2) { 77 | if(!algGP.withinBoundaries(p2)) { 78 | return false; 79 | } 80 | if(algGP.avoidIntersections) { 81 | for(let i = 0; i < algGP.listOfLines.length; i++) { 82 | let q1 = algGP.listOfLines[i][0]; 83 | let q2 = algGP.listOfLines[i][1]; 84 | if(algGP.checkIntersection(p1, p2, q1, q2)) { 85 | return false; 86 | } 87 | } 88 | } 89 | return true; 90 | } 91 | 92 | /* Drawing Functions */ 93 | algGP.drawLine = function (p1, p2) { 94 | ctx.globalAlpha = 1.0; 95 | ctx.strokeStyle = algGP.lineColor; 96 | if(algGP.colorByLength) { 97 | let length = algGP.distance(p1, p2); 98 | let lengthScale = (length - algGP.lineMinLength) / (algGP.lineMaxLength - algGP.lineMinLength); 99 | let r = Math.floor(lengthScale * algGP.longColor[0] + (1 - lengthScale) * algGP.shortColor[0]); 100 | let g = Math.floor(lengthScale * algGP.longColor[1] + (1 - lengthScale) * algGP.shortColor[1]); 101 | let b = Math.floor(lengthScale * algGP.longColor[2] + (1 - lengthScale) * algGP.shortColor[2]); 102 | ctx.strokeStyle = "rgb(" + r + "," + g + "," + b + ")"; 103 | } else if(algGP.colorByStep) { 104 | let stepScale = (algGP.numOfSteps % algGP.stepInterval) / algGP.stepInterval; 105 | stepScale = Math.abs(1 - 2 * stepScale); 106 | let r = Math.floor(stepScale * algGP.initialColor[0] + (1 - stepScale) * algGP.finalColor[0]); 107 | let g = Math.floor(stepScale * algGP.initialColor[1] + (1 - stepScale) * algGP.finalColor[1]); 108 | let b = Math.floor(stepScale * algGP.initialColor[2] + (1 - stepScale) * algGP.finalColor[2]); 109 | ctx.strokeStyle = "rgb(" + r + "," + g + "," + b + ")"; 110 | } else if(algGP.backtracking && algGP.colorByBranch) { 111 | let length = algGP.branchOfLines.length; 112 | let lengthScale = (length % algGP.branchInterval) / algGP.branchInterval; 113 | lengthScale = Math.abs(1 - 2 * lengthScale); 114 | let r = Math.floor(lengthScale * algGP.longBranchColor[0] + (1 - lengthScale) * algGP.shortBranchColor[0]); 115 | let g = Math.floor(lengthScale * algGP.longBranchColor[1] + (1 - lengthScale) * algGP.shortBranchColor[1]); 116 | let b = Math.floor(lengthScale * algGP.longBranchColor[2] + (1 - lengthScale) * algGP.shortBranchColor[2]); 117 | ctx.strokeStyle = "rgb(" + r + "," + g + "," + b + ")"; 118 | } else if(algGP.randomColor) { 119 | let r = Math.floor(256 * Math.random()); 120 | let g = Math.floor(256 * Math.random()); 121 | let b = Math.floor(256 * Math.random()); 122 | ctx.strokeStyle = "rgb(" + r + "," + g + "," + b + ")"; 123 | } 124 | ctx.lineWidth = algGP.lineWidth; 125 | ctx.beginPath(); 126 | ctx.moveTo(p1[0], p1[1]); 127 | ctx.lineTo(p2[0], p2[1]); 128 | ctx.stroke(); 129 | if(algGP.drawEndpoints) { 130 | ctx.fillStyle = algGP.endpointColor; 131 | ctx.beginPath(); 132 | ctx.arc(p1[0], p1[1], algGP.endpointRadius, 0, 2 * Math.PI); 133 | ctx.fill(); 134 | ctx.beginPath(); 135 | ctx.arc(p2[0], p2[1], algGP.endpointRadius, 0, 2 * Math.PI); 136 | ctx.fill(); 137 | } 138 | if(algGP.drawCircles) { 139 | ctx.fillStyle = ctx.strokeStyle; 140 | ctx.globalAlpha = algGP.circlesAlpha; 141 | ctx.beginPath(); 142 | ctx.arc(p1[0], p1[1], algGP.circlesRadius, 0, 2 * Math.PI); 143 | ctx.fill(); 144 | ctx.beginPath(); 145 | ctx.arc(p2[0], p2[1], algGP.circlesRadius, 0, 2 * Math.PI); 146 | ctx.fill(); 147 | ctx.globalAlpha = 1.0; 148 | } 149 | if(algGP.drawRectangles) { 150 | ctx.fillStyle = ctx.strokeStyle; 151 | ctx.globalAlpha = algGP.rectanglesAlpha; 152 | ctx.beginPath(); 153 | ctx.rect(p1[0] - algGP.rectanglesWidth / 2, p1[1] - algGP.rectanglesHeight / 2, algGP.rectanglesWidth, algGP.rectanglesHeight); 154 | ctx.fill(); 155 | ctx.beginPath(); 156 | ctx.rect(p2[0] - algGP.rectanglesWidth / 2, p2[1] - algGP.rectanglesHeight / 2, algGP.rectanglesWidth, algGP.rectanglesHeight); 157 | ctx.fill(); 158 | ctx.globalAlpha = 1.0; 159 | } 160 | } 161 | 162 | algGP.attemptToDraw = function () { 163 | // Angle 164 | let angle = ((algGP.angleMax - algGP.angleMin) * Math.random() + algGP.angleMin + 2 * pi) % (2 * pi); 165 | if(algGP.discreteAngles) { 166 | angle = Math.round(angle / algGP.discreteAngleOffset) * algGP.discreteAngleOffset; 167 | } else if(algGP.spiral) { 168 | angle = (algGP.currentAngle + Math.random() * (algGP.angleOffsetMax - algGP.angleOffsetMin) + algGP.angleOffsetMin + 2 * pi) % (2 * pi); 169 | } 170 | // Length 171 | let length = (algGP.lineMaxLength - algGP.lineMinLength) * Math.random() + algGP.lineMinLength; 172 | if(algGP.discreteLengths) { 173 | length = Math.round(length / algGP.discreteLengthOffset) * algGP.discreteLengthOffset; 174 | } 175 | // Position 176 | let x = algGP.currentX + length * Math.cos(angle); 177 | let y = algGP.currentY + length * Math.sin(angle); 178 | // Points 179 | let p1 = [algGP.currentX, algGP.currentY]; 180 | let p2 = [x, y]; 181 | // Check if valid 182 | if(algGP.checkValid(p1, p2)) { 183 | // console.log("Drawing Line"); 184 | algGP.drawLine(p1, p2); 185 | algGP.listOfLines[algGP.listOfLines.length] = [p1, p2, angle]; 186 | algGP.branchOfLines[algGP.branchOfLines.length] = [p1, p2, angle]; 187 | algGP.currentX = x; 188 | algGP.currentY = y; 189 | algGP.currentAngle = angle; 190 | return true; 191 | } else { 192 | return false; 193 | } 194 | } 195 | 196 | algGP.drawOneStep = function () { 197 | if(algGP.numOfSteps > algGP.maxNumOfSteps) { 198 | clearInterval(algGP.loop); 199 | return false; 200 | } 201 | for(let i = 0; i < algGP.numOfAttempts; i++) { 202 | if(algGP.attemptToDraw()) { 203 | algGP.numOfSteps++; 204 | return true; 205 | } 206 | } 207 | if(algGP.backtracking && algGP.branchOfLines.length > 0) { 208 | algGP.branchOfLines.pop(); 209 | if(algGP.branchOfLines.length == 0) { 210 | algGP.currentX = algGP.startX; 211 | algGP.currentY = algGP.startY; 212 | algGP.currentAngle = algGP.startAngle; 213 | } else { 214 | algGP.currentX = algGP.branchOfLines[algGP.branchOfLines.length - 1][0][0]; 215 | algGP.currentY = algGP.branchOfLines[algGP.branchOfLines.length - 1][0][1]; 216 | algGP.currentAngle = algGP.branchOfLines[algGP.branchOfLines.length - 1][2]; 217 | } 218 | return false; 219 | } else { 220 | clearInterval(algGP.loop); 221 | return false; 222 | } 223 | } 224 | 225 | algGP.reset = function () { 226 | // Check if already running 227 | algGP.pause(); 228 | // Initialize values 229 | algGP.numOfSteps = 0; 230 | algGP.currentX = algGP.startX; 231 | algGP.currentY = algGP.startY; 232 | algGP.currentAngle = algGP.startAngle; 233 | algGP.listOfLines = []; 234 | algGP.branchOfLines = []; 235 | } 236 | 237 | algGP.initialize = function() { 238 | algGP.reset(); 239 | } 240 | 241 | algGP.pause = function () { 242 | if("loop" in algGP) { 243 | clearInterval(algGP.loop); 244 | } 245 | } 246 | 247 | algGP.start = function () { 248 | algGP.loop = setInterval(algGP.drawOneStep, algGP.speed); 249 | } 250 | -------------------------------------------------------------------------------- /algGP_params.js: -------------------------------------------------------------------------------- 1 | /* Parameters */ 2 | algGP.speed = 0; 3 | algGP.maxNumOfSteps = 8000; 4 | algGP.numOfAttempts = 50; 5 | algGP.lineColor = "black"; 6 | algGP.lineWidth = 5; 7 | algGP.lineMaxLength = 50; 8 | algGP.lineMinLength = 5; 9 | algGP.angleMin = -pi; 10 | algGP.angleMax = pi; 11 | algGP.startX = 200; 12 | algGP.startY = 200; 13 | algGP.startAngle = 0; 14 | algGP.midpointRadius = 8; 15 | algGP.avoidIntersections = true; 16 | algGP.backtracking = true; 17 | algGP.discreteAngles = true; 18 | algGP.discreteAngleOffset = pi / 4; 19 | algGP.discreteLengths = false; 20 | algGP.discreteLengthOffset = 5; 21 | algGP.colorByLength = false; 22 | algGP.longColor = [0, 255, 0]; 23 | algGP.shortColor = [155, 0, 155]; 24 | algGP.colorByStep = false; 25 | algGP.stepInterval = 3000; 26 | algGP.initialColor = [255, 40, 150]; 27 | algGP.finalColor = [0, 255, 0]; 28 | algGP.colorByBranch = true; 29 | algGP.branchInterval = 1000; 30 | algGP.longBranchColor = [0, 255, 0]; 31 | algGP.shortBranchColor = [255, 0, 0]; 32 | algGP.randomColor = false; 33 | algGP.drawEndpoints = false; 34 | algGP.endpointRadius = 3; 35 | algGP.endpointColor = "gray"; 36 | algGP.rectanglesWidth = 50; 37 | algGP.rectanglesHeight = 50; 38 | algGP.rectanglesAlpha = 0.02; 39 | algGP.drawRectangles = false; 40 | algGP.circlesAlpha = 0.5; 41 | algGP.circlesRadius = 5; 42 | algGP.drawCircles = true; 43 | algGP.spiral = true; 44 | algGP.angleOffsetMax = pi / 2; 45 | algGP.angleOffsetMin = -pi / 8; 46 | -------------------------------------------------------------------------------- /algVD.js: -------------------------------------------------------------------------------- 1 | var algVD = {}; 2 | const shapes = ["circle", "square", "rectangle", "circle-fitted", "10PRINT", "diagonal"] 3 | const shapeModes = ["circle", "square", "rectangle", "circle-fitted", "10PRINT", "diagonal", "random", "randomEachSite", "random-unfitted", "random-fitted", "sqcircs"] // used for randomize() 4 | const expandingSiteModes = ["euclidean", "manhattan", "polarEuclidean", "polarManhattan", "polarHyperbolic", "wave", "minDiff", "absDiff", "approachingSite", "randomized", "sequential", "diffProd", "euclihattan", "chaos", "odd", "chaos2", "polarWave"] // used to initialize placement modes assigned to each site 5 | const placementModes = ["random", "random-norepeat", "expandingRadius", "shrinkingRadius", "randomWalk", "expandingSite", "sequential", "square", "triangle", "expanding-square"] 6 | const distances = ["euclidean", "manhattan", "hyperbolic", "wave", "minDiff", "absDiff", "diffProd", "euclihattan", "odd", "chaos", "chaos2", "polarWave", "polarEuclidean", "polarManhattan", "polarHyperbolic"] 7 | const fillModes = ["yes", "no", "random", "randomEachSite"]; 8 | const alphaModes = ["random", "closer", "farther", "fixed", "index", "reverse-index", "site-index", "reverse-site-index"] 9 | const gradientModes = ["center-out", "index", "top-bottom", "left-right", "diagonal"] 10 | const sitePlacementModes = ["random", "circle", "spiral", "triangle", "diamond", "parabola", "trbl-diagonal", "tlbr-diagonal", "modulus", "grid", "mapToCanvas"] 11 | 12 | algVD.setBackgroundColor = function(color) { 13 | ctx.beginPath(); 14 | ctx.fillStyle = color; 15 | ctx.globalAlpha = 1; 16 | ctx.rect(0, 0, width, height); 17 | ctx.fill(); 18 | ctx.closePath(); 19 | } 20 | 21 | algVD.randomColor = function() { 22 | let hex = ["#"]; 23 | for (let i = 0; i < 6; i++) { 24 | hex.push(Math.floor(Math.random()*16).toString(16)); 25 | } 26 | return hex.join(""); 27 | } 28 | 29 | algVD.hexToRGB = function(hex) { 30 | const r = parseInt(hex.slice(1, 3), 16); 31 | const g = parseInt(hex.slice(3, 5), 16); 32 | const b = parseInt(hex.slice(5, 7), 16); 33 | return [r, g, b]; 34 | } 35 | 36 | algVD.randColor = function() { 37 | return [Math.floor(256 * Math.random()), Math.floor(256 * Math.random()), Math.floor(256 * Math.random())]; 38 | } 39 | 40 | algVD.weightedColorAvg = function(weight, colorOne, colorTwo) { 41 | let r1 = colorOne[0]; 42 | let g1 = colorOne[1]; 43 | let b1 = colorOne[2]; 44 | let r2 = colorTwo[0]; 45 | let g2 = colorTwo[1]; 46 | let b2 = colorTwo[2]; 47 | let r = weight * r1 + (1 - weight) * r2; 48 | let g = weight * g1 + (1 - weight) * g2; 49 | let b = weight * b1 + (1 - weight) * b2; 50 | return [Math.floor(r), Math.floor(g), Math.floor(b)]; 51 | } 52 | 53 | algVD.getColorWeight = function (p1) { 54 | if (algVD.gradientMode === "center-out") { 55 | let p2 = {x: Math.floor(width/2), y: Math.floor(height/2)} 56 | let ret = algVD.distance(p1,p2,"euclidean") / algVD.distance({x: 0, y: 0},p2, "euclidean") 57 | return ret 58 | } else if (algVD.gradientMode === "index") { 59 | return p1.i / (algVD.numSites-1) 60 | } else if (algVD.gradientMode === "top-bottom") { 61 | return p1.y / height 62 | } else if (algVD.gradientMode === "left-right") { 63 | return p1.x / width 64 | } else if (algVD.gradientMode === "diagonal") { 65 | return Math.max(p1.x / width, p1.y / height) 66 | } 67 | } 68 | 69 | algVD.drawShape = function(shape, x, y, radius, color, alpha, fill) { 70 | let temp1 = width / algVD.cols; 71 | let temp2 = height / algVD.rows; 72 | if(algVD.lessTransparency) { 73 | ctx.globalAlpha = 0.5 + alpha / 2; 74 | } else { 75 | ctx.globalAlpha = alpha; 76 | } 77 | ctx.fillStyle = color; 78 | ctx.strokeStyle = color; 79 | ctx.beginPath(); 80 | if (shape == "square") { 81 | ctx.rect(x-radius/2,y-radius/2,radius,radius) 82 | } else if (shape == "rectangle") { 83 | ctx.rect(x-temp1/2,y-temp2/2,temp1,temp2) 84 | } else if (shape == "circle-fitted") { 85 | ctx.arc(x, y, Math.min(temp1/2,temp2/2), 0, 2 * Math.PI); 86 | } else if (shape === "diagonal" || shape === "10PRINT") { 87 | let bool = Math.floor(Math.random()*2) 88 | if (bool && shape === "10PRINT") { 89 | ctx.moveTo(x-temp1/2,y+temp2/2) 90 | ctx.lineTo(x+temp1/2,y-temp2/2) 91 | } else { 92 | ctx.moveTo(x-temp1/2,y-temp2/2) 93 | ctx.lineTo(x+temp1/2,y+temp2/2) 94 | } 95 | } else { 96 | ctx.arc(x, y, radius, 0, 2 * Math.PI); 97 | } 98 | ctx.closePath(); 99 | if (fill) { 100 | ctx.fill(); 101 | } 102 | ctx.stroke() 103 | } 104 | 105 | algVD.getFill = function(site) { 106 | if (algVD.fill == "yes") { 107 | return true; 108 | } else if (algVD.fill == "no") { 109 | return false; 110 | } else if (algVD.fill == "random") { 111 | return Math.floor(Math.random() * 2); 112 | } else if (algVD.fill == "randomEachSite") { 113 | return site.fill; 114 | } 115 | } 116 | 117 | algVD.getShape = function(site) { 118 | if (algVD.shape === "random") { 119 | let choice = Math.floor(Math.random() * shapes.length); 120 | return shapes[choice]; 121 | } else if (algVD.shape === "randomEachSite") { 122 | return site.shape; 123 | } else if (algVD.shape === "random-unfitted") { 124 | let choice = Math.floor(Math.random() * 2); 125 | return shapes[choice]; 126 | } else if (algVD.shape === "random-fitted") { 127 | let choice = Math.floor(Math.random() * 2) + 2; 128 | return shapes[choice]; 129 | } else if (algVD.shape === "sqcircs") { 130 | let choice = Math.floor(Math.random() * 4); 131 | return shapes[choice]; 132 | } else { 133 | return algVD.shape; 134 | } 135 | } 136 | 137 | algVD.distance = function(p1,p2, space) { 138 | if (space === "manhattan") { 139 | return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y); 140 | } else if (space === "hyperbolic") { 141 | const modulus1 = Math.sqrt(p1.x * p1.x + p1.y * p1.y); 142 | const modulus2 = Math.sqrt(p2.x * p2.x + p2.y * p2.y); 143 | const diffX = p1.x - p2.x; 144 | const diffY = p1.y - p2.y; 145 | const distance = Math.acosh(1 + (2 * (diffX * diffX + diffY * diffY)) / ((1 - modulus1 * modulus1) * (1 - modulus2 * modulus2))); 146 | return distance; 147 | } else if (space === "euclihattan") { 148 | return Math.abs(Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y) + Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2))) 149 | } else if (space === "polarEuclidean") { 150 | p1x = Math.cos(p1.x) 151 | p2x = Math.cos(p2.x) 152 | p1y = Math.sin(p1.y) 153 | p2y = Math.sin(p2.y) 154 | 155 | return Math.sqrt(Math.pow(p1x - p2x,2) + Math.pow(p1y - p2y,2)); 156 | } else if (space === "polarManhattan") { 157 | p1x = Math.cos(p1.x) 158 | p2x = Math.cos(p2.x) 159 | p1y = Math.sin(p1.y) 160 | p2y = Math.sin(p2.y) 161 | 162 | return Math.abs(p1x - p2x) + Math.abs(p1y - p2y); 163 | } else if (space === "polarHyperbolic") { 164 | p1x = Math.cos(p1.x) 165 | p2x = Math.cos(p2.x) 166 | p1y = Math.sin(p1.y) 167 | p2y = Math.sin(p2.y) 168 | 169 | const modulus1 = Math.sqrt(p1x * p1x + p1y * p1y); 170 | const modulus2 = Math.sqrt(p2x * p2x + p2y * p2y); 171 | const diffX = p1x - p2x; 172 | const diffY = p1y - p2y; 173 | const distance = Math.acosh(1 + (2 * (diffX * diffX + diffY * diffY)) / ((1 - modulus1 * modulus1) * (1 - modulus2 * modulus2))); 174 | return distance; 175 | } else if (space === "wave" || space === "polarWave") { 176 | 177 | p1x = (p1.x) 178 | p2x = (p2.x) 179 | p1y = (p1.y) 180 | p2y = (p2.y) 181 | if (space === "polarWave") { 182 | p1x = Math.cos(p1.x) 183 | p2x = Math.cos(p2.x) 184 | p1y = Math.sin(p1.y) 185 | p2y = Math.sin(p2.y) 186 | } 187 | return Math.abs(Math.sqrt(Math.abs(p1x - p2x))*((p1x - p2x)/300) - Math.sqrt(Math.abs(p1y - p2y))*((p1y - p2y)/300)) 188 | } else if (space === "minDiff") { 189 | let diffX = Math.abs(p1.x - p2.x) 190 | let diffY = Math.abs(p1.y - p2.y) 191 | 192 | return Math.min(Math.abs(diffX), Math.abs(diffY)) 193 | } else if (space === "absDiff") { 194 | let diffX = Math.abs(p1.x - p2.x) 195 | let diffY = Math.abs(p1.y - p2.y) 196 | 197 | return Math.abs(Math.abs(diffX) - Math.abs(diffY)) 198 | } else if (space === "diffProd") { 199 | let diffX = Math.abs(p1.x - p2.x) 200 | let diffY = Math.abs(p1.y - p2.y) 201 | 202 | return diffX * diffY 203 | } else if (space === "chaos") { 204 | return Math.abs(Math.sqrt(Math.abs(p1.x - p2.x))*(p1.x + p2.x)/1200 - Math.sqrt(Math.abs(p1.y - p2.y))*(p1.y + p2.y)/1200) 205 | } else if (space === "odd") { 206 | return Math.abs(p1.y - p2.y) + 16*Math.sqrt(Math.abs(p1.x - p2.x)) 207 | } else if (space === "chaos2") { 208 | return Math.abs(Math.abs(p1.y - p2.y) - 16*Math.sqrt(Math.abs(p1.x - p2.x))) 209 | } 210 | return Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2)); 211 | } 212 | 213 | algVD.mapValue = function (value, range1, range2) { 214 | return range2[0] + (((value-range1[0])*(range2[1]-range2[0]))/(range1[1]-range1[0])) 215 | } 216 | 217 | algVD.calculateAlpha = function (point, site) { 218 | let space = algVD.alphaSpace 219 | let distance = algVD.distance(point, site, space) 220 | if (algVD.varyAlphaMode === "random") { 221 | return Math.random() 222 | } else if (algVD.varyAlphaMode === "closer") { 223 | let maxDistPoint = site; 224 | let maxPossibleDist = 0; 225 | for (let i = 0; i < algVD.rows; i++) { 226 | for (let j = 0; j < algVD.cols; j++) { 227 | let tempPoint = {x: j, y: i} 228 | let tempDist = algVD.distance(maxDistPoint, tempPoint) 229 | if (tempDist > maxPossibleDist) { 230 | maxPossibleDist = tempDist 231 | maxDistPoint = tempPoint 232 | } 233 | } 234 | } 235 | let stdDistance = Math.max(1 - distance / maxPossibleDist - 0.2, 0.2) 236 | return stdDistance; 237 | } else if (algVD.varyAlphaMode === "farther") { 238 | let maxDistPoint = site; 239 | let maxPossibleDist = 0; 240 | for (let i = 0; i < algVD.rows; i++) { 241 | for (let j = 0; j < algVD.cols; j++) { 242 | let tempPoint = {x: j, y: i} 243 | let tempDist = algVD.distance(maxDistPoint, tempPoint) 244 | if (tempDist > maxPossibleDist) { 245 | maxPossibleDist = tempDist 246 | maxDistPoint = tempPoint 247 | } 248 | } 249 | } 250 | let stdDistance = distance / maxPossibleDist 251 | return stdDistance; 252 | } else if (algVD.varyAlphaMode === "index") { 253 | return algVD.numOfStepsTemp / (algVD.rows * algVD.cols) 254 | } else if (algVD.varyAlphaMode === "reverse-index") { 255 | return 1 - algVD.numOfStepsTemp / (algVD.rows * algVD.cols) 256 | } else if (algVD.varyAlphaMode === "site-index") { 257 | let siteIndex = algVD.sites.findIndex(item => item.x == site.x && item.y == site.y) 258 | return (siteIndex+1) / algVD.sites.length 259 | } else if (algVD.varyAlphaMode === "reverse-site-index") { 260 | let siteIndex = algVD.sites.findIndex(item => item.x == site.x && item.y == site.y) 261 | return 1 - (siteIndex+1) / algVD.sites.length 262 | } else { 263 | return algVD.alphaMultiplier 264 | } 265 | } 266 | 267 | algVD.setCoords = function () { 268 | let x; 269 | let y; 270 | if (algVD.placementMode === "random") { 271 | x = Math.floor(Math.random() * (algVD.cols+1)) 272 | y = Math.floor(Math.random() * (algVD.rows+1)) 273 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 274 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 275 | } else if (algVD.placementMode === "random-norepeat" && algVD.rowColGraph.length > 0) { 276 | let index = Math.floor(Math.random() * (algVD.rowColGraph.length)) 277 | x = algVD.rowColGraph[index].x 278 | y = algVD.rowColGraph[index].y 279 | algVD.rowColGraph.splice(index, 1) 280 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 281 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 282 | } else if (algVD.placementMode === "expandingRadius" && algVD.rowColGraph.length > 0) { 283 | let index = 1; 284 | let minDistIndex = 0 285 | let space = algVD.expandShrinkDist 286 | 287 | for (index; index < algVD.rowColGraph.length; index++) { 288 | let dist = algVD.distance(algVD.rowColGraph[index], algVD.ESRfocus, space) 289 | if (dist < algVD.distance(algVD.rowColGraph[minDistIndex], algVD.ESRfocus, space)) { 290 | minDistIndex = index 291 | } 292 | } 293 | x = algVD.rowColGraph[minDistIndex].x 294 | y = algVD.rowColGraph[minDistIndex].y 295 | algVD.rowColGraph.splice(minDistIndex, 1) 296 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 297 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 298 | } else if (algVD.placementMode === "shrinkingRadius" && algVD.rowColGraph.length > 0) { 299 | let index = 1; 300 | let maxDistIndex = 0 301 | let space = algVD.expandShrinkDist 302 | for (index; index < algVD.rowColGraph.length; index++) { 303 | let dist = algVD.distance(algVD.rowColGraph[index], algVD.ESRfocus, space) 304 | if (dist > algVD.distance(algVD.rowColGraph[maxDistIndex], algVD.ESRfocus, space)) { 305 | maxDistIndex = index 306 | } 307 | } 308 | x = algVD.rowColGraph[maxDistIndex].x 309 | y = algVD.rowColGraph[maxDistIndex].y 310 | algVD.rowColGraph.splice(maxDistIndex, 1) 311 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 312 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 313 | } else if (algVD.placementMode === "randomWalk" && algVD.rowColGraph.length > 0) { 314 | let index = algVD.rowColGraph.length-1; 315 | let minDistIndex = algVD.rowColGraph.length-1 316 | for (index; index >= 0; index--) { 317 | let dist = algVD.distance(algVD.rowColGraph[index], algVD.randomWalkCoords, "euclidean") 318 | if (dist < algVD.distance(algVD.rowColGraph[minDistIndex], algVD.randomWalkCoords, "euclidean")) { 319 | minDistIndex = index 320 | } else if (dist === algVD.distance(algVD.rowColGraph[minDistIndex], algVD.randomWalkCoords, "euclidean")) { 321 | if (Math.floor(Math.random() * 2)) { 322 | minDistIndex = index 323 | } 324 | } 325 | } 326 | x = algVD.rowColGraph[minDistIndex].x 327 | y = algVD.rowColGraph[minDistIndex].y 328 | algVD.rowColGraph.splice(minDistIndex, 1) 329 | algVD.randomWalkCoords.x = x 330 | algVD.randomWalkCoords.y = y 331 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 332 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 333 | 334 | } else if (algVD.placementMode === "expandingSite" && algVD.rowColGraph.length > 0) { 335 | for (let i = 0; i < algVD.sites.length; i++) { 336 | 337 | let currentSiteIndex = ((algVD.expandingSitePerSite ? 0 : algVD.numOfStepsTemp)+i) % algVD.sites.length 338 | let currentSite = algVD.sites[currentSiteIndex] 339 | let siteX = algVD.mapValue(currentSite.x, [0,width], [0,algVD.cols]) 340 | let siteY = algVD.mapValue(currentSite.y, [0,height], [0,algVD.rows]) 341 | let newSiteCoords = {x: siteX, y: siteY} 342 | 343 | const points = algVD.rowColGraph.filter(point => point.siteIndex === currentSiteIndex) 344 | 345 | if (points.length > 0) { 346 | let closestPoint = points[0] 347 | 348 | for (let j = 0; j < points.length; j++) { 349 | let placementWithinSiteMode = algVD.expandingSiteDistance==="random" ? currentSite.mode : algVD.expandingSiteDistance 350 | let minDist = algVD.distance(closestPoint, newSiteCoords, placementWithinSiteMode) 351 | let currDist = algVD.distance(points[j], newSiteCoords, placementWithinSiteMode) 352 | 353 | let check1 = closestPoint.y > points[j].y 354 | let check3 = closestPoint.y <= points[j].y 355 | let check2 = closestPoint.x >= points[j].x 356 | let check4 = closestPoint.x < points[j].x 357 | if (placementWithinSiteMode==="approachingSite"&&(algVD.numOfStepsTemp % 4 == 0 ? check1 : algVD.numOfStepsTemp % 4 == 1 ? check2 : algVD.numOfStepsTemp % 4 == 2 ? check3 : check4)) { 358 | closestPoint = points[j] 359 | } else if (placementWithinSiteMode === "randomized") { 360 | closestPoint = points[Math.floor(Math.random()*points.length)] 361 | } else if (placementWithinSiteMode === "sequential" && check1) { 362 | closestPoint = points[j] 363 | } else if (placementWithinSiteMode!=="approachingSite" && placementWithinSiteMode !=="sequential") { 364 | if (!algVD.expandingSiteReverse && currDist <= minDist) { 365 | closestPoint = points[j] 366 | } else if (algVD.expandingSiteReverse && currDist >= minDist) { 367 | closestPoint = points[j] 368 | } 369 | } 370 | 371 | } 372 | x = closestPoint.x 373 | y = closestPoint.y 374 | let index = algVD.rowColGraph.findIndex(point => point.x === x && point.y === y) 375 | algVD.rowColGraph.splice(index, 1) 376 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 377 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 378 | 379 | algVD.drawShape(algVD.getShape(currentSite), x, y, currentSite.radius, currentSite.color, algVD.calculateAlpha({x: x, y: y}, currentSite), algVD.getFill(currentSite)) 380 | 381 | return; 382 | } 383 | } 384 | } else if (algVD.placementMode === "movingSites" && algVD.numOfStepsTemp < algVD.rowColGraph.length) { 385 | let index = algVD.numOfStepsTemp 386 | x = algVD.rowColGraph[index].x 387 | y = algVD.rowColGraph[index].y 388 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 389 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 390 | } else if (algVD.placementMode === "sequential" && algVD.rowColGraph.length > 0) { 391 | 392 | let index = 0 393 | x = algVD.rowColGraph[index].x 394 | y = algVD.rowColGraph[index].y 395 | algVD.rowColGraph.splice(index, 1) 396 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 397 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 398 | } else if (algVD.placementMode === "square" && algVD.rowColGraph.length > 0) { 399 | let j 400 | const points = algVD.rowColGraph 401 | if (points.length > 0) { 402 | let closestPoint = points[0] 403 | for (j = 0; j < points.length; j++) { 404 | let check1 = closestPoint.y > points[j].y 405 | let check3 = closestPoint.y <= points[j].y 406 | let check2 = closestPoint.x >= points[j].x 407 | let check4 = closestPoint.x < points[j].x 408 | if ((algVD.numOfStepsTemp % 4 == 0 ? check1 : algVD.numOfStepsTemp % 4 == 1 ? check2 : algVD.numOfStepsTemp % 4 == 2 ? check3 : check4)) { 409 | closestPoint = points[j] 410 | } 411 | } 412 | x = closestPoint.x 413 | y = closestPoint.y 414 | let index = algVD.rowColGraph.findIndex(item => item.x === x && item.y === y) 415 | algVD.rowColGraph.splice(index, 1) 416 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 417 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 418 | } 419 | } else if (algVD.placementMode === "triangle" && algVD.rowColGraph.length > 0) { 420 | const points = algVD.rowColGraph; 421 | let resultPoint = points[0] 422 | for (let j = 1; j < points.length; j++) { 423 | if (!(points[j].x > resultPoint.y)) { 424 | resultPoint = points[j] 425 | } 426 | } 427 | x = resultPoint.x 428 | y = resultPoint.y 429 | let index = algVD.rowColGraph.findIndex(item => item.x === x && item.y === y) 430 | algVD.rowColGraph.splice(index, 1) 431 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 432 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 433 | } else if (algVD.placementMode === "expanding-square" && algVD.rowColGraph.length > 0) { 434 | const points = algVD.rowColGraph; 435 | let resultPoint = points[0] 436 | for (let j = 1; j < points.length; j++) { 437 | if ((points[j].x > resultPoint.y)) { 438 | resultPoint = points[j] 439 | } 440 | } 441 | x = resultPoint.x 442 | y = resultPoint.y 443 | let index = algVD.rowColGraph.findIndex(item => item.x === x && item.y === y) 444 | algVD.rowColGraph.splice(index, 1) 445 | x = algVD.mapValue(x,[0,algVD.cols-1],[0,width]) 446 | y = algVD.mapValue(y,[0,algVD.rows-1],[0,height]) 447 | } 448 | 449 | algVD.x = x 450 | algVD.y = y 451 | } 452 | 453 | algVD.drawOneStep = function () { 454 | if(algVD.numOfSteps > algVD.maxNumOfSteps) { 455 | clearInterval(algVD.loop); 456 | return false; 457 | } else { 458 | if (algVD.placementMode === "movingSites") { 459 | for (let q = 0; q < algVD.rows * algVD.cols; q++) { 460 | algVD.setCoords() 461 | 462 | let x = algVD.x; 463 | let y = algVD.y; 464 | 465 | let closest = algVD.sites[0]; 466 | let minDist = algVD.distance({x: x, y: y}, algVD.sites[0], algVD.distanceSpace) 467 | for (let i = 1; i < algVD.sites.length; i++) { 468 | const dist = algVD.distance({x: x, y: y}, algVD.sites[i], algVD.distanceSpace) 469 | if (dist < minDist) { 470 | minDist = dist 471 | closest = algVD.sites[i]; 472 | } 473 | } 474 | algVD.drawShape(algVD.getShape(closest), x, y, closest.radius, closest.color, algVD.calculateAlpha({x: x, y:y}, closest), algVD.getFill(closest)) 475 | algVD.numOfStepsTemp++; 476 | 477 | } 478 | 479 | for (let j = 0; j < algVD.sites.length; j++) { 480 | let dx = Math.ceil(Math.random()*3 - 2) 481 | let dy = Math.ceil(Math.random()*3 - 2) 482 | algVD.sites[j].x += dx 483 | algVD.sites[j].y += dy 484 | } 485 | algVD.numOfStepsTemp = 0; 486 | } else { 487 | algVD.setCoords(); 488 | 489 | let x = algVD.x; 490 | let y = algVD.y; 491 | if (x !== undefined && y !== undefined) { 492 | let closest = algVD.sites[0]; 493 | let minDist = algVD.distance({x: x, y: y}, algVD.sites[0], algVD.distanceSpace) 494 | for (let i = 1; i < algVD.sites.length; i++) { 495 | const dist = algVD.distance({x: x, y: y}, algVD.sites[i], algVD.distanceSpace) 496 | if (dist < minDist) { 497 | minDist = dist 498 | closest = algVD.sites[i]; 499 | } 500 | } 501 | algVD.drawShape(algVD.getShape(closest), x, y, closest.radius, closest.color, algVD.calculateAlpha({x:x, y:y}, closest), algVD.getFill(closest)) 502 | } 503 | } 504 | 505 | if (algVD.displaySites) { 506 | for (let i = 0;i < algVD.sites.length; i++) { 507 | algVD.drawShape("circle", algVD.sites[i].x, algVD.sites[i].y, algVD.siteRadius, "black", algVD.siteAlpha, algVD.siteFill) 508 | } 509 | } 510 | algVD.numOfSteps++; 511 | if (algVD.placementMode !== "movingSites") { 512 | algVD.numOfStepsTemp++; 513 | } 514 | } 515 | } 516 | 517 | algVD.findClosestSiteToIndex = function(currPoint) { 518 | let tempNearestSiteIndex = 0; 519 | let x = algVD.mapValue(currPoint.x,[0,algVD.cols-1],[0,width]) 520 | let y = algVD.mapValue(currPoint.y,[0,algVD.rows-1],[0,height]) 521 | let tempCurrPoint = {x: x, y: y} 522 | 523 | for (let k = 1; k < algVD.sites.length; k++) { 524 | let tempNearestSite = algVD.sites[tempNearestSiteIndex]; 525 | // let nearestSite = algVD.sites[tempNearestSiteIndex]; 526 | 527 | // let tempNearestSite = { 528 | // x: algVD.mapValue(nearestSite.x,[0,width],[-1,1]), 529 | // y: algVD.mapValue(nearestSite.y,[0,height],[-1,1]) 530 | // } 531 | 532 | let tempMinDist = algVD.distance(tempNearestSite,tempCurrPoint, algVD.distanceSpace) 533 | 534 | let tempCurrSite = algVD.sites[k]; 535 | 536 | let tempCurrDist = algVD.distance(tempCurrSite, tempCurrPoint, algVD.distanceSpace) 537 | 538 | if (tempCurrDist <= tempMinDist) { 539 | tempNearestSiteIndex = k 540 | } 541 | } 542 | return tempNearestSiteIndex 543 | } 544 | 545 | algVD.reset = function () { 546 | // Check if already running 547 | algVD.pause(); 548 | // Initialize values 549 | algVD.numOfSteps = 0; 550 | // Set line width 551 | ctx.lineWidth = Math.min(width, height) * 0.0025; 552 | 553 | algVD.numOfStepsTemp = 0; 554 | algVD.sites = []; 555 | 556 | if (algVD.randomGradientColors) { 557 | algVD.gradientColor1 = algVD.randColor() 558 | algVD.gradientColor2 = algVD.randColor() 559 | 560 | let rDelta = Math.abs(algVD.gradientColor2[0] - algVD.gradientColor1[0]) 561 | let gDelta = Math.abs(algVD.gradientColor2[1] - algVD.gradientColor1[1]) 562 | let bDelta = Math.abs(algVD.gradientColor2[2] - algVD.gradientColor1[2]) 563 | while (rDelta + gDelta + bDelta < 200) { 564 | algVD.gradientColor2 = algVD.randColor() 565 | rDelta = Math.abs(algVD.gradientColor2[0] - algVD.gradientColor1[0]) 566 | gDelta = Math.abs(algVD.gradientColor2[1] - algVD.gradientColor1[1]) 567 | bDelta = Math.abs(algVD.gradientColor2[2] - algVD.gradientColor1[2]) 568 | } 569 | } 570 | 571 | let rwx = Math.floor(Math.random() * (algVD.cols+1)) 572 | let rwy = Math.floor(Math.random() * (algVD.rows+1)) 573 | algVD.randomWalkCoords = {x: rwx, y: rwy} 574 | 575 | for (let i = 0; i < algVD.numSites; i++) { 576 | let x,y 577 | if (algVD.sitePlacement === "mapToCanvas") { 578 | let mapped = algVD.mapValue(i, [0, algVD.numSites-1], [0, width * height]) 579 | y = mapped / (width); 580 | x = mapped % (width); 581 | } else if (algVD.sitePlacement === "triangle") { 582 | let min = Math.min(width, height); 583 | let minLo = min / 4; 584 | let minHi = min - minLo; 585 | 586 | if (i % 3 === 0) { 587 | let mapped = algVD.mapValue(i, [0, algVD.numSites-1], [minLo, minHi]); 588 | x = mapped; 589 | y = minHi; 590 | } else if (i % 3 === 1) { 591 | let mapped = algVD.mapValue(i, [0, algVD.numSites-1], [minLo, minHi]); 592 | y = min - mapped; 593 | x = minLo/2 + mapped / 2; 594 | } else { 595 | let mapped = algVD.mapValue(i, [0, algVD.numSites-1], [minLo, minHi]); 596 | y = min - mapped; 597 | x = minHi + minLo/2 - mapped / 2; 598 | } 599 | 600 | 601 | // let mapped = algVD.mapValue(i, [0, algVD.numSites-1], [0, width]) 602 | // x = mapped 603 | // y = i%2? Math.abs(x - 300) + 300 : -Math.abs(x - 300) + 300 // x 604 | // y = i%2? Math.abs(x - 300) : -Math.abs(x - 300) + 600 // <> 605 | // y = i%2? Math.abs(x - 300) + 150 : -Math.abs(x - 300) + 450 // ><>< 606 | // y = -Math.abs(x - 300) + 450 // v 607 | // y = Math.abs(x - 300) + 150 // ^ 608 | // y = Math.sin(x) // sin upside down parabola, cos s 609 | // y = algVD.mapValue(y, [0, 1], [150, 450]) + 150 610 | 611 | } else if (algVD.sitePlacement === "tlbr-diagonal") { 612 | let minDim = Math.min(height,width) 613 | let mapped = algVD.mapValue(i, [0, algVD.numSites-1], [0, minDim]) 614 | x = mapped 615 | y = mapped 616 | 617 | } else if (algVD.sitePlacement === "trbl-diagonal") { 618 | let minDim = Math.min(height,width) 619 | let mapped = algVD.mapValue(i, [0, algVD.numSites-1], [0, minDim]) 620 | x = width - mapped 621 | y = mapped 622 | } else if (algVD.sitePlacement === "circle") { 623 | let r = width / 3 624 | let theta = i 625 | x = r * Math.cos(theta) + Math.floor(width / 2) 626 | y = r * Math.sin(theta) + Math.floor(height / 2) 627 | } else if (algVD.sitePlacement === "spiral") { 628 | let r = algVD.mapValue(i, [0,algVD.numSites], [20, width/2]) 629 | let theta = i 630 | x = r * Math.cos(theta) + Math.floor(width / 2) 631 | y = r * Math.sin(theta) + Math.floor(height / 2) 632 | } else if (algVD.sitePlacement === "diamond") { 633 | let r = width / 3 634 | let theta = i 635 | x = r * Math.tan(theta) + Math.floor(width / 2) 636 | y = r * Math.sin(theta) + Math.floor(height / 2) 637 | } else if (algVD.sitePlacement === "parabola") { 638 | let r = width / 3 639 | let theta = i 640 | x = r * Math.cos(theta) + Math.floor(width / 2) 641 | y = r * Math.sin(theta)*Math.sin(theta) + Math.floor(height / 2) 642 | } else if (algVD.sitePlacement === "modulus") { 643 | x = i % algVD.siteGridDim 644 | y = Math.floor(i / algVD.siteGridDim) 645 | let xmod = i % algVD.sitePlacementModulusX 646 | let ymod = i % algVD.sitePlacementModulusY 647 | x = algVD.mapValue(x,[-1 + xmod, algVD.siteGridDim - xmod],[0,width]) 648 | y = algVD.mapValue(y,[-1 + ymod, algVD.siteGridDim - ymod],[0,height]) 649 | } else if (algVD.sitePlacement === "grid" && i < algVD.siteGridDim*algVD.siteGridDim) { 650 | x = i % algVD.siteGridDim 651 | y = Math.floor(i / algVD.siteGridDim) 652 | x = algVD.mapValue(x,[0,algVD.siteGridDim-1],[0.5*width/algVD.siteGridDim,width - 0.5*width/algVD.siteGridDim]) 653 | y = algVD.mapValue(y,[0,algVD.siteGridDim-1],[0.5*height/algVD.siteGridDim,height - 0.5*height/algVD.siteGridDim]) 654 | } else if (algVD.sitePlacement === "random") { 655 | x = Math.floor(width * Math.random()); 656 | y = Math.floor(height * Math.random()); 657 | } 658 | 659 | let rand = algVD.randColor() 660 | 661 | if (algVD.gradient) { 662 | let col1 = algVD.randomGradientColors ? algVD.gradientColor1 : algVD.hexToRGB(algVD.gradientColor1) 663 | let col2 = algVD.randomGradientColors ? algVD.gradientColor2 : algVD.hexToRGB(algVD.gradientColor2) 664 | rand = algVD.weightedColorAvg(algVD.getColorWeight({x: x, y: y, i: i}),col1,col2) 665 | } 666 | 667 | let shapeIndex = Math.floor(Math.random() * (shapes.length)-1) 668 | if (Math.random() < 0.5) { 669 | shapeIndex = Math.floor(Math.random() * (shapes.length)-2) 670 | } 671 | let placementModeIndex = Math.floor(Math.random() * (expandingSiteModes.length)) 672 | let fill = Math.floor(Math.random() * 2) 673 | 674 | algVD.sites.push({x: x, y: y, radius: algVD.circleRadius, color: "rgb(" + rand[0] + "," + rand[1] + "," + rand[2] + ")", shape: shapes[shapeIndex], fill: fill, mode: expandingSiteModes[placementModeIndex]}) 675 | } 676 | algVD.rowColGraph = []; 677 | for (let i = 0; i < algVD.rows * algVD.cols; i++) { 678 | let closestSiteIndex = algVD.findClosestSiteToIndex({x: i % algVD.cols, y: Math.floor(i / algVD.cols)}) 679 | algVD.rowColGraph.push({x: i % algVD.cols, y: Math.floor(i / algVD.cols), siteIndex: closestSiteIndex}) 680 | } 681 | 682 | algVD.expandShrinkDist = algVD.expandShrinkDist === "random" ? distances[Math.floor(Math.random() * distances.length)] : algVD.expandShrinkDist 683 | 684 | algVD.ESRfocus = {x: algVD.expandShrinkx ===-1 ? Math.floor(Math.random() * algVD.cols) : algVD.expandShrinkx, y: algVD.expandShrinky === -1 ? Math.floor(Math.random() * algVD.rows) : algVD.expandShrinky} 685 | 686 | } 687 | 688 | algVD.initialize = function() { 689 | algVD.reset() 690 | } 691 | 692 | algVD.pause = function () { 693 | if("loop" in algVD) { 694 | clearInterval(algVD.loop); 695 | } 696 | } 697 | 698 | algVD.start = function () { 699 | if (algVD.backgroundColor !== "#FFFFFF") { 700 | algVD.setBackgroundColor(algVD.backgroundColor); 701 | } 702 | 703 | algVD.loop = setInterval(algVD.drawOneStep, algVD.speed); 704 | } 705 | 706 | algVD.loadDefaultParams = function () { 707 | algVD.circleRadius = 3; 708 | algVD.numSites = 25; 709 | algVD.placementMode = "expandingSite"; // random, random-norepeat, expandingRadius, shrinkingRadius, randomWalk, expandingSite, movingSites, sequential, square 710 | algVD.fill = "yes"; // yes, no, random, randomEachSite 711 | algVD.varyAlphaMode = "fixed" ;// random, closer, farther, fixed 712 | algVD.alphaMultiplier = 0.95; 713 | algVD.displaySites = true; 714 | algVD.siteRadius = 4; 715 | algVD.siteAlpha = 1; 716 | algVD.rows = 100; 717 | algVD.cols = 100; 718 | algVD.distanceSpace = "euclidean" // euclidian, manhattan, hyperbolic, euclihattan, polarEuclidean, polarManhattan, polarHyperbolic, wave, absDiff, minDiff, diffProd, chaos, odd, chaos2 719 | algVD.gradientColor1 = "#FF0000" 720 | algVD.gradientColor2 = "#0000FF" 721 | algVD.gradient = false; 722 | algVD.gradientMode = "center-out" // center-out, index, top-bototm, left-right, diagonal 723 | algVD.randomGradientColors = false; 724 | algVD.sitePlacement = "modulus"; // mapToCanvas, circle, spiral, diamond, parabola, modulus, grid, random 725 | algVD.siteGridDim = 5 // for sitePlacement == grid 726 | algVD.shape = "10PRINT" // circle, square, rectangle, circle-fitted random, randomEachSite, random-unfitted, random-fitted 727 | algVD.expandingSiteDistance = "random" // for expandingSite placement mode: random, randomized, approachingSite, sequential, euclidean, manhattan, polarEuclidean, polarManhattan, polarHyperbolic, wave, minDiff, absDiff, diffProd, euclihattan, chaos, odd, chaos2 728 | algVD.expandingSitePerSite = true; // true - fill current site before moving on to the next one 729 | algVD.expandingSiteReverse = false; // false = minDist, true = maxDist 730 | algVD.sitePlacementModulusX = 2 // for modulus site placement 731 | algVD.sitePlacementModulusY = 2 // for modulus site placement 732 | algVD.siteFill = true 733 | algVD.alphaSpace = "euclidean" // any of the different distances 734 | algVD.expandShrinkDist = "euclidean" 735 | algVD.expandShrinkx = -1 736 | algVD.expandShrinky = -1 737 | } 738 | -------------------------------------------------------------------------------- /algVD_params.js: -------------------------------------------------------------------------------- 1 | /* Parameters */ 2 | algVD.speed = 0; 3 | algVD.maxNumOfSteps = 10000; 4 | 5 | // Site Parameters 6 | algVD.displaySites = true; 7 | algVD.numSites = 25; 8 | algVD.siteRadius = 4; 9 | algVD.siteAlpha = 1; 10 | algVD.siteFill = true 11 | algVD.sitePlacement = "modulus"; // mapToCanvas, circle, spiral, diamond, parabola, modulus, grid, random 12 | algVD.siteGridDim = 5 // for sitePlacement == grid 13 | algVD.sitePlacementModulusX = 2 // for modulus site placement 14 | algVD.sitePlacementModulusY = 2 // for modulus site placement 15 | 16 | // Grid 17 | algVD.rows = 100; 18 | algVD.cols = 100; 19 | algVD.backgroundColor = "#FFFFFF"; 20 | 21 | // Point Placement Parameters 22 | algVD.distanceSpace = "euclidean" // euclidian, manhattan, hyperbolic, euclihattan, polarEuclidean, polarManhattan, polarHyperbolic, wave, polarWave, absDiff, minDiff, diffProd, chaos, odd, chaos2 23 | algVD.placementMode = "expandingSite"; // random, random-norepeat, expandingRadius, shrinkingRadius, randomWalk, expandingSite, movingSites, sequential, square 24 | algVD.expandingSiteDistance = "random" // for expandingSite placement mode: random, randomized, approachingSite, sequential, euclidean, manhattan, polarEuclidean, polarManhattan, polarHyperbolic, wave, minDiff, absDiff, diffProd, euclihattan, chaos, odd, chaos2 25 | algVD.expandingSitePerSite = true; // true - fill current site before moving on to the next one 26 | algVD.expandingSiteReverse = false; // false = minDist, true = maxDist 27 | algVD.expandShrinkDist = "euclidean" // For expanding or shrinking radius placement mode 28 | algVD.expandShrinkx = -1 29 | algVD.expandShrinky = -1 30 | 31 | // Point Properties 32 | algVD.circleRadius = 3; // used for other shapes as well (square) 33 | algVD.shape = "10PRINT" // circle, square, rectangle, circle-fitted random, randomEachSite, random-unfitted, random-fitted 34 | algVD.fill = "yes"; // yes, no, random, randomEachSite 35 | 36 | algVD.varyAlphaMode = "fixed" ;// random, closer, farther, fixed 37 | algVD.alphaSpace = "euclidean" // any of the different distances 38 | algVD.alphaMultiplier = 0.95; 39 | 40 | algVD.gradient = false; 41 | algVD.randomGradientColors = false; 42 | algVD.gradientMode = "center-out"; // center-out, index, top-bototm, left-right, diagonal 43 | algVD.gradientColor1 = "#FF0000"; 44 | algVD.gradientColor2 = "#0000FF"; 45 | 46 | // Added by Mike for less transparency 47 | algVD.lessTransparency = false; 48 | -------------------------------------------------------------------------------- /algVines.js: -------------------------------------------------------------------------------- 1 | var algVines = {}; 2 | 3 | algVines.distance = function (p1, p2) { 4 | return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1])); 5 | } 6 | 7 | algVines.getRandomColor = function() { 8 | let r = Math.floor(256 * Math.random()); 9 | let g = Math.floor(256 * Math.random()); 10 | let b = Math.floor(256 * Math.random()); 11 | return "rgb(" + r + "," + g + "," + b + ")"; 12 | } 13 | 14 | algVines.drawFilledCircle = function(x, y, radius, shape_color, shape_opacity) { 15 | ctx.fillStyle = shape_color; 16 | ctx.globalAlpha = shape_opacity; 17 | ctx.beginPath(); 18 | ctx.arc(x, y, radius, 0, 2 * Math.PI); 19 | ctx.closePath(); 20 | ctx.fill(); 21 | } 22 | 23 | algVines.drawTriangle = function (x, y, length, lineWidth, radians, shape_color, shape_opacity) { 24 | let height = length * Math.sqrt(3) / 2; 25 | let triangle_radius = length * Math.sqrt(3) / 3; 26 | ctx.lineWidth = lineWidth; 27 | ctx.strokeStyle = shape_color; 28 | ctx.globalAlpha = shape_opacity; 29 | ctx.beginPath(); 30 | if(algVines.rotation) { 31 | // console.log(algVines.rotationRads); 32 | let moveto_x = x + triangle_radius * Math.cos(-radians); 33 | let moveto_y = y + triangle_radius * Math.sin(-radians); 34 | let lineto1_x = x + triangle_radius * Math.cos(-(radians + 2 * Math.PI / 3)); 35 | let lineto1_y = y + triangle_radius * Math.sin(-(radians + 2 * Math.PI / 3)); 36 | let lineto2_x = x + triangle_radius * Math.cos(-(radians + 4 * Math.PI / 3)); 37 | let lineto2_y = y + triangle_radius * Math.sin(-(radians + 4 * Math.PI / 3)); 38 | ctx.moveTo(moveto_x, moveto_y); 39 | ctx.lineTo(lineto1_x, lineto1_y); 40 | ctx.lineTo(lineto2_x, lineto2_y); 41 | ctx.lineTo(moveto_x, moveto_y); 42 | } else { 43 | ctx.moveTo(x, y - height / 2); 44 | ctx.lineTo(x - length / 2, y + height / 2); 45 | ctx.lineTo(x + length / 2, y + height / 2); 46 | ctx.lineTo(x, y - height / 2); 47 | } 48 | ctx.closePath(); 49 | ctx.stroke(); 50 | } 51 | 52 | algVines.drawSquare = function (x, y, radius, length, lineWidth, shape_color, shape_opacity, radians) { 53 | ctx.lineWidth = lineWidth; 54 | ctx.strokeStyle = shape_color; 55 | ctx.globalAlpha = shape_opacity; 56 | ctx.beginPath(); 57 | if(!algVines.rotation) { 58 | ctx.rect(x, y, length, length); 59 | } else { 60 | let angle = (2 * Math.PI / 4); 61 | ctx.moveTo(x + radius * Math.cos(0 * angle + radians), y + radius * Math.sin(0 * angle + radians)); 62 | for(i = 1; i < 4; i++) { 63 | ctx.lineTo(x + radius * Math.cos(i * angle + radians), y + radius * Math.sin(i * angle + radians)); 64 | } 65 | ctx.lineTo(x + radius * Math.cos(0 * angle + radians), y + radius * Math.sin(0 * angle + radians)); 66 | } 67 | ctx.closePath(); 68 | ctx.stroke(); 69 | } 70 | 71 | algVines.drawPentagon = function (x, y, radius, lineWidth, shape_color, shape_opacity, radians) { 72 | ctx.lineWidth = lineWidth; 73 | ctx.strokeStyle = shape_color; 74 | ctx.globalAlpha = shape_opacity; 75 | ctx.beginPath(); 76 | if(!algVines.rotation) { 77 | let angle = 2 * Math.PI / 5; 78 | ctx.moveTo(x + radius * Math.cos(0 * angle), y + radius * Math.sin(0 * angle)); 79 | for(i = 1; i < 5; i++) { 80 | ctx.lineTo(x + radius * Math.cos(i * angle), y + radius * Math.sin(i * angle)); 81 | } 82 | ctx.lineTo(x + radius * Math.cos(0 * angle), y + radius * Math.sin(0 * angle)); 83 | } else { 84 | // Rotation code goes here 85 | let angle = 2 * Math.PI / 5; 86 | ctx.moveTo(x + radius * Math.cos(-(0 * angle + radians)), y + radius * Math.sin(-(0 * angle + radians))); 87 | for(i = 1; i < 5; i++) { 88 | ctx.lineTo(x + radius * Math.cos(-(i * angle + radians)), y + radius * Math.sin(-(i * angle + radians))); 89 | } 90 | ctx.lineTo(x + radius * Math.cos(-(0 * angle + radians)), y + radius * Math.sin(-(0 * angle + radians))); 91 | } 92 | ctx.closePath(); 93 | ctx.stroke(); 94 | } 95 | 96 | algVines.drawLine = function(p1, p2, lineWidth, shape_color, shape_opacity) { 97 | ctx.lineWidth = lineWidth; 98 | ctx.strokeStyle = shape_color; 99 | ctx.globalAlpha = shape_opacity; 100 | ctx.beginPath(); 101 | ctx.moveTo(p1[0], p1[1]); 102 | ctx.lineTo(p2[0], p2[1]); 103 | ctx.stroke(); 104 | } 105 | 106 | algVines.setBackgroundColor = function(color) { 107 | ctx.beginPath(); 108 | ctx.fillStyle = color; 109 | ctx.globalAlpha = 1; 110 | ctx.rect(0, 0, width, height); 111 | ctx.fill(); 112 | } 113 | 114 | algVines.lineIsValid = function(p1, p2) { 115 | return p1[0] >= 0 && p1[1] >= 0 && p2[0] >= 0 && p2[1] >= 0 && algVines.distance(p1, p2) < algVines.spikeMaxLength; 116 | } 117 | 118 | /* Start at a random pixel on the canvas. Randomly choose which of the surrounding pixels to draw on next. 119 | This is how the alg will draw the main branch of the "vines". For every pixel that the algorithm chooses to draw on, 120 | a winding series of slowly shrinking circles will be drawn from that pixel at random angles to mimic the curly, 121 | waviness of grapevines. */ 122 | 123 | algVines.drawNextShape = function(x, y, radians, color, opacity, numOfSides){ 124 | if(algVines.useColorList && algVines.changingColors) { 125 | let colorIndex = Math.floor(Math.random() * algVines.colorList.length); 126 | color = algVines.colorList[colorIndex]; 127 | } else if(algVines.useColorList && !algVines.changingColors) { 128 | color = algVines.initialColor; 129 | } else if(algVines.randomColor && algVines.changingColors) { 130 | color = algVines.getRandomColor(); 131 | } else if(algVines.randomColor && !algVines.changingColors) { 132 | color = algVines.initialColor; 133 | } 134 | // console.log(numOfSides); 135 | if(numOfSides == 3) { 136 | algVines.drawTriangle(x, y, algVines.sideLength, algVines.lineWidth, radians, color, opacity); 137 | } else if(numOfSides == 4) { 138 | algVines.drawSquare(x, y, Math.sqrt(2) * algVines.sideLength / 2, algVines.sideLength, algVines.lineWidth, color, opacity, radians); 139 | } else { 140 | // x, y, numOfSides, radius, lineWidth, shape_color, shape_opacity, radians 141 | algVines.drawPentagon(x, y, algVines.sideLength / (2 * Math.sin(Math.PI / 5)), algVines.lineWidth, color, opacity, radians); 142 | } 143 | } 144 | 145 | algVines.drawOneStep = function() { 146 | // Reached the end 147 | if(algVines.numOfSteps > algVines.maxNumOfSteps) { 148 | clearInterval(algVines.loop); 149 | return false; 150 | } 151 | 152 | // Increment iterations 153 | algVines.currentIterations += 1; 154 | // Path and movement 155 | let prev_x = algVines.current_x; 156 | let prev_y = algVines.current_y; 157 | let step_x = algVines.stepX; 158 | let step_y = algVines.stepY; 159 | if(algVines.randomPath) { 160 | let x_index = Math.floor(Math.random() * algVines.stepChoicesX.length); 161 | let y_index = Math.floor(Math.random() * algVines.stepChoicesY.length); 162 | step_x = algVines.stepChoicesX[x_index]; 163 | step_y = algVines.stepChoicesY[y_index]; 164 | } else if(algVines.curvedPath) { 165 | let angle = 2 * Math.PI * (algVines.currentIterations % algVines.pathCycle) / algVines.pathCycle; 166 | let radius = algVines.pathRadius; 167 | if(algVines.changingRadius) { 168 | radius = algVines.pathRadius + Math.floor(algVines.currentIterations / algVines.radiusIncrementCycle); 169 | } 170 | if(algVines.alsoLinear) { 171 | step_x = radius * Math.cos(angle) + algVines.stepX; 172 | step_y = algVines.stepY; 173 | } else { 174 | step_x = radius * Math.cos(angle); 175 | step_y = radius * Math.sin(angle); 176 | } 177 | } 178 | if(algVines.noisyPath) { 179 | step_x += Math.floor(Math.random() * algVines.noiseX); 180 | step_y += Math.floor(Math.random() * algVines.noiseY); 181 | } 182 | algVines.current_x = (algVines.current_x + step_x + width) % width; 183 | algVines.current_y = (algVines.current_y + step_y + height) % height; 184 | // Drawing variation 185 | algVines.currentOpacity = - 0.9 * Math.sin(0.004 * Math.PI * (algVines.currentIterations % 250)) + 1; 186 | algVines.currentRadius = algVines.circleRadius * Math.sin(0.01 * Math.PI * (algVines.currentIterations % 100)) + 2; 187 | algVines.sideNum = Math.round(2 * Math.sin(0.01 * Math.PI * (algVines.currentIterations % 100)) + 3); 188 | algVines.rotationDegs = 180 * Math.sin(0.01 * Math.PI * (algVines.currentIterations % 100)); 189 | algVines.rotationRads = algVines.rotationDegs * (Math.PI / 180); 190 | // Select color 191 | let color = algVines.defaultColor; 192 | if(algVines.useColorList && algVines.changingColors) { 193 | let colorIndex = Math.floor(Math.random() * algVines.colorList.length); 194 | color = algVines.colorList[colorIndex]; 195 | } else if(algVines.useColorList && !algVines.changingColors) { 196 | color = algVines.initialColor; 197 | } else if(algVines.randomColor && algVines.changingColors) { 198 | color = algVines.getRandomColor(); 199 | } else if(algVines.randomColor && !algVines.changingColors) { 200 | color = algVines.initialColor; 201 | } 202 | // Draw shape or texture 203 | if(algVines.drawCircles) { 204 | let radius = algVines.circleRadius; 205 | let opacity = algVines.circleOpacity; 206 | if(algVines.pressureVariation && algVines.circleRandomness) { 207 | let pressure = algVines.penPressureMin + (1.0 - algVines.penPressureMin) * Math.random(); 208 | radius = Math.floor(pressure * algVines.circleRadius); 209 | opacity = algVines.currentOpacity; 210 | } else if(algVines.pressureVariation && !algVines.circleRandomness){ 211 | radius = algVines.currentRadius; 212 | opacity = algVines.currentOpacity; 213 | } 214 | algVines.drawFilledCircle(algVines.current_x, algVines.current_y, radius, color, opacity); 215 | } else if(algVines.drawShapes && !algVines.rotation) { 216 | // x, y, radians, color, opacity, numOfSides 217 | algVines.drawNextShape(algVines.current_x, algVines.current_y, 0, color, algVines.currentOpacity, algVines.sideNum); 218 | } else if(algVines.drawShapes && algVines.rotation) { 219 | // console.log(algVines.rotationDegs); 220 | algVines.drawNextShape(algVines.current_x, algVines.current_y, algVines.rotationRads, color, algVines.currentOpacity, algVines.sideNum); 221 | } else { 222 | for(let i = 0; i < algVines.numOfSpikes; i++) { 223 | let x_offset = Math.floor(algVines.spikeOffsetRangeX * Math.random()) - Math.floor(algVines.spikeOffsetRangeX / 2); 224 | let y_offset = Math.floor(algVines.spikeOffsetRangeY * Math.random()) - Math.floor(algVines.spikeOffsetRangeY / 2); 225 | let p1 = [prev_x, prev_y]; 226 | let p2 = [algVines.current_x + x_offset, algVines.current_y + y_offset]; 227 | if(algVines.lineIsValid(p1, p2)) { 228 | algVines.drawLine(p1, p2, algVines.spikeLineWidth, color, algVines.spikeAlpha); 229 | } 230 | } 231 | } 232 | 233 | algVines.numOfSteps++; 234 | return true; 235 | } 236 | 237 | algVines.reset = function () { 238 | algVines.pause(); 239 | algVines.currentIterations = 0; 240 | algVines.current_x = Math.floor(Math.random() * width); 241 | algVines.current_y = Math.floor(Math.random() * height); 242 | if(!algVines.changingColors) { 243 | algVines.initialColor = algVines.getRandomColor(); 244 | } 245 | algVines.numOfSteps = 0; 246 | } 247 | 248 | algVines.initialize = function() { 249 | algVines.reset(); 250 | } 251 | 252 | algVines.pause = function () { 253 | if("loop" in algVines) { 254 | clearInterval(algVines.loop); 255 | } 256 | } 257 | 258 | algVines.start = function () { 259 | if(algVines.isBackgroundSet) { 260 | algVines.setBackgroundColor(algVines.backgroundColor); 261 | } else { 262 | algVines.backgroundChoice = Math.floor(Math.random() * algVines.backgroundColors.length); 263 | algVines.backgroundColor = algVines.backgroundColors[algVines.backgroundChoice]; 264 | algVines.setBackgroundColor(algVines.backgroundColor); 265 | } 266 | algVines.loop = setInterval(algVines.drawOneStep, algVines.speed); 267 | } 268 | -------------------------------------------------------------------------------- /algVines_params.js: -------------------------------------------------------------------------------- 1 | /* Parameters */ 2 | /* Yellow to blue gradient */ 3 | algVines.palette1 = [ 4 | "rgb(250,250,110)", 5 | "rgb(227,230,108)", 6 | "rgb(204,210,105)", 7 | "rgb(181,191,103)", 8 | "rgb(158,171,100)", 9 | "rgb(134,151,98)", 10 | "rgb(111,131,95)", 11 | "rgb(88,112,93)", 12 | "rgb(65,92,90)", 13 | "rgb(42,72,88)" 14 | ]; 15 | 16 | algVines.palette2 = [ 17 | "purple", 18 | "fuchsia", 19 | "lime", 20 | "aqua", 21 | "darkorange", 22 | "deeppink", 23 | "mediumspringgreen", 24 | "white", 25 | "tomato" 26 | ] 27 | 28 | algVines.palette3 = [ 29 | "rgb(163,0,33)", 30 | "rgb(245,99,41)", 31 | "rgb(238,175,27)", 32 | "rgb(236,237,232)", 33 | "rgb(129,177,162)" 34 | ]; 35 | 36 | algVines.palette4 = [ 37 | "rgb(57,0,153)", 38 | "rgb(158,0,89)", 39 | "rgb(255,0,84)", 40 | "rgb(255,84,0)", 41 | "rgb(255,189,0)" 42 | ]; 43 | 44 | algVines.palette5 = [ 45 | "rgb(121,125,98)", 46 | "rgb(155,155,122)", 47 | "rgb(217,174,148)", 48 | "rgb(241,220,167)", 49 | "rgb(208,140,96)", 50 | "rgb(153,123,102)" 51 | ]; 52 | 53 | // Palettes 54 | algVines.palettes = [ 55 | algVines.palette1, 56 | algVines.palette2, 57 | algVines.palette3, 58 | algVines.palette4, 59 | algVines.palette5 60 | ]; 61 | 62 | // Draw colors 63 | algVines.defaultColor = "blue"; 64 | algVines.colorList = algVines.palette2; 65 | 66 | // Background colors 67 | algVines.isBackgroundSet = false; 68 | algVines.backgroundColors = [ 69 | "black", 70 | "white", 71 | "gainsboro", 72 | "gray", 73 | "red", 74 | "blue", 75 | "green", 76 | "yellow" 77 | ]; 78 | 79 | // Movement params 80 | algVines.speed = 0; 81 | algVines.maxNumOfSteps = 50000; 82 | 83 | // Path params 84 | algVines.stepX = 1; 85 | algVines.stepY = 8; 86 | algVines.noisyPath = false; 87 | algVines.noiseX = 2; 88 | algVines.noiseY = 1; 89 | algVines.randomPath = false; 90 | algVines.stepChoicesX = [-10, 0, 14]; 91 | algVines.stepChoicesY = [-6, 0, 8]; 92 | algVines.curvedPath = true; 93 | algVines.pathRadius = 5; 94 | algVines.pathCycle = 30; 95 | algVines.changingRadius = true; 96 | algVines.radiusIncrementCycle = 20; 97 | algVines.alsoLinear = false; 98 | 99 | // Color params 100 | algVines.useColorList = true; 101 | algVines.randomColor = false; 102 | algVines.changingColors = true; 103 | 104 | // Circle params 105 | algVines.drawCircles = false; 106 | algVines.circleRadius = 15; 107 | algVines.circleOpacity = 0.2; 108 | algVines.pressureVariation = true; 109 | algVines.penPressureMin = 0.4; 110 | algVines.circleRandomness = false; 111 | 112 | // Shape params 113 | algVines.drawShapes = true; 114 | algVines.rotation = true; 115 | algVines.lineWidth = 2; 116 | algVines.sideLength = 30; 117 | 118 | // Spike params 119 | algVines.numOfSpikes = 5; 120 | algVines.spikeAlpha = 0.4; 121 | algVines.spikeLineWidth = 1; 122 | algVines.spikeOffsetRangeX = 50; 123 | algVines.spikeOffsetRangeY = 100; 124 | algVines.spikeMaxLength = 200; 125 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AlgoArt Creator Studio 5 | 6 | 7 | 8 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 54 |
55 | 56 | 57 |
58 |
59 | 60 |
61 | 62 | 80 | 81 | 90 | 91 | 101 | 102 | 114 |
115 | 116 |
117 | Your browser does not support the HTML5 canvas 118 | tag. 119 | 120 |
121 |
122 | 123 |
124 | 125 |
126 |
127 | 128 |
130 |
131 | 132 | 133 | 136 | 137 | 138 |  Algorithm Lists 139 | 140 | 141 |
142 |
143 |
144 | 145 |
147 |
148 | 149 | 150 | 153 | 155 | 156 |  Parameter Panel 157 | 158 | 159 |
160 | 161 | Selected: 163 | 164 |
165 | 166 |
167 |
168 | 171 | 174 | 177 |
178 |
179 | 180 |
181 |
182 | Boolean Params 183 | 184 |
185 |
186 | Numeric & String Params 187 | 188 |
189 |
190 | Longer Params 191 | 192 |
193 |
194 |
195 |
196 |
197 | 198 |
200 |
201 | 202 | 203 | 206 | 207 | 208 | 209 |  About 210 | 211 | 212 |
213 |

Welcome to the Creator Studio!

214 |

215 | AlgoArt.org is a digital platform that brings together Artists and Technologists to create algorithmically generated visual designs and artworks. Using our Creator Studio, you can generate your own unique artworks! 216 |

217 |

218 | Here is how to get started! 219 |

220 |
    221 |
  • 222 | Select an Algorithm from our Algorithm List. 223 |
  • 224 |
  • 225 | Go to the Params Tab, then adjust parameters by clicking checkboxes or typing into textboxes and pressing enter. 226 |
  • 227 |
  • 228 | Press the Start Button to create your own artwork! 229 |
  • 230 |
  • 231 | You can pause and start again at anytime. You can restart by clicking the Reset Button and clear the canvas by clicking the Clear Button. 232 |
  • 233 |
  • 234 | Finally, be sure to click the Save Button to download your masterpiece! 235 |
  • 236 |
237 |

238 | To see more artworks, please visit Our Gallery and select your favorites! Also, visit our GitHub Repo to create your own drawing algorithms using our framework! 239 |

240 |
241 |

242 | To learn more about our drawing algorithms and platform, see the following videos: 243 |

244 |
Demonstration of Drawing Algorithms
245 |

246 | 247 | 248 | 249 |

250 |
251 |
Talk About How Our Platform Works
252 |

253 | 254 | 255 | 256 |

257 |
258 |
259 |
Credits
260 |

261 | The AlgoArt Platform was developed by Swarthmore College faculty member M. Wehar and student researchers X. Dong, J. Gallardo Moreno, O. Khan, X. Li, M. Newman-Toker, R. Oet, V. Sumano, L. Suresh, and A. Zhang. Special thanks to all friends and collaborators who offered help! 262 |

263 |

264 | * This platform was developed using the JQuery and Bootstrap libraries as well as Bootstrap Icons which were made available under MIT License. 265 |

266 |
267 |
268 |
269 |
270 |
271 | 272 |
273 | 288 |
289 | 290 |
292 | 293 | 295 |
296 |
297 |
298 | 299 | 300 | 301 | 302 | 303 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Algorithms set up 2 | var algShortName = ["algGP", "algVines", "algCS", "algVD", "algCL", "algDots"]; 3 | var algNames = ["Geometric Patterns", "Vines", "Constellations", "Voronoi", "Collisions", "Dots (Simple Example)"]; 4 | var algCredits = ["Geometric Patterns by Michael Wehar", "Vines by Alyssa Zhang", "Constellations by Jhovani Gallardo Moreno", "Voronoi by Jhovani Gallardo Moreno", "Collisions by Omar Khan", "Modify This Algorithm!"]; 5 | var algorithms = [algGP, algVines, algCS, algVD, algCL, algDots]; 6 | var algorithmsPaused = []; 7 | for (let i = 0; i < algorithms.length; i++) { 8 | algorithms[i].initialize(); 9 | algorithmsPaused.push(true); 10 | } 11 | 12 | // Algorithm selection set up 13 | var currentSelection = 1; 14 | 15 | // parameter set up 16 | let all_params = param_initialize(); 17 | let params_store = create_params_store(); 18 | param_display(all_params[currentSelection - 1]); 19 | 20 | function selectAlgorithm(id) { 21 | $('#' + id).css({"border-color": "#86b7fe", "box-shadow": "0 0 0 .25rem rgba(13, 110, 253, .25)"}); 22 | } 23 | 24 | function deselectAlgorithm(id) { 25 | $('#' + id).css({ 26 | "border-color": "none", 27 | "box-shadow": "none" 28 | }).attr("style", "rgba(0, 0, 0, 0.16) 0 1px 4px;"); 29 | } 30 | 31 | // Functions for user interaction 32 | function changeSelectionModified(id) { 33 | // reset the prev algo 34 | param_not_display(all_params[currentSelection - 1]); 35 | deselectAlgorithm(currentSelection); 36 | 37 | // assign the new algo 38 | currentSelection = id; 39 | selectAlgorithm(currentSelection); 40 | param_display(all_params[currentSelection - 1]); 41 | algoNameUpdate(currentSelection); 42 | 43 | // button display 44 | if (algorithmsPaused[currentSelection - 1]) { 45 | document.getElementById("startButton").style.display = "initial"; 46 | document.getElementById("pauseButton").style.display = "none"; 47 | const tooltip = bootstrap.Tooltip.getInstance("#start_pause_button"); 48 | tooltip.setContent({'.tooltip-inner': 'Start'}); 49 | } else { 50 | document.getElementById("startButton").style.display = "none"; 51 | document.getElementById("pauseButton").style.display = "initial"; 52 | const tooltip = bootstrap.Tooltip.getInstance("#start_pause_button"); 53 | tooltip.setContent({'.tooltip-inner': 'Pause'}); 54 | } 55 | 56 | // Optional buttons 57 | enableOptionalButtons(); 58 | } 59 | 60 | function enableOptionalButtons() { 61 | // Randomize button 62 | if("randomize" in algorithms[currentSelection - 1]) { 63 | // document.getElementById("randomize").disabled = false; 64 | document.getElementById("randomize").style.display = "block"; 65 | } else { 66 | // document.getElementById("randomize").disabled = true; 67 | document.getElementById("randomize").style.display = "none"; 68 | } 69 | } 70 | 71 | // Create cards function 72 | function createCard(algId) { 73 | let code = '
\ 74 |
\ 75 |
\ 76 |
' + algNames[algId - 1] + '
\ 77 |
\ 78 |
'; 79 | document.getElementById("alg-cards").innerHTML += code; 80 | } 81 | 82 | // Initialize cards 83 | for (let i = 1; i <= algNames.length; i++) { 84 | createCard(i); 85 | } 86 | // Fill in alg name 87 | algoNameUpdate(currentSelection); 88 | selectAlgorithm(currentSelection); 89 | 90 | // start or pause selected algorithm 91 | function start() { 92 | if (algorithmsPaused[currentSelection - 1]) { 93 | algorithms[currentSelection - 1].start(); 94 | document.getElementById("startButton").style.display = "none"; 95 | document.getElementById("pauseButton").style.display = "initial"; 96 | const tooltip = bootstrap.Tooltip.getInstance("#start_pause_button"); 97 | tooltip.setContent({'.tooltip-inner': 'Pause'}); 98 | console.log("Started " + currentSelection); 99 | } else { 100 | algorithms[currentSelection - 1].pause(); 101 | document.getElementById("startButton").style.display = "initial"; 102 | document.getElementById("pauseButton").style.display = "none"; 103 | const tooltip = bootstrap.Tooltip.getInstance("#start_pause_button"); 104 | tooltip.setContent({'.tooltip-inner': 'Start'}); 105 | console.log("Paused " + currentSelection); 106 | } 107 | algorithmsPaused[currentSelection - 1] = !algorithmsPaused[currentSelection - 1]; 108 | } 109 | 110 | // reset selected algorithm 111 | function reset() { 112 | algorithms[currentSelection - 1].reset(); 113 | document.getElementById("startButton").style.display = "initial"; 114 | document.getElementById("pauseButton").style.display = "none"; 115 | const tooltip = bootstrap.Tooltip.getInstance("#start_pause_button"); 116 | tooltip.setContent({'.tooltip-inner': 'Start'}); 117 | algorithmsPaused[currentSelection - 1] = true; 118 | console.log("Reset " + currentSelection); 119 | } 120 | 121 | // Clear canvas 122 | function clearCanvas() { 123 | ctx.clearRect(0, 0, width, height); 124 | } 125 | 126 | // Save canvas as png 127 | function saveCanvas() { 128 | var link = document.getElementById('link'); 129 | link.setAttribute('download', 'artwork.png'); 130 | link.setAttribute('href', c.toDataURL("image/png").replace("image/png", "image/octet-stream")); 131 | link.click(); 132 | } 133 | 134 | /******** Updating functionalities of the Website ********/ 135 | 136 | // ready to change panels 137 | let offcanvas_start = window.matchMedia("(max-width: 1310px)"); 138 | var lastPanel = "#v-pills-algo-tab"; 139 | transformPanel(offcanvas_start); 140 | // listen to browser width change 141 | offcanvas_start.addEventListener('change', function () { 142 | transformPanel(offcanvas_start); 143 | }); 144 | 145 | // parameter set up 146 | function create_params_store() { 147 | let store = []; 148 | for (let id in algorithms) { 149 | let algo = algorithms[id]; 150 | let algo_copy = {}; 151 | for (let param in algo) { 152 | if (typeof algo[param] === "boolean" || typeof algo[param] === "number" || typeof algo[param] === "string") { 153 | algo_copy[param] = algo[param]; 154 | } else if (Array.isArray(algo[param]) && (typeof algo[param][0] === "number" || typeof algo[param][0] === "string")) { 155 | let temp_array = []; 156 | for (let i = 0; i < algo[param].length; i++) { 157 | temp_array.push(algo[param][i]); 158 | } 159 | algo_copy[param] = temp_array; 160 | } 161 | } 162 | store[id] = algo_copy; 163 | } 164 | return store; 165 | } 166 | 167 | /** 168 | * This function is used to load the parameters of the drawing algorithms to the 169 | * parameter panel in the Creator Studio. There are 7 types of allowed parameters: 170 | * 1) Color-related params, 2) List of numbers, 3) List of strings, 4) List of booleans, 171 | * 5) Numbers, 6) Booleans, 7) Strings. 172 | * @return {*[]} 173 | */ 174 | function param_initialize() { 175 | let all_params = []; 176 | for (let id in algorithms) { 177 | let algo = algorithms[id]; 178 | let curr_params = []; 179 | let keys = Object.keys(algo).sort(); 180 | for (let index in keys) { 181 | let param = keys[index]; 182 | let param_id = id + "__" + param; 183 | if (param.toLowerCase().includes("color") && Array.isArray(algo[param]) && algo[param].length === 3 && typeof algo[param][0] === "number") { 184 | colorParamInitialize(param_id, param, algo[param]); 185 | } else if (param.toLowerCase().includes("color") && typeof algo[param] === "string") { 186 | colorParamInitialize(param_id, param, algo[param]); 187 | } else if (Array.isArray(algo[param]) && typeof algo[param][0] === "number") { 188 | addToPage(param, listInitialization(param_id, param, algo[param])); 189 | } else if (typeof algo[param] === "number") { 190 | addToPage(param, numberInitialization(param_id, param, algo[param])); 191 | } else if (typeof algo[param] === "boolean") { 192 | addBooleanToPage(booleanInitialization(param_id, param, algo[param])); 193 | } else if (typeof algo[param] === "string") { 194 | addToPage(param, stringInitialization(param_id, param, algo[param])); 195 | } else { 196 | continue; 197 | } 198 | curr_params.push(param); 199 | } 200 | all_params.push(curr_params); 201 | } 202 | return all_params; 203 | } 204 | 205 | function booleanInitialization(id, name, value) { 206 | let bool_elem = 207 | "