Javascript: The Good Parts, adding icons for the Reactickles and fixing bugs in KeyboardFountain

After reading Javascript: The Good Parts by Douglas Crockford I decided to make some minor changes to the codebase of the suite:

  1. Instead of using the postfix increment operator ++ use += 1
  2. Instead of using the == comparison operator which uses type coercion , use the more reliable ===. Similarly for !=, use !==.

After making those changes, I screen grabbed each Reactickle to make an icon. Midway through the process I realised that it was difficult to tell the difference between some of the mouse based interactions and the keyboard interactions, so decided to make animated video icons instead of a still.

I used Screeny to be able to grab at a fixed pixel dimension of 256×256, but drawn at 128×128 in the main menu.

After adding the video thumbnails I spotted a bug with the KeyboardFountain – objects were being added to the matter.js simulation, but never removed.

First I made sure that the array holding all the circles was empty before I started adding to it:

 while(this.circles.length > 0) { // https://stackoverflow.com/questions/1232040/how-do-i-empty-an-array-in-javascript
 this.circles.pop();
 }

Secondly, I made sure the matter.js World had been cleared before adding new elements to it:

 World.clear(world);

Finally, I noticed that the circles in KeyboardFountain were being drawn incorrectly, overlapping with each other. I realised two things:

  1. I needed to set the ellipseMode to RADIUS:
    ellipseMode(RADIUS);
  2. I needed to draw the circles in the simulation in a new way because of this mode change:
    ellipse(0, 0, r); //previously ellipse(0, 0, r *2)

    The updated suite can be seen here.

Fixing bugs in the beta version of the suite

After making the first version of the full suite, I duplicated the project and set to work on ironing out bugs.

I added the title of the Reactickle that is currently running to the back button which allows you to return to the menu screen.

 this.updateButtonText = function(textToAdd){
 this.title = textToAdd+"\nClick to return to Main Menu";
 }

I used the “\n” special character to make sure that the text instructions were on the next line.

While looking at the discussion in the above link, I discovered that the text() command in p5.js has optional width and height parameters, to make sure that the text stays within a defined area. I implemented this throughout the code.

 this.draw = function(){
 textSize(characterSize);
 noStroke();
 fill(this.backgroundColour);
 rect(this.position.x, this.position.y, this.dimensions.x, this.dimensions.y);
 fill(this.textColour);
 text(this.title, this.position.x+5, this.position.y+20, this.dimensions.x-5, this.dimensions.y-20); //https://p5js.org/reference/#/p5/text
 }

Taking each Reactickle one by one, I started with KeyboardScalingCircleGrid.

On line 14 of KeyboardScalingCircleGrid.js, I had a console output, detailing the size of the array that held each of the ScalingCircles, one for each keyboard button I was taking input from. Repeatedly starting the Reactickle and going back to the main menu resulting in the following output:

KeyboardScalingCircleGrid.js:14 The size of the circles array is 216
KeyboardScalingCircleGrid.js:14 The size of the circles array is 252
KeyboardScalingCircleGrid.js:14 The size of the circles array is 288

It was clear that I needed to treat the array in a different way, by emptying it at the beginning of the setup method of KeyboardScalingCircleGrid.js. Looking in the Javascript basics part of the p5.js wiki pointed me at the MDN array reference page, which didn’t have a clear method. I found a StackOverflow post on how to empty an array in Javascript, and ended up using the following code:

while(A.length > 0) {
 A.pop();
}

I added this to every other Reactickle that had a similar array bug.

While testing every Reactickle to confirm that I’d expunged the array bug, I discovered that that both KeyboardSquares.js and KeyboardFountain.js used a centred rectMode() rather than a cornered one. I added a rectMode() change before each of their draw calls, as well as reseting to corner mode in the main sketch.js to ensure that the GUI was drawn correctly.

Next will be creating some icons for the Reactickles, rather than the current placeholder text.

 

Making the Fountain part of the physics simulation and creating a suite of Reactickles

After trying and failing to get a triangle to represent the fountain in KeyboardFountain, I reverted to using a rectangle, which functions nicely.

The new sketch can be seen here.

After talking to Wendy, we agreed that the next step was to get all the individual sketches working in a suite.

I created a new folder to contain the suite: Reactickles3, and started by trying to run KeyboardScalingCircleGrid within a larger p5.js sketch.

I created a new sketch.js in the Reactickles3 folder and copied KeyboardScalingCircleGrid.js into the same folder. After making the entire KeyboardScalingCircleGrid a class of its own, I was able to call its setup, draw and keyTyped methods from within my main sketch.js.

With that functioning, I started to test other Reactickles in this new structure, starting with KeyboardWorm, followed by MouseWorm, KeyboardSpringyCircles, MouseSpringyCircles, KeyboardSquares and finally KeyboardFountain.

Once those were working, I created an array of those Reactickles as well as a main menu screen and a return to main menu button. There are still some bugs in the system, but I’m going to save those for the next session.

KeyboardSquares and KeyboardFountain with Matter.js

The original KeyboardSquares can be seen above – on key presses the larger square broke up into smaller squares and flew apart, as well as changing colour. It was a relatively quick port to create, based on previously ported Reactickles. One sticking point was making sure that the larger square was always centred – even if the user changed the size of the window of the browser. Luckily p5.js has a windowResized() function to detect that. I also found some different options for emptying an array in JavaScript. The ported version can be found here.

The next Reactickle was Keyboard Fountain which can be seen from 01:43- 02:20 on the video above.

After creating KeyboardFountain in my local Git repository, I set about creating the “fountain” at the bottom of the screen. Conveniently, p5.js has an existing triangle drawing function, called triangle(). Quoting from the reference:

Description
A triangle is a plane created by connecting three points. The first two arguments specify the first point, the middle two arguments specify the second point, and the last two arguments specify the third point.

Syntax
triangle(x1,y1,x2,y2,x3,y3)

I created the following function to draw my Fountain:

function drawFountain(){
 var fountainWidth = 50; //50 pixels wide
 var fountainHeight = 50; //50 pixels high
 var translatedX = this.fountainPosition.x * windowWidth;
 var translatedY = this.fountainPosition.y * windowHeight;
 var redColour = color(0,100,100,100);
 fill(redColour);
 triangle(translatedX-(fountainWidth/2),translatedY,translatedX+(fountainWidth/2),translatedY,translatedX,translatedY-fountainHeight); //https://p5js.org/reference/#/p5/triangle
}

After I was happy with the drawing of my Fountain, I added interactivity by using the p5.js keyCode variable to allow the user to use the left and right arrows keys to move the fountain:

function keyPressed(){
 var moveAmount = 0.01;
 if (keyCode == LEFT_ARROW) { //https://p5js.org/reference/#/p5/keyCode
 if(this.fountainPosition.x >= moveAmount){
 this.fountainPosition.x -= moveAmount;
 }
 } else if (keyCode == RIGHT_ARROW) {
 if(this.fountainPosition.x <= (1-moveAmount)){
 this.fountainPosition.x += moveAmount;
 }
 }
 return false; // prevent default
}

However, this meant that the user had to repeatedly press the arrow keys to move, whereas I wanted the Fountain to keep moving as long as I held the key down. I found the keyIsDown function which allowed me to have the Fountain move in the way that I wanted:

function moveFountain(){
 var moveAmount = 0.005;

if (keyIsDown(LEFT_ARROW) && (this.fountainPosition.x >= moveAmount))
 this.fountainPosition.x -= moveAmount;

if (keyIsDown(RIGHT_ARROW) && (this.fountainPosition.x <= (1-moveAmount)))
 this.fountainPosition.x += moveAmount;
}

In order to get the particles flying out of the fountain in a realistic way, I new I would have to create some kind of 2D physics simulation – at the very least using basic projectile physics. In the spirit of keeping it simple, I looked online to see if there were any existing physics libraries that worked with p5.js. Thanks to the fantastic Coding Train by Daniel Shiffman, I discovered matter.js.

Daniel was also kind enough to provide source code for p5.js running with matter.js on his GitHub.

I initialised matter.js by duplicating most of Daniel’s code in a new function called setupPhysics, after declaring the necessary variables for matter.js to run:

//matter-js and p5.js integration based on https://github.com/shiffman/p5-matter by Daniel Shiffman
//also see https://www.youtube.com/watch?v=urR596FsU68 introduction to matter.js by Daniel Shiffman
var Engine = Matter.Engine;
//var Render = Matter.Render; // commented out as we are using p5.js to render everything to the screen
var World = Matter.World;
var Bodies = Matter.Bodies;
var Body = Matter.Body;
var Composite = Matter.Composite;
var Composites = Matter.Composites;

var engine;
var world;
var bodies;
var canvas;

function setupPhysics(){
 // create an engine
 engine = Engine.create();
 world = engine.world;

//make walls to constrain everything
 var params = {
 isStatic: true
 }
 var ground = Bodies.rectangle(width / 2, height, width, 1, params);
 var leftWall = Bodies.rectangle(0, height / 2, 1, height, params);
 var rightWall = Bodies.rectangle(width, height / 2, 1, height, params);
 var top = Bodies.rectangle(width / 2, 0, width, 1, params);
 World.add(world, ground);
 World.add(world, leftWall);
 World.add(world, rightWall);
 World.add(world, top);

// run the engine
 Engine.run(engine);
}

I changed keyTyped to trigger a new function, addCircle – as well as adding code to draw all the new circles that would be created:

 

function addCircle(){
 var params = {
 restitution: 0.7,
 friction: 0.2
 }
 var translatedX = this.fountainPosition.x * windowWidth;
 var translatedY = this.fountainPosition.y * windowHeight;
 var radius = 21;
 var newCircle = Bodies.circle(translatedX, translatedY-(fountainHeight), radius, params);
 circles.push(newCircle);
 World.add(world, newCircle);

//set a random velocity of the new circle
 //see http://brm.io/matter-js/docs/classes/Body.html
 //from http://codepen.io/lilgreenland/pen/jrMvaB?editors=0010#0
 Body.setVelocity(newCircle, {
 x: random(-5,5),
 y: -random(15,30)
 });
}

function drawCircles(){
 stroke(255);
 strokeWeight(1);
 fill(randomColour);

for (var i = 0; i < circles.length; i++) {
 var circle = circles[i];
 var pos = circle.position;
 var r = circle.circleRadius;
 var angle = circle.angle;
 push();
 translate(pos.x, pos.y);
 rotate(angle);
 ellipse(0, 0, r * 2);
 line(0, 0, r, 0);
 pop();
 }
}

function keyTyped(){
 addCircle();
 return false; //https://p5js.org/reference/#/p5/keyTyped preventing default behaviour
}

After adding drawCircles to my overall draw function, I had a working prototype, with source code available on the project GitHub as normal.

The next steps will be to add the fountain to the simulation, as well as code to make each circle random in size and colour.

Trying Easing and more on the springiness of Circles

I found this explanation of Robert Penner’s easing equations very useful – the most useful being the idea that one is feeding a timing value between 0 and 1 to the easing function to get back another value how far along the transformation in question you are – and that this value may be greater than 1 or less than zero. This is exactly what I want in my springy circles – for them to overshoot their target and oscillate a few times before settling at the final position.

Starting with KeyboardSpringyCircles, I started adding the logic to allow for non-linear tweens for movement. I got timing working by using the the millis() function of p5.js.

function SpringyCircle(){ //SpringyCircle object
 this.colour = color(random(100),50,100,50);; //random hue, saturation 50%, brightness 100%, alpha 50%
 this.radius = random(circleMinRadius,circleMaxRadius);
 this.position = createVector(random(windowWidth)/windowWidth,random(windowHeight)/windowHeight);
 this.startPosition = createVector(this.position.x, this.position.y);
 this.startPosition.y += 0.15; //want to start 15% of the screen down when the circle is interacted with
 this.durationOfTween = 1000; //1000 milliseconds for tween
 this.endPosition = createVector(this.position.x, this.position.y); //want to finish back where we started
 this.startTimeOfTween = -1;

this.display = function(){
 var milliseconds = millis();
 var elapsedMillisSinceStartOfTween = milliseconds - this.startTimeOfTween;
 if(this.startTimeOfTween > 0 && elapsedMillisSinceStartOfTween < this.durationOfTween){
 var changeBetweenStartAndEnd = this.endPosition.y - this.startPosition.y;
 var ratioOfTweenComplete = elapsedMillisSinceStartOfTween/this.durationOfTween;
 var changeUpToNow = changeBetweenStartAndEnd*this.bouncePast(ratioOfTweenComplete);
 this.position.y = this.startPosition.y + changeUpToNow;
 }
 var translatedX = this.position.x * windowWidth;
 var translatedY = this.position.y * windowHeight;
 fill(this.colour);
 ellipse(translatedX, translatedY, this.radius); // https://p5js.org/reference/#/p5/ellipse and https://p5js.org/reference/#/p5/ellipseMode
 }

this.startTween = function(){ //move the position of the spring a bit
 print("Starting a tween");
 this.startTimeOfTween = millis();
 }

this.bouncePast = function(howFarThroughTween){
 //see https://github.com/jeremyckahn/shifty/blob/master/src/shifty.formulas.js
 //and http://upshots.org/actionscript/jsas-understanding-easing
 //and of course http://robertpenner.com/easing/
 if (howFarThroughTween < (1 / 2.75)) {
 return (7.5625 * howFarThroughTween * howFarThroughTween);
 } else if (howFarThroughTween < (2 / 2.75)) {
 return 2 - (7.5625 * (howFarThroughTween -= (1.5 / 2.75)) * howFarThroughTween + 0.75);
 } else if (howFarThroughTween < (2.5 / 2.75)) {
 return 2 - (7.5625 * (howFarThroughTween -= (2.25 / 2.75)) * howFarThroughTween + 0.9375);
 } else {
 return 2 - (7.5625 * (howFarThroughTween -= (2.625 / 2.75)) * howFarThroughTween + 0.984375);
 }
 }
}

One bug that caused me intense frustration was that my local webserver didn’t seem to be serving the latest code when I made an update. Eventually I tracked it down to Chrome caching files wherever possible – meaning that I had to use Command Shift R to force a reload of all the files being served – not just the HTML.

I also managed to repeated Vector copying values explicitly bug from late November, by doing writing:

this.startPosition = this.position;

Rather than:

this.startPosition = createVector(this.position.x, this.position.y);

At this stage I had the following interaction working:

Which was a good start, but I didn’t like the precise settling animation, so I decided to create a new external JS file with all the easing equations contained in one convenient place. This would allow me to use all the easing equations in other places in my code.

I created easing.p5.jgl.js and keyboard.p5.jgl.js to contain all my easing logic and keyboard logic respectively. After adding both references to my index.html file:

 <script language="javascript" src="../libraries/keyboard.p5.jgl.js"></script>
 <script language="javascript" src="../libraries/easing.p5.jgl.js"></script>
 <script language="javascript" type="text/javascript" src="sketch.js"></script>

I began going through all the possible options for easing:

 var ratioOfEaseComplete = elapsedMillisSinceStartOfEase/this.durationOfEase;
 // exhaustively trying all the different easing possibilities
 // var changeUpToNow = changeBetweenStartAndEnd*easeOutQuad(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInOutQuad(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInCubic(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeOutCubic(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInOutCubic(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInQuart(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeOutQuart(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInOutQuart(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInQuint(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeOutQuint(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInOutQuint(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInSine(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeOutSine(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInOutSine(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInExpo(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeOutExpo(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInOutExpo(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInCirc(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeOutCirc(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInOutCirc(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeOutBounce(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInBack(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeOutBack(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeInOutBack(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*elastic(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*swingFromTo(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*swingFrom(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*swingTo(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*bounce(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*bouncePast(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeFromTo(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeFrom(ratioOfEaseComplete);
 // var changeUpToNow = changeBetweenStartAndEnd*easeTo(ratioOfEaseComplete);

I broke this down to the following options, which felt like they were in the correct ballpark:

 // var changeUpToNow = changeBetweenStartAndEnd*easeOutBounce(ratioOfEaseComplete);
 // easeOutBounce is good but not right
 // var changeUpToNow = changeBetweenStartAndEnd*easeOutBack(ratioOfEaseComplete);
 // easeOutBack is also good but not right
 // var changeUpToNow = changeBetweenStartAndEnd*elastic(ratioOfEaseComplete);
 // elastic is good
 // var changeUpToNow = changeBetweenStartAndEnd*swingTo(ratioOfEaseComplete);
 // swingTo is good
 // var changeUpToNow = changeBetweenStartAndEnd*bounce(ratioOfEaseComplete);
 // bounce is good
 // var changeUpToNow = changeBetweenStartAndEnd*bouncePast(ratioOfEaseComplete);
 // bouncePast is good, but feels abrupt at end

It was proving laborious to manually change the code every time I wanted to see how the particular easing function looked. Luckily, p5.js comes with a library called p5.dom:

The web is much more than just canvas and p5.dom makes it easy to interact with other HTML5 objects, including text, hyperlink, image, input, video, audio, and webcam.

p5.dom was even already included, but commented out in my index.html file, so I just removed the comment:

 <script language="javascript" type="text/javascript" src="../libraries/p5.js"></script>
 <!-- uncomment lines below to include extra p5 libraries -->
 <script language="javascript" src="../libraries/p5.dom.js"></script>
 <!--<script language="javascript" src="../libraries/p5.sound.js"></script>-->
 <script language="javascript" src="../libraries/keyboard.p5.jgl.js"></script>
 <script language="javascript" src="../libraries/easing.p5.jgl.js"></script>
 <script language="javascript" type="text/javascript" src="sketch.js"></script>

I created a new select object:

 sel = createSelect();
 sel.position(10, 10);
 sel.option('easeOutBounce');
 sel.option('easeOutBack');
 sel.option('elastic');
 sel.option('swingTo');
 sel.option('bounce');
 sel.option('bouncePast');

Following that I added some logic to link the select object to the selection of the particular easing function that I wanted, using the JavaScript switch statement:

var changeUpToNow = 0;
 var easeOption = sel.value();

switch(easeOption){
 case 'easeOutBounce':
 changeUpToNow = changeBetweenStartAndEnd*easeOutBounce(ratioOfEaseComplete);
 break;
 case 'easeOutBack'):
 changeUpToNow = changeBetweenStartAndEnd*easeOutBack(ratioOfEaseComplete);
 break;
 case 'elastic'):
 changeUpToNow = changeBetweenStartAndEnd*elastic(ratioOfEaseComplete);
 break;
 case 'swingTo'):
 changeUpToNow = changeBetweenStartAndEnd*swingTo(ratioOfEaseComplete);
 break;
 case 'bounce'):
 changeUpToNow = changeBetweenStartAndEnd*bounce(ratioOfEaseComplete);
 break;
 case 'bouncePast'):
 changeUpToNow = changeBetweenStartAndEnd*bouncePast(ratioOfEaseComplete);
 break;
 default:
 changeUpToNow = changeBetweenStartAndEnd*bouncePast(ratioOfEaseComplete);
 break;
 }

Using the “Beyond the canvas” p5.js tutorial, I added an HTML text label to select object make it clearer for users. The demo can be tried here: KeyboardSpringyCirclesWithEaseSelect.

I decided that “elastic” was the closest to the original version, but still wasn’t satisfied with the result. I found this spring example on the Processing.js site and decided to port it to p5.js. After realising that I had to rewrite the code to use the static methods of the p5.vector class, I got to something that was much closer to the original version. I increase the number of circles to 100 as an added bonus. Give the updated KeyboardSpringyCircles demo a try.

I folded the updated spring code into KeyboardBouncingCircleGrid and MouseSpringyCircles too!

Fixing the the springyness of Circles

As my first post of 2017 I decided to revisit the three most recent Reactickles ports that I made at the end of 2016:

This is how the previous interaction looked in the original Reactickles:

And this is how it was looking in my original ports:

I.e. much too linear and constrained. Having a look at Robert Penner’s original easing equations again, and some of their online explanations made me think that what I want is in fact an “elasticOut” aka “bouncepast” aka movement. The next step will be to port these algorithms to p5.js.

Fixing an circle (or ellipse) selection bug

Last night I read “Getting Started with p5.js” by Lauren McCarthy, Casey Reas and Ben Fry. This morning I tweeted out a photo of me with the book:

It’s true! By page 11 I’d realised what had been causing the bug that had been bothering me yesterday. My code for clicking to select which circle should bounce was incorrect:

 if(distanceBetweenMouseAndCircle < (springyCircles[i].radius)){ //this is resulting in a bug - radius/2 works properly, but why isn't radius alone giving the correct interaction?
 //if the mouse is under the springy circle, then spring/move it
 springyCircles[i].moveSpring();
 }

From checkIfCirclesShouldSpring inside the MouseSpringyCircles Reactickle. The reason was that the code for drawing an Ellipse also has an ellipseMode function which dictates whether it should be drawn taking a width parameter or a radius parameter. By issuing the following command in my setup(), I could set to draw with the correct radius parameter:

ellipseMode(RADIUS);

This fixed all my circle selection code. The next step is to convert all my radius variables to be proportional to the screen size (as my position co-ordinates already are) rather than being pixel based. It’s important to me that Reactickles 3 is resolution independent – i.e. that it looks the same on a variety of different screen sizes and proportions.

Making KeyboardSpringyCircles, MouseSpringyCircles and KeyboardBouncingCircleGrid

My first Reactickle for today was KeyboardSpringyCircles, which can be seen at 2:31 on the video that Wendy shot:

I started by duplicating KeyboardScalingCircleGrid, then creating a new SpringyCircle object:

function SpringyCircle(){ //SpringyCircle object
 var circleMinRadius = 100;
 var circleMaxRadius = 200;
 this.colour = color(random(100),50,100,50);; //random hue, saturation 50%, brightness 100%, alpha 50%
 this.radius = random(circleMinRadius,circleMaxRadius);
 this.position = createVector(random(windowWidth)/windowWidth,random(windowHeight)/windowHeight);

// Spring simulation from https://p5js.org/examples/simulate-spring.html
 // Spring simulation constants
 this.M = 0.8; // Mass
 this.K = 0.2; // Spring constant
 this.D = 0.92; // Damping
 this.R = this.position.y; // Rest position

// Spring simulation variables
 //this.ps = R, // Position, not needed as we have this.position.y
 this.vs = 0.0, // Velocity
 this.as = 0, // Acceleration
 this.f = 0; // Force

this.display = function(){
 this.spring();
 var translatedX = this.position.x * windowWidth;
 var translatedY = this.position.y * windowHeight;
 fill(this.colour);
 ellipse(translatedX, translatedY, this.radius, this.radius);
 }

this.spring = function(){
 // Update the spring position
 this.f = -this.K * ( this.position.y - this.R ); // f=-ky
 this.as = this.f / this.M; // Set the acceleration, f=ma == a=f/m
 this.vs = this.D * (this.vs + this.as); // Set the velocity
 this.position.y = this.position.y + this.vs; // Updated position

if (abs(this.vs) < 0.1) {
 this.vs = 0.0;
 }
 }

this.moveSpring = function(){ //move the position of the spring a bit
 this.position.y += 0.2; //move a 5th of the screen down
 }
}

As noted in the source code comments, the spring simulation was based on the spring p5.js example. Once I had the circles randomly generating their position, colour and radius, I had to add the interaction – springing if a key was pressed within their area:

function keyTyped(){
 var lowerCaseKey = key.toLowerCase(); //key is a system variable via https://p5js.org/reference/#/p5/key, toLowerCase via http://www.w3schools.com/jsref/jsref_tolowercase.asp

if(allTheKeys.includes(lowerCaseKey)){
 //if the key is a valid one, circles nearby should react
 var positionOfKey = getCanvasPositionFromKey(lowerCaseKey);
 var positionOfKeyInPixels = createVector(positionOfKey.x * windowWidth, positionOfKey.y * windowHeight);

for (var i = 0; i < springyCircles.length; i++) {
 //for all the springyCircles
 var positionOfCircleInPixels = createVector(springyCircles[i].position.x * windowWidth, springyCircles[i].position.y * windowHeight);
 var radiusOfCircle = springyCircles[i].radius;
 var distanceBetweenKeyAndCircle = dist(positionOfKeyInPixels.x, positionOfKeyInPixels.y, positionOfCircleInPixels.x, positionOfCircleInPixels.y);

if(distanceBetweenKeyAndCircle < radiusOfCircle){
 //if the key is under the springy circle, then spring/move it
 springyCircles[i].moveSpring();
 }

}
 }

return false; //https://p5js.org/reference/#/p5/keyTyped preventing default behaviour
}

Try the KeyboardSpringyCircles demo.

I wanted this to work with mouse clicks and touches on mobile, so I quickly created MouseSpringyCircles, which swapped the above keyboard code for a mouse or touch interaction:

function checkIfCirclesShouldSpring(){
 for (var i = 0; i < springyCircles.length; i++) {
 //for all the springyCircles
 var positionOfCircleInPixels = createVector(springyCircles[i].position.x * windowWidth, springyCircles[i].position.y * windowHeight);
 var distanceBetweenMouseAndCircle = dist(mouseX, mouseY, positionOfCircleInPixels.x, positionOfCircleInPixels.y); //https://p5js.org/reference/#/p5/dist

if(distanceBetweenMouseAndCircle < (springyCircles[i].radius)){ //this is resulting in a bug - radius/2 works properly, but why isn't radius alone giving the correct interaction?
 //if the mouse is under the springy circle, then spring/move it
 springyCircles[i].moveSpring();
 }

}
}

function touchMoved(){
 checkIfCirclesShouldSpring();
 return false; //https://p5js.org/reference/#/p5/touchMoved
}

function mouseReleased(){
 checkIfCirclesShouldSpring();
}

function keyTyped(){
 var lowerCaseKey = key.toLowerCase(); //key is a system variable via https://p5js.org/reference/#/p5/key, toLowerCase via http://www.w3schools.com/jsref/jsref_tolowercase.asp

return false; //https://p5js.org/reference/#/p5/keyTyped preventing default behaviour
}

Try the MouseSpringyCircles demo.

As noted in the source code, there is a bug on selection of circles which I am going to come back to tomorrow.

The last of the three Reactickles that I wanted to port was KeyboardBouncingCircleGrid, which I made by combining KeyboardSpringyCircles with the previously created KeyboardScalingCircleGrid.

Try the KeyboardBouncingCircleGrid demo.

 

KeyboardWorm and MouseWorm

After talking things through with Wendy yesterday, I moved on from KeyBoardScalingCircleGrid to KeyboardSnake. Wendy pointed out the multicoloured background wasn’t necessary for this version – so I could concentrate on making the interaction work with a single colour for the snake. I decided to change the name to the slightly more friendly KeyboardWorm.

I began by creating a WormSegment object:

function WormSegment(aColour,aRadius){ //WormSegment object
 this.radius = aRadius;
 this.position = createVector(0.5,0.5); //start in centre of screen
 this.colour = aColour;

this.display = function(){
 var translatedX = this.position.x * windowWidth;
 var translatedY = this.position.y * windowHeight;
 fill(this.colour);
 ellipse(translatedX, translatedY, this.radius, this.radius);
 }
}

Then created an array of these segments to make up a worm:

var worm = []; //array of WormSegment objects
var wormLength = 30; //number of segments of the worm

and

function setup() {
 createCanvas(windowWidth,windowHeight); //make a fullscreen canvas, thanks to: http://codepen.io/grayfuse/pen/wKqLGL
 noStroke(); //no outlines, just filled shapes
 colorMode(HSB, 100);// Use HSB with scale of 0-100, see https://p5js.org/reference/#/p5/color
 var segmentColourMaxBrightness = 100;
 var segmentColourMinBrightness = 80;
 var segmentColourBrightnessRatio = (segmentColourMaxBrightness-segmentColourMinBrightness)/wormLength;
 var segmentColour = color(random(100),50,segmentColourMaxBrightness, 100); //random hue, saturation 50%, brightness 100%, alpha 100%
 var segmentMaxRadius = 100;
 var segmentMinRadius = 70;
 var segmentRadiusRatio = (segmentMaxRadius-segmentMinRadius)/wormLength;
 var segmentRadius = segmentMaxRadius;

for (var i=0; i < wormLength; i++) {
 worm.push(new WormSegment(segmentColour, segmentRadius));
 segmentRadius -= segmentRadiusRatio;
 segmentColour = color(hue(segmentColour), saturation(segmentColour), brightness(segmentColour)-segmentColourBrightnessRatio, alpha(segmentColour));
 }
 console.log("The length of the worm is " + worm.length);
}

I changed my draw() function to the following:

function draw() {
 background(255); //white background
 updateWorm();
 drawWorm();
}

Drawing the worm was pretty straight forward, after I realised that I had to draw the segments in reverse order so that the “head” of the worm drew last:

function drawWorm(){
 //draw the first segment of the worm last so that the shading looks correct
 for (var i = (worm.length-1); i > 0; i--) {
 worm[i].display();
 }
}

Most challenging was updating the worm:

function updateWorm(){
 seekWormTowardsKey(key);

//starting at back of the worm, copy the previous worm segments position onto the current segments position
 for (var i = (worm.length-1); i > 0; i--) {
 worm[i].position.x = worm[i-1].position.x;
 worm[i].position.y = worm[i-1].position.y;
 //had to copy both values - not the reference, worm[i].position = worm[i-1].position doesn't work
 }
}

As I noted in the comments – just setting the position as a reference didn’t work – I had to set each position x and y value manually – this was a nasty bug to track down, after two hours of drawing and redrawing my segment shifting code I finally realised my error.

Seeking the worm towards the key was a matter of checking to make sure that the key was valid, then easing towards the virtual key position – code that I had already written for the KeyboardScalingCircleGrid Reactickle.

function seekWormTowardsKey(aKey){
 var lowerCaseKey = key.toLowerCase(); //key is a system variable via https://p5js.org/reference/#/p5/key, toLowerCase via http://www.w3schools.com/jsref/jsref_tolowercase.asp

if(allTheKeys.includes(lowerCaseKey)){
 //if the key is a valid one, then seek the worm towards it
 seekWormTowardsPosition(getCanvasPositionFromKey(lowerCaseKey));
 }
}

function seekWormTowardsPosition(relativeSeekPosition){
 var easing = 0.05;
 //move the head of the worm a bit closer... via https://processing.org/examples/easing.html
 var dx = relativeSeekPosition.x - worm[0].position.x;
 worm[0].position.x += dx * easing;
 var dy = relativeSeekPosition.y - worm[0].position.y;
 worm[0].position.y += dy * easing;
}

You can try the KeyboardWorm online, as well as a MouseWorm version that I made as a bonus, which also works on Android or iOS mobile devices.

Reporting a bug properly, making KeyboardScalingCircleGrid

After tweeting about the multitouch bug to the author of p5.js, I received the following reply:

I therefore filed the bug on the p5.js GitHub.

Wendy forwarded me a video that she had shot of someone using the original version of Reactickles that I am now in the process of porting to the web. I’ve embedded it below:

The three Reactickles that I am aiming to port initially are at the following points in the video:

  1. 2:41, which I am calling KeyboardScalingCircleGrid.
  2. 3:57, which I am calling KeyboardBouncingCircleGrid.
  3. 0:37, which I am calling KeyboardSnake.

keyboardscalingcirclegridgrab

So lets start with the development of KeyboardScalingCircleGrid. I began by duplicating the code I wrote yesterday (KeyboardToScreen) and renaming the folder to KeyboardScalingCircleGrid.

I knew that I would have to create an array of ScalingCircles and instantiate them with certain values, so I had a look at the p5.js examples page to see if there were any sketches that might be useful. Sure enough, the Array of Objects example and the Objects 2 example looked perfect.

I started by creating a new ScalingCircle object:

function ScalingCircle(aKey, aCircleRadius){ //ScalingCircle object
 this.key = aKey;
 this.circleRadius = aCircleRadius;
 this.position = createVector(-1,-1);
 this.position = getCanvasPositionFromKey(aKey);
 this.colour = color(random(100),50,100); //random hue, saturation 50% and brightness 100%

this.display = function(){
 var translatedX = this.position.x * windowWidth;
 var translatedY = this.position.y * windowHeight;
 fill(this.colour);
 ellipse(translatedX, translatedY, this.circleRadius, this.circleRadius);
 }; //don't forget to close your method!
}

And creating an array of those objects inside the setup method of the sketch:

var characterSize = 50;
var circles = []; //array of ScalingCircle objects
var allTheKeys = "1234567890qwertyuiopasdfghjklzxcvbnm";
var circleRadius = 100;

function setup() {
 createCanvas(windowWidth,windowHeight); //make a fullscreen canvas, thanks to: http://codepen.io/grayfuse/pen/wKqLGL
 textSize(characterSize);
 colorMode(HSB, 100);// Use HSB with scale of 0-100, see https://p5js.org/reference/#/p5/color
 for (var i=0; i < allTheKeys.length; i++) {
 circles.push(new ScalingCircle(allTheKeys[i],circleRadius));
 }
 console.log("The size of the circles array is " + circles.length);
}

Finally, I looped through the array of circles inside the draw method of the sketch:

function draw() {
 background(255); //white background
 noStroke();
 for (var i=0; i<circles.length; i++) {
 circles[i].display();
 }
}

This resulted in the following output:

2016_11_22_firstgridofcircles_scalingcircle

The next step is to add scaling when keyboard buttons are pressed.

I added a scaleUp() method to the ScalingCircle, and changed the display method to ease towards the new targetCircleRadius (The Processing Easing example was useful for this):

function ScalingCircle(aKey, aCircleRadius){ //ScalingCircle object
 this.key = aKey;
 this.actualCircleRadius = aCircleRadius;
 this.targetCircleRadius = aCircleRadius;
 this.position = createVector(-1,-1);
 this.position = getCanvasPositionFromKey(aKey);
 this.colour = color(random(100),50,100); //random hue, saturation 50% and brightness 100%

this.display = function(){
 var differenceInRadius = this.targetCircleRadius - this.actualCircleRadius;
 var changeThisFrame = differenceInRadius*easing;
 this.actualCircleRadius += changeThisFrame;
 var translatedX = this.position.x * windowWidth;
 var translatedY = this.position.y * windowHeight;
 fill(this.colour);
 ellipse(translatedX, translatedY, this.actualCircleRadius, this.actualCircleRadius);
 }; //don't forget to close your method!

this.scaleUp = function(){
 this.targetCircleRadius = this.actualCircleRadius+10;
 }
}

Then added code to check which button was pressed, and to call the scaleUp() method on the correct ScalingCircle in the circles array:

function keyTyped(){
 var lowerCaseKey = key.toLowerCase(); //key is a system variable via https://p5js.org/reference/#/p5/key
 for (var i=0; i<circles.length; i++) {
 if(lowerCaseKey == circles[i].key){
 circles[i].scaleUp();
 }
 }
 return false; //https://p5js.org/reference/#/p5/keyTyped preventing default behaviour
}

This resulted in a pleasing scaling animation, but it wasn’t quite as fast as the video, so I altered the easing ratio to 0.3 from 0.1, as well as setting a fixed number for the target radius of each circle – initially 100 pixels, then 200 pixels on keypress. Finally, I changed the colour of the circle to be 50% transparent to match the blending effect that was visible on the Reactickles 1 demonstration video.

2016_11_22_alphacircles_scalingcircle

However, in the video I could see that the circles not only scale up, but scale back to their original size – and worse than that they don’t scale up in a linear way, but seem to “bounce” around their full size – overshooting initially and then scaling back. From previous experience in Actionscript and C++ I knew of the existence of Robert Penner’s Easing functions, and that it was very likely that they had already been implemented in p5.js. I Googled “easing functions p5.js” and found p5.ijeoma.js:

 A p5.js addon for ijeoma.js, a JS library for creating animations.

I downloaded the library and added it and its dependencies to my libraries folder. Looking at the examples and documentation I started by creating a tween for the scaling up of circles:

function ScalingCircle(aKey){ //ScalingCircle object
 this.key = aKey;
 this.startCircleRadius = 100;
 this.endCircleRadius = 200;
 this.circleRadius = this.startCircleRadius; //start with the start
 this.scaleUpDuration = 0.5; //take half of a second to scale up
 this.scaleDownDelay = this.scaleUpDuration; //wait until the scale up is down to scale down
 this.scaleDownDuration = 0.25; //take quarter of a second to scale down
 this.position = createVector(-1,-1);
 this.position = getCanvasPositionFromKey(aKey);
 this.colour = color(random(100),50,100,50); //random hue, saturation 50% and brightness 100%, alpha 50%

this.display = function(){
 var translatedX = this.position.x * windowWidth;
 var translatedY = this.position.y * windowHeight;
 fill(this.colour);
 ellipse(translatedX, translatedY, this.circleRadius, this.circleRadius);
 }; //don't forget to close your method!

this.scaleUpAndThenDown = function(){
 //syntax for tweens is createTween(object property, end, duration, [delay], [easing])
 //see https://github.com/ekeneijeoma/p5.ijeoma.js
 var scaleUpTween = createTween('this.circleRadius', this.endCircleRadius, this.scaleUpDuration).easing(Quad.In).play();
 var scaleDownTween = createTween('this.circleRadius', this.startCircleRadius, this.scaleDownDuration, this.scaleDownDelay).easing(Quad.Out).play();
 }
}

Unfortunately, this didn’t work, resulting in the JavaScript errors:

[Log] p5 had problems creating the global function "frames", possibly because your code is already using that name as a variable. You may want to rename your variable to something else. (p5.js, line 9429)
[Log] p5 had problems creating the global function "stop", possibly because your code is already using that name as a variable. You may want to rename your variable to something else. (p5.js, line 9429)
[Log] The size of the circles array is 36 (sketch.js, line 14)
[Warning] Only numbers, p5.colors and p5.vectors are supported. (p5.ijeoma.js, line 142)
[Error] TypeError: undefined is not an object (evaluating 'this._properties[i].update')
 _updateProperties (ijeoma.js:1170)
 dispatchChangedEvent (ijeoma.js:1297)
 seek (ijeoma.js:194)
 play (ijeoma.js:148)
 scaleUpAndThenDown (sketch.js:59:122)
 keyTyped (sketch.js:31)
 _onkeypress (p5.js:16261)
 (anonymous function)

I duplicated the code into an example, Tweeted at the developer, filed a bug on the library GitHub and rolled back to my previous code.

I decided to try getting elements to scale up and then down, before worrying about “bouncing”.

function ScalingCircle(aKey){ //ScalingCircle object
 this.key = aKey;
 this.circleBigRadius = 200;
 this.circleSmallRadius = 100;
 this.circleRadius = this.circleSmallRadius;
 this.position = createVector(-1,-1);
 this.position = getCanvasPositionFromKey(aKey);
 this.colour = color(random(100),50,100,50); //random hue, saturation 50% and brightness 100%, alpha 50%
 this.millisToScaleUp = 50;
 this.millisToScaleDown = 200;
 this.startScale = 0;
 this.endScale = 0;
 this.scaling = false;

this.display = function(){
 if(this.scaling){
 this.scale();
 }
 var translatedX = this.position.x * windowWidth;
 var translatedY = this.position.y * windowHeight;
 fill(this.colour);
 ellipse(translatedX, translatedY, this.circleRadius, this.circleRadius);
 }; //don't forget to close your method!

this.scale = function(){
 var now = millis();
 var millisElapsed = now-this.startScale;

if(millisElapsed < this.millisToScaleUp){
 var howFarAlongScaleUp = millisElapsed/this.millisToScaleUp;
 this.scaleUp(howFarAlongScaleUp);
 }else{
 var howFarAlongScaleDown = (millisElapsed-this.millisToScaleUp)/this.millisToScaleDown;
 this.scaleDown(howFarAlongScaleDown);
 }

if(now >= this.endScale){
 this.scaling = false;
 }
 }

this.scaleUp = function(howFarAlongScale){
 var differenceInRadius = this.circleBigRadius - this.circleSmallRadius;
 var newRadius = this.circleSmallRadius+(howFarAlongScale*differenceInRadius);
 this.circleRadius = newRadius;
 }

this.scaleDown = function(howFarAlongScale){
 var differenceInRadius = this.circleBigRadius - this.circleSmallRadius;
 var newRadius = this.circleBigRadius-(howFarAlongScale*differenceInRadius);
 this.circleRadius = newRadius;
 }

this.scaleUpandDown = function(){
 this.scaling = true;
 this.startScale = millis();
 this.endScale = this.startScale+this.millisToScaleUp+this.millisToScaleDown;
 }
}

This resulted in a scaling up and down, but without the bounce that I could see in the video.