xxxxxxxxxx
830
const AnuratiUrl = "https://static.observableusercontent.com/files/5d90d13355b0f8db98ebd049eb2ae6e1a17bba830827904420d963e08490872d7c3a6f970b60a3029c64909f6d03e330105cd6ae4e487ae0966b1b479afebb6b"
let itemInterval;
let started = false;
let paused = false;
let anuratiFont;
const speedLimit = 5;
const neonPink = "#FE019A";
const ribbonNearThreshold = 5;
let cycles = [];
let items = [];
const maxCycleNo = 6;
const maxItemNo = 6;
const immortalTime = 3;
const itemHitBox = 6;
const itemScale = 1.5;
const itemVisiableRange = 50;
const getColor = function () {
const pool = [
"#6CBDD8",
"#F7DF4B",
"#D8700C",
"#00FDC3",
"#FF352D",
];
return random(pool);
}
const objDeepEq = function (a,b){
//simple deep eq
if (a === b) return true;
if (a === null && b === null) return true;
if ((a === null && b !== null) || (a !== null && b === null)) return false;
if (typeof a != 'object' || typeof b != 'object') return false;
let keysA = Object.keys(a), keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (const key of keysA) {
if (!keysB.includes(key) || !objDeepEq(a[key], b[key])) return false;
}
return true;
}
const getItemType = function () {
const pool = [
"massUp",
"ribbonOff",
"jumpKit",
"Lturn",
"Uturn"
];
return random(pool);
}
const drawGrid = function (size) {
background("#191840");
push();
stroke("#65A6CD");
strokeWeight(5);
line(0,0,width/2 -5, 0);
line(width/2 + 5, 0, width, 0);
line(0,height,width/2 -5, height);
line(width/2 + 5, height, width, height);
line(0,0,0, height/2 -5);
line(0, height/2 + 5, 0, height);
line(width,0,width, height/2 -5);
line(width, height/2 + 5, width, height);
const strokeC = color("#65A6CD");
strokeC.setAlpha(100);
stroke(strokeC);
strokeWeight(1);
for (let x = -10; x < width + 10; x += size){
line(x, -10, x, height + 10);
}
for (let y = -10; y < height + 10; y += size) {
line(-10, y, width + 10, y);
}
pop();
}
const spawnItem = function () {
if (items.length < maxItemNo){
if (random() > 0.3) {
items.push(new Item(random(10, width - 10), random(10, height - 10),getItemType()));
}
}
}
const collisionCheck = function (cycle, item) {
return dist(cycle.loc.x, cycle.loc.y, item.x, item.y) < itemHitBox * itemScale
};
const caculateDistPoint2Line = function (p, l){
let d1 = dist(p.x,p.y,l[0].x,l[0].y);
let d2 = dist(p.x,p.y,l[1].x,l[1].y);
return abs(d1+(d2-d1)/2);
}
const ribbonNearCheck = function (cyc, ribbon) {
if (ribbon.length < 2) return false;
if (objDeepEq(cyc.lightRibbon, ribbon)) return false;
return mightNearCheck(cyc.loc, ribbon);
}
const mightNearCheck = function (point, ribbon) {
if (ribbon.length < 2) return false;
let rib = [ribbon];
rib.pop();
for (let i = 1; i < rib.length; i++) {
if (rib[i] != null && rib[i-1] !== null) {
if (dist(rib[i].x, rib[i].y, rib[i-1].x, rib[i-1].y) > 500) continue;
if (caculateDistPoint2Line(point, [rib[i-1], rib[i]]) < ribbonNearThreshold) return [rib[i-1], rib[i]];
}
}
return false;
}
const check2LinesIntersect = function (l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2) {
if (dist(l2x1,l2y1,l2x2,l2y2) > 500 || dist(l1x1, l1y1, l1x2, l1y2) > 500) return false;
//from https://stackoverflow.com/a/24392281
let det, gamma, lambda;
det = (l1x2 - l1x1) * (l2y2 - l2y1) - (l2x2 - l2x1) * (l1y2 - l1y1);
if (det === 0) {
return false;
} else {
lambda = ((l2y2 - l2y1) * (l2x2 - l1x1) + (l2x1 - l2x2) * (l2y2 - l1y1)) / det;
gamma = ((l1y1 - l1y2) * (l2x2 - l1x1) + (l1x2 - l1x1) * (l2y2 - l1y1)) / det;
return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1);
}
}
const checkIntersect= function (l, r){
if (r.length < 2) return false;
for (let i = 1; i < r.length; i++) {
if (r[i] !== null && r[i-1] !== null) {
if (check2LinesIntersect(l[0].x,l[0].y,l[1].x,l[1].y,r[i].x,r[i].y,r[i-1].x,r[i-1].y)) return [r[i], r[i-1]];
}
}
return false;
}
// below over all shall be O(n);
const goOverCheck = function (cyc, ribbon) {
if (!cyc.lastPosition) return false;
if (ribbon.length < 2) return false;
let rib = [ribbon];
if (objDeepEq(rib, cyc.lightRibbon)) rib.pop();
return checkIntersect([cyc.loc, cyc.lastPosition], rib);
}
const mightGoOver = function (l, ribbon) {
let rib = [ribbon];
return checkIntersect(l, rib);
}
//
const newPlayerIn = function () {
while (cycles.length < maxCycleNo) {
let dice = random([0,1,2]);
switch(dice){
default:
cycles.push(new LightCycle(createVector(random(width/2 - 5, width/2 + 5), 7), getColor(), createVector(random(-0.5,0.5),random(0.6, 1)), 1));
break;
case 0:
cycles.push(new LightCycle(createVector(random(width/2 - 5, width/2 + 5), height - 7), getColor(), createVector(random(-0.5,0.5),random(-1, -0.6)), 1));
break;
case 1:
cycles.push(new LightCycle(createVector(7, random(height/2 - 5, height/2 + 5)), getColor(), createVector(random(0.6, 1),random(-0.5, 0.5)), 1));
break;
case 2:
cycles.push(new LightCycle(createVector(width - 7, random(height/2 - 5, height/2 + 5)), getColor(), createVector(random(-1, -0.6),random(-0.5, 0.5)), 1));
break;
}
}
// things.push(new Thing(cycles[cycles.length - 1],cycles[0]));
// things.push(new Thing(cycles[cycles.length - 2],cycles[cycles.length - 1]));
}
class LightCycle{
constructor(loc,c,v,imm){
this.loc = loc;
this.c = c;
this.m = 2;
this.mChangedCount = 0;
this.hasJumpKit = 0;
this.lightRibbonOn = true;
this.lightRibbonOffCount = 0;
this.v = v;
this.acc = createVector(0,0);
this.lightRibbon = [];
this.immortal = true;
this.immortalTime = imm;
this.immortalCount = 0;
this.destroyed = false;
this.lastPosition = null;
}
render(){
//destroyed animation (only one frame ... lol)
if (this.destroyed){
//boom
push();
translate(this.loc.x, this.loc.y);
rotate(random(-PI, PI));
noFill();
stroke(this.c);
strokeWeight(3);
beginShape();
for (let i = 0; i < 10; i++) {
let di;
di = random(6,10);
if (i % 2 === 0) di = random(2,5);
let r = (PI/5 * i) + random(-0.1, 0.1);
vertex(di*s.cos(r), di*s.sin(r));
}
endShape(CLOSE);
pop();
return;
}
//
// jumpKit
if (this.hasJumpKit){
push();
stroke(255);
noFill();
strokeWeight(0.8);
translate(this.loc.x, this.loc.y);
rotate(frameCount/10);
for (let i = 0; i < 2*s.PI; i += PI/2){
arc(0,0,15,15,i, i + PI/4);
}
pop();
}
//
//light ribbon
for (let i = 1; i < this.lightRibbon.length; i ++) {
push();
let temC = color(this.c);
temC.setAlpha(i < 50? map(i,0,49,50,255): 255);
stroke(temC);
noFill();
strokeWeight( i < 4 ? map(i, 0, 3, 0.5, 2.5) : 2.5);
if (this.lightRibbon[i] != null && this.lightRibbon[i-1] != null) {
if (dist(this.lightRibbon[i-1].x, this.lightRibbon[i-1].y,this.lightRibbon[i].x,this.lightRibbon[i].y) < 500){
line(this.lightRibbon[i-1].x, this.lightRibbon[i-1].y,this.lightRibbon[i].x,this.lightRibbon[i].y);
}
}
pop();
}
//the vehicle
push();
fill(0);
if (this.immortal) {
if (this.immortalCount % frameRate()/4 > frameRate() / 8) {
let temC = color(this.c);
temC.setAlpha(150);
stroke(temC)
} else {
stroke(this.c)
}
} else {
stroke(this.c);
}
translate(this.loc.x, this.loc.y);
rotate(this.getDirection());
//wheel 1
beginShape();
vertex(1.5, -1);
vertex(1.5, -5);
curveVertex(1.5,-5);
curveVertex(1.5,-7);
curveVertex(-1.5, -7);
curveVertex(-1.5,-5);
vertex(-1.5,-5);
vertex(-1.5,-1);
curveVertex(-1.5,-1);
curveVertex(-1.5, 1);
curveVertex(1.5,1);
curveVertex(1.5,-1);
endShape(CLOSE);
//wheel 2
beginShape();
vertex(1.5, 1);
vertex(1.5, 5);
curveVertex(1.5,5);
curveVertex(1.5,7);
curveVertex(-1.5,7);
curveVertex(-1.5,5);
vertex(-1.5,5);
vertex(-1.5,1);
curveVertex(-1.5,1);
curveVertex(-1.5,-1);
curveVertex(1.5,-1);
curveVertex(1.5,1);
endShape(CLOSE);
pop();
}
update(){
//check immortal state
this.immortalCount ++;
if (this.immortal && this.immortalCount > this.immortalTime * frameRate()) this.immortal = false;
//
//check if distroyed
if (!this.immortal) {
for (const cyc of cycles) {
if (ribbonNearCheck(this, cyc.lightRibbon) || goOverCheck(this, cyc.lightRibbon)){
if (this.hasJumpKit > 0){
this.hasJumpKit --;
} else {
this.destroyed = true;
}
break;
}
}
if (this.loc.x < -1 || this.loc.x > width + 1 || this.loc.y < -1|| this.loc.y > height + 1) {
this.destroyed = true;
}
}
//
if (!this.destroyed) {
//check other states (m, ribbon state, etc)
if (this.m > 2) {
if (this.mChangedCount > frameRate() * 7) {
this.m = 2;
this.mChangedCount = 0;
} else {
this.mChangedCount ++;
}
}
if (!this.lightRibbonOn) {
if (this.lightRibbonOffCount > frameRate() * 5){
this.lightRibbonOn = true;
this.lightRibbonOffCount = 0;
} else {
this.lightRibbonOffCount ++;
}
}
//
//check "food"
for (const it of items) {
if (collisionCheck(this, it)) {
switch(it.type) {
default:
break;
case "massUp":
this.m *= 2;
this.v.x = this.v.x/2;
this.v.y = this.v.y/2;
break;
case "jumpKit":
this.hasJumpKit = 3;
break;
case "ribbonOff":
this.lightRibbonOn = false;
break;
case "Lturn":
let tem = this.v.x;
this.v.x = this.v.y;
this.v.y = tem;
break;
case "Uturn":
this.v.x = -this.v.x;
this.v.y = -this.v.y;
break;
}
it.shouldBeRemoved = true;
break;
}
}
//
this.move();
}
}
move(){
this.caculateSpeed();
if (this.v.magSq() > 0) {
if (!this.immortal){
if (this.lightRibbonOn) {
this.lightRibbon.push(createVector(this.loc.x, this.loc.y));
} else {
this.lightRibbon.push(null);
}
}
if (this.lightRibbon.length > 100) this.lightRibbon.shift();
this.lastPosition = createVector(this.loc.x, this.loc.y);
}
this.loc.add(this.v);
}
caculateSpeed(){
this.caculateAcc();
this.v.x += this.acc.x;
this.v.y += this.acc.y;
//speed limit
this.v.x = constrain(this.v.x, -speedLimit, speedLimit);
this.v.y = constrain(this.v.y, -speedLimit, speedLimit);
}
caculateAcc(){
let forceX = 0;
let forceY = 0;
//random walk
forceX += random(-1,1);
forceY += random(-1,1);
//
let nextPosible = createVector(this.loc.x + this.v.x, this.loc.y + this.v.y);
//prevent hit
if (!this.hasJumpKit){
//the cycle is not afraid with jumpKit
for (const cyc of cycles) {
let nearSeg = mightNearCheck(nextPosible, cyc.lightRibbon);
let intersectSeg = mightGoOver([nextPosible, this.loc], cyc.lightRibbon);
if (nearSeg || intersectSeg) {
//try to avoid
let dir;
if (nearSeg) {
let temVec1 = createVector(nearSeg[0].x - nearSeg[1].x, nearSeg[0].y - nearSeg[1].y);
let temVec2 = createVector(nearSeg[1].x - nearSeg[0].x, nearSeg[1].y - nearSeg[0].y);
let ang1 = temVec1.angleBetween(this.v);
let ang2 = temVec2.angleBetween(this.v);
let determin = (abs(ang1) > abs(ang2) ? ang1 : ang2);
if (determin < 0) {
dir = - PI/2;
} else {
dir = PI/2;
}
}
if (intersectSeg) {
let temVec1 = createVector(intersectSeg[0].x - intersectSeg[1].x, intersectSeg[0].y - intersectSeg[1].y);
let temVec2 = createVector(intersectSeg[1].x - intersectSeg[0].x, intersectSeg[1].y - intersectSeg[0].y);
let ang1 = temVec1.angleBetween(this.v);
let ang2 = temVec2.angleBetween(this.v);
let determin = (abs(ang1) > abs(ang2) ? ang1 : ang2);
if (determin < 0) {
dir = - PI/2;
} else {
dir = PI/2;
}
}
let turnForce = createVector(this.v.x, this.v.y);
turnForce.normalize();
turnForce.rotate(dir + random(-PI/8, PI/8)); // around 90 degree turn
let para = random(5,7);
forceX += turnForce.x * para;
forceY += turnForce.y * para;
//
}
}
}
//
// hit wall
let dir = undefined;
if (nextPosible.x < 0) dir = (this.v.heading() < 0) ? PI/2 : -PI/2;
if (nextPosible.x > width) dir = (this.v.heading() > 0) ? PI/2 : -PI/2;
if (nextPosible.y < 0) dir = (this.v.heading() > -PI/2) ? PI/2 : -PI/2;
if (nextPosible.y > height ) dir = (this.v.heading() > PI/2) ? PI/2 : -PI/2;
if (dir) {
let turnForce = createVector(this.v.x, this.v.y);
turnForce.normalize();
turnForce.rotate(dir + random(-PI/8, PI/8)); // around 90 degree turn
let para = random(5,10);
forceX += turnForce.x * para;
forceY += turnForce.y * para;
}
//
// items hunt
for (const it of items) {
if (dist(it.x, it.y, this.loc.x, this.loc.y) < itemVisiableRange) {
if (it.type === "jumpKit") {
let para = random(0.3,1.5);
let vec = createVector(it.x - this.loc.x, it.y - this.loc.y);
vec.normalize();
forceX += vec.x * para;
forceY += vec.y * para;
} else if (it.type === "Lturn" || it.type === "Uturn") {
let para = random(0.2,0.6);
let vec = createVector(it.x - this.loc.x, it.y - this.loc.y);
vec.normalize();
forceX += vec.x * para;
forceY += vec.y * para;
} else {
let para = random(0.2,0.5);
let vec = createVector(this.loc.x - it.x, this.loc.y - it.y);
vec.normalize();
forceX += vec.x * para;
forceY += vec.y * para;
}
//break; // multi-tasking?
}
}
//
//force to acc
this.acc.x = forceX/this.m;
this.acc.y = forceY/this.m;
}
getDirection(){
return this.v.heading() + PI/2;
}
}
class Item {
constructor(x,y,type){
this.x = x;
this.y = y;
this.type = type;
//this.v = createVector(0,0);
//this.acc = createVector(0,0);
this.lifeSpan = floor(random(15, 20)) * frameRate();
this.lifeCount = 0;
this.shouldBeRemoved = false;
this.m = floor(random(2000, 5000));
}
update(){
this.lifeCount ++;
if (this.lifeCount > this.lifeSpan) this.shouldBeRemoved = true;
this.x += (0.5 - noise(this.lifeCount/1000 + this.lifeSpan)) / 2;
this.y += (0.5 - noise(this.lifeCount/1000 + this.lifeSpan*2)) / 2;
this.x = constrain(this.x, 5, width - 5);
this.y = constrain(this.y, 5, height - 5);
}
render(){
switch(this.type) {
default:
break;
case "massUp":
push();
translate(this.x, this.y);
scale(itemScale);
rotate(this.lifeCount/20);
noFill();
strokeWeight(0.5);
stroke(neonPink);
push();
for (let i = 0; i < 4; i ++) {
triangle(0, -2, 1, -5, -1, -5);
rotate(PI/2);
}
pop();
pop();
break;
case "ribbonOff":
push();
translate(this.x, this.y);
scale(itemScale);
rotate(this.lifeCount/20 + PI/4);
noFill();
strokeWeight(0.5);
stroke(neonPink);
push();
for (let i = 0; i < 10; i ++) {
ellipse(2, -2, 3, 3);
rotate(PI/5);
}
pop();
pop();
break;
case "jumpKit":
push();
translate(this.x, this.y);
scale(itemScale);
rotate(this.lifeCount/20 + PI/2);
noFill();
strokeWeight(0.5);
stroke(neonPink);
push();
for (let i = 0; i < 6; i ++) {
rect(0, 0, 5, 5);
rotate(PI/3);
}
pop();
pop();
break;
case "Lturn":
push();
translate(this.x, this.y);
scale(itemScale);
rotate(this.lifeCount/20 + PI * 3 / 4);
noFill();
stroke(255);
strokeWeight(0.5);
textAlign(CENTER, CENTER);
textSize(10);
text("L",0,0);
pop();
break;
case "Uturn":
push();
translate(this.x, this.y);
scale(itemScale);
rotate(this.lifeCount/20 + PI);
noFill();
stroke(255);
strokeWeight(0.5);
textAlign(CENTER, CENTER);
textSize(10);
text("U",0,0);
pop();
break;
}
}
}
preload = function () {
anuratiFont = loadFont(AnuratiUrl);
//a by type designer Emmeran Richard.
//https://www.behance.net/gallery/33704618/ANURATI-Free-font
}
setup = function () {
createCanvas(650,650);
background(250);
frameRate(30);
if (itemInterval) {
clearInterval(itemInterval);
itemInterval = undefined;
}
}
let things = [];
const getThingC = function(){
let pool = [
"#d793bb","#1071b7","#83c8a3","#e7caa1","#e4a590"
];
return random(pool);
}
class Thing{
constructor(p1, p2){
this.p1 = p1;
this.p2 = p2;
this.c = getThingC();
this.act = true;
this.center = createVector((this.p1.loc.x + this.p2.loc.x)/2, (this.p1.loc.y + this.p2.loc.y)/2);
this.r = dist(this.p1.loc.x, this.p1.loc.y, this.p2.loc.x, this.p2.loc.y)/2;
this.count = 0;
this.gapUp = random(0.05,0.2);
this.gapDown = random(0.05,0.2);
this.noiseStart1 = floor(random(40,10000));
this.noiseStart2 = floor(random(40,10000));
this.cp1 = random([-1,1]);
}
update(){
if (!this.p1 || !this.p2 || this.p1.destroyed || this.p2.destroyed) {
this.act = false;
}
this.center = createVector((this.p1.loc.x + this.p2.loc.x)/2, (this.p1.loc.y + this.p2.loc.y)/2);
this.r = dist(this.p1.loc.x, this.p1.loc.y, this.p2.loc.x, this.p2.loc.y)/2;
this.count ++;
this.gapUp += random(-0.001, 0.001);
this.gapDown += random(-0.001, 0.001);
this.gapUp = constrain(this.gapUp, 0.05, 0.2);
this.gapDown = constrain(this.gapDown, 0.05, 0.2);
}
render(){
push();
noFill();
strokeWeight(0.5);
// let temc = color(this.c);
// //temc.setAlpha(map(this.r, 0, 400, 100, 20));
// //s.stroke(temc);
// let hue = hue(temc);
// let sat = saturation(temc);
// let bri = brightness(temc);
// hue += (0.5 - noise(this.count / 100 + 40)) * 30;
// hue = (360 + hue)%360;
// colorMode(HSB);
stroke(map(this.r, 0, 400, 0, 250), map(this.r, 0, 400, 100, 50));
translate(this.center.x, this.center.y);
let d = createVector(this.p1.loc.x - this.p2.loc.x, this.p1.loc.y - this.p2.loc.y);
rotate(d.heading());
let cupx = noise(this.count/100 + this.noiseStart1) * this.r * 2 - this.r;
let cdownx = noise(this.count/100 + this.noiseStart2) * this.r * 2 - this.r;
bezier(-this.r, 0, cupx , - this.gapUp* 2 * this.r * this.cp1, cdownx, this.gapDown * 2 * this.r * this.cp1, this.r, 0);
pop();
}
}
const circleIntersection = function(cir1, cir2){
let d = dist(cir1.center.x,cir1.center.y,cir2.center.x,cir2.center.y)
if (cir1.r + cir2.r < d || d < abs(cir1.r - cir2.r) || d < 5) return false; // no intersection
let a = (cir1.r * cir1.r - cir2.r * cir2.r + d*d) / (2 * d);
let midx = cir1.center.x + (a * (cir2.center.x - cir1.center.x)) / d;
let midy = cir1.center.y + (a * (cir2.center.y - cir1.center.y)) / d;
let h = sqrt(cir1.r * cir1.r - a*a);
let p1x = midx + h*(cir2.center.y - cir1.center.y ) / d;
let p1y = midy - h*(cir2.center.x - cir1.center.x ) / d;
let p2x = midx - h*(cir2.center.y - cir1.center.y ) / d;
let p2y = midy + h*(cir2.center.x - cir1.center.x ) / d;
return [s.createVector(p1x, p1y), createVector(p2x, p2y)];
}
const polygon = function (x, y, radius, npoints) {
let angle = PI*2 / npoints;
beginShape();
for (let a = 0; a < PI*2; a += angle) {
let sx = x + cos(a) * radius;
let sy = y + sin(a) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
}
let cColor = getThingC();
draw = function () {
if (!started){
//not started
//init screen
push();
textFont(anuratiFont);
textSize(40);
fill(0);
noStroke();
textAlign(CENTER, CENTER);
text("SPLINE CURVES", width/2, height/2);
pop();
noLoop();
} else {
//setting interval function lines
if (!itemInterval) {
itemInterval = setInterval(spawnItem,2000);
for (let no = 0; no < maxCycleNo; no++) {
cycles.push(new LightCycle(createVector(width/2,height/2),getColor(),createVector(random(-1,1),random(-1,1)), 3));
}
// things.push(new Thing(cycles[cycles.length - 1], cycles[0]));
// for (let i = 1; i < cycles.length; i++) {
// things.push(new Thing(cycles[i - 1],cycles[i]));
// }
background(250);
}
//
if (!paused){
//main
//drawGrid(10);
//s.background(250);
//draw items (if any)
if (items.length > 0) {
let i = items.length - 1;
while (i > -1) {
if (items[i].shouldBeRemoved) {
items.splice(i, 1);
}
i --;
}
items.forEach(it => {
it.update();
//it.render();
});
}
//
//update circles
// if (things.length > 0) {
// let i = things.length - 1;
// while (i > -1){
// if (!things[i].act) {
// things.splice(i, 1);
// } else {
// things[i].update();
// }
// i--;
// }
// }
//
//draw cycles
if (cycles.length > 0) {
let i = cycles.length - 1;
while (i > -1) {
if (cycles[i].destroyed) {
cycles.splice(i, 1);
}
i --;
}
}
cycles.forEach(c => {
c.update();
//c.render();
})
if (cycles.length < maxCycleNo) {
newPlayerIn();
cColor = getThingC();
}
// draw a contour
push();
let temc = color(cColor);
//temc.setAlpha(map(this.r, 0, 400, 100, 20));
//s.stroke(temc);
let hue = hue(temc);
let sat = saturation(temc);
let bri = brightness(temc);
hue += (0.5 - noise(frameCount / 100 + 40)) * 30;
hue = (360 + hue)%360;
colorMode(HSB);
stroke(hue, sat, bri, 0.2);
strokeWeight(0.5);
noFill();
curveTightness((0.5 - noise(this.frameCount/120 + 144)) * 5);
beginShape();
curveVertex(cycles[cycles.length - 1].loc.x, cycles[cycles.length - 1].loc.y);
cycles.forEach(cycle => {s.curveVertex(cycle.loc.x, cycle.loc.y)});
curveVertex(cycles[0].loc.x, cycles[0].loc.y);
curveVertex(cycles[1].loc.x, cycles[1].loc.y);
endShape();
pop();
//things.forEach(c => c.render());
//
//s.noLoop();
} else {
noLoop();
}
}
}
mouseClicked = function () {
if (!(mouseX>0&&s.mouseX<s.width&&s.mouseY>0&&s.mouseY<s.height)) return;
if (!started) {
started = true;
loop();
} else {
if (paused) {
paused = false;
loop();
} else {
paused = true;
}
}
}