Skip to content

Instantly share code, notes, and snippets.

@artemuzz
Created May 12, 2020 11:01
Show Gist options
  • Save artemuzz/1a0deede5a39cca11ee8e07fed7b745f to your computer and use it in GitHub Desktop.
Save artemuzz/1a0deede5a39cca11ee8e07fed7b745f to your computer and use it in GitHub Desktop.
Scallop shells
"use strict";
/**** parameters you should try to modify */
const minRadius = 25;
const maxRadius = 150;
const margin = 2; // minimum distance between 2 circles
/**** modifications beyond this line at your own risk */
let canv, ctx;
let maxx, maxy; // canvas sizes (in pixels)
let circles;
let events = [];
// shortcuts for Math.…
const mrandom = Math.random;
const mfloor = Math.floor;
const mround = Math.round;
const mceil = Math.ceil;
const mabs = Math.abs;
const mmin = Math.min;
const mmax = Math.max;
const mPI = Math.PI;
const mPIS2 = Math.PI / 2;
const m2PI = Math.PI * 2;
const msin = Math.sin;
const mcos = Math.cos;
const matan2 = Math.atan2;
const mhypot = Math.hypot;
const msqrt = Math.sqrt;
const rac3 = msqrt(3);
const rac3s2 = rac3 / 2;
const mPIS3 = Math.PI / 3;
//-----------------------------------------------------------------------------
// miscellaneous functions
//-----------------------------------------------------------------------------
function alea (min, max) {
// random number [min..max[ . If no max is provided, [0..min[
if (typeof max == 'undefined') return min * mrandom();
return min + (max - min) * mrandom();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function intAlea (min, max) {
// random integer number [min..max[ . If no max is provided, [0..min[
if (typeof max == 'undefined') {
max = min; min = 0;
}
return mfloor(min + (max - min) * mrandom());
} // intAlea
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function arrayShuffle (array) {
/* randomly changes the order of items in an array
only the order is modified, not the elements
*/
let k1, temp;
for (let k = array.length - 1; k >= 1; --k) {
k1 = intAlea(0, k + 1);
temp = array[k];
array[k] = array[k1];
array[k1] = temp;
} // for k
return array
} // arrayShuffle
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function randomOrder (range) {
/* returns an array with numbers 0..range-1 in random order */
let array = [];
for ( let k = 0; k < range; ++k) array[k] = k;
return arrayShuffle(array);
} // randomOrder
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function randomElement (array) {
return array[intAlea(array.length)];
} // randomElement
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/* returns intermediate value between v0 and v1,
alpha = 0 will return v0, alpha = 1 will return v1
values of alpha outside [0,1] may be used to compute points outside the v0-v1 range
*/
function lerp (v0, v1, alpha) {
return (1 - alpha) * v0 + alpha * v1;
} // function lerp;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/* returns point based on :
- an origin point
- a distance
- a direction given by two coordinates
*/
function dirPoint (porg, dist, dir) {
let distDir = mhypot (dir[0], dir[1]);
return [porg[0] + dist * dir[0] / distDir,
porg[1] + dist * dir[1] / distDir];
} // function dirPoint;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/* returns two coordinates representing move from porg to pend
*/
function diffPoints (porg, pend) {
return [pend[0] - porg[0],
pend[1] - porg[1]];
} // function diffPoints;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function sumPoints (pa, pb) {
return [pa[0] + pb[0],
pa[1] + pb[1]];
} // function sumPoints;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/* returns intermediate point between p0 and p1,
alpha = 0 will return p0, alpha = 1 will return p1
values of alpha outside [0,1] may be used to compute points outside the p0-p1 segment
*/
function intermediate (p0, p1, alpha) {
return [(1 - alpha) * p0[0] + alpha * p1[0],
(1 - alpha) * p0[1] + alpha * p1[1]];
} // function intermediate
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function length (p0, p1) {
/* distance between points */
return mhypot (p0[0] - p1[0], p0[1] - p1[1]);
} // function length
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/* find first occurence of element in array
returns index if found
returns -1 if not found but safe == true
throws error if not found and safe == false
*/
function findIndex (array, element, safe = false) {
let idx = array.indexOf(element);
if (idx != -1 || safe) return idx;
throw ('not found element in array');
} // removeFromArray
//-----------------------------------------------------------------------------
function Circle(x, y, radius) {
this.c = [x, y];
this.radius = radius;
} // Circle
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Circle.prototype.draw = function(stroke) {
let th1, th2, k;
th1 = alea(0, m2PI);
let xa = this.c[0] + this.radius * mcos(th1);
let ya = this.c[1] + this.radius * msin(th1);
let dk = mmax( 8, mround( this.radius / 5))
for (k = 0 ; k < dk ; ++k) {
th2 = th1 + k * m2PI / dk;
ctx.beginPath();
ctx.moveTo (xa,ya);
ctx.lineTo (this.c[0] + this.radius * mcos(th2), this.c[1] + this.radius * msin(th2));
ctx.strokeStyle = stroke;
ctx.lineWidth = 2;
ctx.stroke();
}
} //
//------------------------------------------------------------------------
function createCircles() {
let x, y, maxRad, d, closest;
let limit = minRadius + margin;
circles = []; // initially empty
let failcnt = 0;
retry:
while (circles.length < 5000) {
failcnt ++;
if (failcnt > 500) {
// console.log ( failcnt );
break; // 100 unsuccessful tries, give up
}
x = alea(maxx);
y = alea(maxy);
maxRad = x;
maxRad = mmin(maxRad, maxx - x);
maxRad = mmin(maxRad, y);
maxRad = mmin(maxRad, maxy - y);
if (maxRad < limit) continue retry;
let closest = -1;
for (let k = 0; k < circles.length; ++k) {
d = length([x, y], circles[k].c) - circles[k].radius;
if (d < limit) continue retry; // too close
if (d < maxRad) {
maxRad = d;
closest = k;
}
} // for k
maxRad -= margin;
// if we have much place, do not allways use maxRadius
if (maxRad > maxRadius) {
maxRad = alea(mmax(minRadius, 0.8 * maxRadius), maxRadius);
// move chosen point towards closest neighbour
if (closest >= 0) {
let cl = circles[closest];
d = length([x, y], cl.c); // actual distance
let d1 = maxRad + margin + cl.radius; // desired distance
x = cl.c[0] + (x - cl.c[0]) * d1 / d;
y = cl.c[1] + (y - cl.c[1]) * d1 / d;
}
}
let nc = new Circle(x, y, mmin(maxRadius, maxRad ))
circles.push (nc);
failcnt = 0;
} // while
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, maxx, maxy);
let hue = intAlea(360);
// circles.sort ( (c1, c2) => c2.radius - c1.radius);
circles.forEach( (circle,k) => circle.draw(`hsl(${hue}, 100%, ${intAlea(20,80)}%)`) );
} // createCircles
//------------------------------------------------------------------------
function startOver() {
// canvas dimensions
maxx = window.innerWidth;
maxy = window.innerHeight;
canv.style.left = ((window.innerWidth ) - maxx) / 2 + 'px';
canv.style.top = ((window.innerHeight ) - maxy) / 2 + 'px';
canv.width = maxx;
canv.height = maxy;
if (maxx < 10) return false; // not yet ready
createCircles();
return true;
} // startOver
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function clickCanvas() {
events.push({event: 'click'});
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function resize() {
events.push({event: 'resize'});
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
let animate = (()=>{ // scope for animate
let state;
return function (tstamp) {
let event;
while (event = events.shift()) {
switch (event.event) {
case 'init' :
case 'click' :
case 'resize' :
state = 1;
break;
} //switch (event.event)
} // while events
switch (state) {
case 1:
if (startOver()) ++state;
break;
} // switch (state)
window.requestAnimationFrame(animate);
} // animate
})(); // scope for animate
//------------------------------------------------------------------------
//------------------------------------------------------------------------
// beginning of execution
window.addEventListener("load",function() {
{
canv = document.createElement('canvas');
canv.style.position="absolute";
document.body.appendChild(canv);
ctx = canv.getContext('2d');
canv.setAttribute('title','Click for new pattern');
} // canvas creation
window.addEventListener('click',clickCanvas);
window.addEventListener('resize',resize);
/* launch animation */
events.push ({event: 'init'});
window.requestAnimationFrame(animate); // animate
}); // window load listener
body {
font-family: Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif;
background-color: #000;
margin:0;
padding:0;
border-width:0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment