Categories
Reactickles 3

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.