Hi everybody,
I am trying to make an object follow a curved path (representing a breathing ratio). The object is centred on the x-axis, however, should dynamically adjust its y-position to match/stay on the path, which is moving infinitely from right to left. When moving up/down on the path (inhalation/exhalation phase) the object changes its color and rotation to indicate the current phase. (see images)
What I’ve already tried so far is to use an oscillating sine wave to fake the rolling movement of the graph.
This approach had the advantage that I could easily get the exact y-values of the graph and set the objects y-position to match. Problem here is that since I also want to include a moving background image behind the graph it is hard to make the wave oscillate at the exact same speed as the moving background, so that it looks like they’re both just moving together from right to left. Additionally, I wasn’t able to figure out how to create more complex wave shapes where e.g. the descending part of the curve is twice the length of the ascending. As I want the wave to represent multiple different breathing ratios at the end, this is a must have.
Therefor, I switched to my current approach (see code below). Now, I am creating the graph statically (inside a PImage) using BezierVertex. This has the advantage that both the background and the image of the graph can be moved together. To achieve the y-movement of the object I iterate through the points the graph is being drawn through using bezierPoint.
The issue I’m having here is that there sometimes (you might need to let the sketch run for a little while to see it) is a glitch happening whenever a phase switch at one of the peaks of the graph takes place. I think it has to do with a rounding issue of processing but I’m not entirely sure. Another issue is that whenever I change the moving speed of background and the image of the graph I need to tweak the iteration speed of bezierPoint as well to match (as close as possible) the new speed again.
So, to sum up, I think the solution is far from ideal and probably overly complicated
How would you best approach this? I don’t mind starting from 0 again if somebody points me in the direction of a better solution… However, any tips on why the glitch is happening and how I could fix it are also very welcome
I’ve separated my code into three tabs, you need all of the below for it to work. I tried to simplify it as much as possible. If anything is unclear or missing please let me know.
Main Sketch:
// Declare breathing-ratio images
PImage breathingRatio_1_1;
PImage breathingRatio_1_2;
int imgx1 = 0, imgx2 = 1920, imgx3 = 1920*2; // images positions
int img2x1 = 960, img2x2 = 2880+960, img2x3 = 2880*2+960; // images positions
// Player
float playerPosX, playerPosY;
PVector speed, pos;
float rotationAngle;
color lineColor, playerColor;
color currentPhaseColor;
color guidingPathColor = #BF8D54;
int guidingPathPosX;
float angle = 0;
float bgVelocity = 5; // Bg scroll speed
void setup(){
fullScreen();
//size(1920,1080);
// Create breathing-ratio images
breathingRatio_1_1 = createImageBR_1_1();
breathingRatio_1_2 = createImageBR_1_2();
// Create array for 'followPath'
brList = new ArrayList();
}
void draw(){
background(#F2E1CE);
drawBr1_1();
//drawBr1_2();
drawPositionIndiators();
createBr1_1();
//createBr1_2();
//showBezier();
displayPlayer1_1();
//displayPlayer1_2();
}
void drawPositionIndiators() {
// Draw Line
noStroke();
fill(#000000, 40);
rect(0,0, width/2,height);
noFill();
stroke(lineColor);
strokeWeight(4);
line(width/2,height,width/2,-height);
}
Code for creation of breathing ratio images:
PImage createImageBR_1_1() {
float amplitude = 150;
float xspacing = width/8;
float bezierAmt = 150;
color pathColor = #BF8D54;
final PGraphics pg = createGraphics(width, height, JAVA2D);
pg.beginDraw();
pg.smooth();
pg.background(0, 0);
pg.noFill();
pg.stroke(pathColor);
pg.strokeWeight(100);
pg.pushMatrix();
pg.translate(0,height/2);
pg.beginShape();
pg.vertex(0,amplitude); // first point
pg.bezierVertex(0+bezierAmt,amplitude, xspacing*2-bezierAmt,-amplitude, xspacing*2,-amplitude);
pg.bezierVertex(xspacing*2+bezierAmt,-amplitude, xspacing*4-bezierAmt,amplitude, xspacing*4,amplitude);
pg.bezierVertex(xspacing*4+bezierAmt,amplitude, xspacing*6-bezierAmt,-amplitude, xspacing*6,-amplitude);
pg.endShape();
pg.popMatrix();
pg.pushMatrix();
pg.translate(width/2,height/2);
pg.beginShape();
pg.vertex(0,amplitude); // first point
pg.bezierVertex(0+bezierAmt,amplitude, xspacing*2-bezierAmt,-amplitude, xspacing*2,-amplitude);
pg.bezierVertex(xspacing*2+bezierAmt,-amplitude, xspacing*4-bezierAmt,amplitude, xspacing*4,amplitude);
pg.bezierVertex(xspacing*4+bezierAmt,amplitude, xspacing*6-bezierAmt,-amplitude, xspacing*6,-amplitude);
pg.endShape();
pg.popMatrix();
// Anchor Points
/*pg.fill(255);
pg.noStroke();
pg.ellipse(-width/8, height/2+150, 16, 16);
pg.ellipse(width/8, height/2-150, 16, 16);
pg.ellipse(width/8*3, height/2+150, 16, 16);
pg.ellipse(width/8*5,height/2-150, 16, 16);
pg.pushMatrix();
pg.translate(width/2,0);
pg.ellipse(width/8,height/2-150, 16, 16);
pg.ellipse(width/8*3,height/2+150, 16, 16);
pg.ellipse(width/8*5,height/2-150, 16, 16);
pg.ellipse(width/8*5,height/2+150, 16, 16);
pg.popMatrix();*/
pg.endDraw();
return pg.get();
}
PImage createImageBR_1_2() {
float amplitude = 150;
float xspacing = width/8;
float bezierAmt = 150;
color pathColor = #BF8D54;
final PGraphics pg = createGraphics(width+width/2, height, JAVA2D);
pg.beginDraw();
pg.smooth();
pg.background(0, 0);
pg.noFill();
pg.stroke(pathColor);
pg.strokeWeight(100);
pg.pushMatrix();
pg.translate(0,height/2);
pg.beginShape();
pg.vertex(0,amplitude); // first point
pg.bezierVertex(0+bezierAmt,amplitude, xspacing*2-bezierAmt,-amplitude, xspacing*2,-amplitude);
pg.bezierVertex(xspacing*2+bezierAmt,-amplitude, xspacing*6-bezierAmt,amplitude, xspacing*6,amplitude);
pg.bezierVertex(xspacing*6+bezierAmt,amplitude, xspacing*8-bezierAmt,-amplitude, xspacing*8,-amplitude);
pg.endShape();
pg.popMatrix();
pg.pushMatrix();
pg.translate(width/8*6,height/2);
pg.beginShape();
pg.vertex(0,amplitude); // first point
pg.bezierVertex(0+bezierAmt,amplitude, xspacing*2-bezierAmt,-amplitude, xspacing*2,-amplitude);
pg.bezierVertex(xspacing*2+bezierAmt,-amplitude, xspacing*6-bezierAmt,amplitude, xspacing*6,amplitude);
pg.bezierVertex(xspacing*6+bezierAmt,amplitude, xspacing*8-bezierAmt,-amplitude, xspacing*8,-amplitude);
pg.endShape();
pg.popMatrix();
pg.endDraw();
return pg.get();
}
void drawBr1_1(){
// Draw breathing-ratio images
image(breathingRatio_1_1, imgx1, 0);
image(breathingRatio_1_1, imgx2, 0);
image(breathingRatio_1_1, imgx3, 0);
// Move breathing-ratio images position
imgx1 -= bgVelocity; imgx2 -= bgVelocity; imgx3 -= bgVelocity;
// Reset image position for infinite scroll
if(imgx1 <= -width){imgx1 = width*2;}
if(imgx2 <= -width){imgx2 = width*2;}
if(imgx3 <= -width){imgx3 = width*2;}
}
void drawBr1_2(){
// Draw breathing-ratio images
image(breathingRatio_1_2, img2x1, 0);
image(breathingRatio_1_2, img2x2, 0);
image(breathingRatio_1_2, img2x3, 0);
// Move breathing-ratio images position
img2x1 -= bgVelocity; img2x2 -= bgVelocity; img2x3 -= bgVelocity;
if(img2x1 <= -width-width/2){img2x1 = (width+width/2)*2;}
if(img2x2 <= -width-width/2){img2x2 = (width+width/2)*2;}
if(img2x3 <= -width-width/2){img2x3 = (width+width/2)*2;}
}
Code for object to follow y-postions of path:
ArrayList<brPoints> brList = new ArrayList();
float invert;
float y, y2;
float t;
void createBr1_1() {
float amplitude = 150;
float xspacing = width/8;
float bezierAmt = 150;
// Point 1
brPoints point1 = new brPoints(0,amplitude);
brList.add(point1);
// Point 2
brPoints point2_cp1 = new brPoints(0+bezierAmt,amplitude);
brPoints point2_cp2 = new brPoints(xspacing*2-bezierAmt,-amplitude);
brPoints point2 = new brPoints(xspacing*2,-amplitude);
brList.add(point2_cp1);
brList.add(point2_cp2);
brList.add(point2);
// Point 3
brPoints point3_cp1 = new brPoints(xspacing*2+bezierAmt,-amplitude);
brPoints point3_cp2 = new brPoints(xspacing*4-bezierAmt,amplitude);
brPoints point3 = new brPoints(xspacing*4,amplitude);
brList.add(point3_cp1);
brList.add(point3_cp2);
brList.add(point3);
// Point 4
brPoints point4_cp1 = new brPoints(xspacing*4+bezierAmt,amplitude);
brPoints point4_cp2 = new brPoints(xspacing*6-bezierAmt,-amplitude);
brPoints point4 = new brPoints(xspacing*6,-amplitude);
brList.add(point4_cp1);
brList.add(point4_cp2);
brList.add(point4);
}
void createBr1_2() {
float amplitude = 150;
float xspacing = width/8;
float bezierAmt = 150;
// Point 1
brPoints point1 = new brPoints(0,amplitude);
brList.add(point1);
// Point 2
brPoints point2_cp1 = new brPoints(0+bezierAmt,amplitude);
brPoints point2_cp2 = new brPoints(xspacing*2-bezierAmt,-amplitude);
brPoints point2 = new brPoints(xspacing*2,-amplitude);
brList.add(point2_cp1);
brList.add(point2_cp2);
brList.add(point2);
// Point 3
brPoints point3_cp1 = new brPoints(xspacing*2+bezierAmt,-amplitude);
brPoints point3_cp2 = new brPoints(xspacing*6-bezierAmt,amplitude);
brPoints point3 = new brPoints(xspacing*6,amplitude);
brList.add(point3_cp1);
brList.add(point3_cp2);
brList.add(point3);
// Point 4
brPoints point4_cp1 = new brPoints(xspacing*6+bezierAmt,amplitude);
brPoints point4_cp2 = new brPoints(xspacing*8-bezierAmt,-amplitude);
brPoints point4 = new brPoints(xspacing*8,-amplitude);
brList.add(point4_cp1);
brList.add(point4_cp2);
brList.add(point4);
}
void displayPlayer1_1() {
//Get points of graph
int i=0;
brPoints point1=(brPoints)brList.get(i);
brPoints point2_cp1=(brPoints)brList.get(i+1);
brPoints point2_cp2=(brPoints)brList.get(i+2);
brPoints point2=(brPoints)brList.get(i+3);
brPoints point3_cp1=(brPoints)brList.get(i+4);
brPoints point3_cp2=(brPoints)brList.get(i+5);
brPoints point3=(brPoints)brList.get(i+6);
//Iterate through y-values of points on the graph
y = bezierPoint(point1.y, point2_cp1.y, point2_cp2.y, point2.y, t/9.5);
y2 = bezierPoint(point2.y, point3_cp1.y, point3_cp2.y, point3.y, t/9.5);
//Reverse iteration once highest/lowest y-value is reached
if(t >= 9.5){
invert = -1;
y = y2;
rotationAngle = PI/2;
playerColor = #ff0000;
lineColor = #ff0000;
}
if (t <= 0) {
invert = 1;
y = bezierPoint(point1.y, point2_cp1.y, point2_cp2.y, point2.y, t/9.5);//Set y back to y
rotationAngle = -PI/2;
playerColor = #00ff00;
lineColor = #00ff00;
}
//Iteration speed
t += 0.1 * invert;
//Draw player
pushMatrix();
translate(width/2, height/2 + y);
rotate(rotationAngle);
fill(#000000, 40);
noStroke();
triangle(+28,0, -24,-26, -24,+26);
fill(playerColor);
triangle(+20,0, -20,-20, -20,+20);
popMatrix();
}
void displayPlayer1_2() {
int i=0;
brPoints point1=(brPoints)brList.get(i);
brPoints point2_cp1=(brPoints)brList.get(i+1);
brPoints point2_cp2=(brPoints)brList.get(i+2);
brPoints point2=(brPoints)brList.get(i+3);
brPoints point3_cp1=(brPoints)brList.get(i+4);
brPoints point3_cp2=(brPoints)brList.get(i+5);
brPoints point3=(brPoints)brList.get(i+6);
y = bezierPoint(point1.y, point2_cp1.y, point2_cp2.y, point2.y, t/9.5);
y2 = bezierPoint(point2.y, point3_cp1.y, point3_cp2.y, point3.y, t/9.5);
if(t >= 9.5){
invert = -0.5;
y = bezierPoint(point2.y, point3_cp1.y, point3_cp2.y, point3.y, t/9.5);
rotationAngle = PI/2;
playerColor = #ff0000;
lineColor = #ff0000;
}
if (t <= 0) {
invert = 1;
y = bezierPoint(point1.y, point2_cp1.y, point2_cp2.y, point2.y, t/9.5);
rotationAngle = -PI/2;
playerColor = #00ff00;
lineColor = #00ff00;
}
}
void showBezier() {
int i=0;
brPoints point1=(brPoints)brList.get(i);
brPoints point2_cp1=(brPoints)brList.get(i+1);
brPoints point2_cp2=(brPoints)brList.get(i+2);
brPoints point2=(brPoints)brList.get(i+3);
brPoints point3_cp1=(brPoints)brList.get(i+4);
brPoints point3_cp2=(brPoints)brList.get(i+5);
brPoints point3=(brPoints)brList.get(i+6);
brPoints point4_cp1=(brPoints)brList.get(i+7);
brPoints point4_cp2=(brPoints)brList.get(i+8);
brPoints point4=(brPoints)brList.get(i+9);
stroke(255);
noFill();
pushMatrix();
beginShape();
translate(width/2,height/2);
vertex(point1.x, point1.y);
bezierVertex(point2_cp1.x, point2_cp1.y, point2_cp2.x, point2_cp2.y, point2.x, point2.y);
bezierVertex(point3_cp1.x, point3_cp1.y, point3_cp2.x, point3_cp2.y, point3.x, point3.y);
bezierVertex(point4_cp1.x, point4_cp1.y, point4_cp2.x, point4_cp2.y, point4.x, point4.y);
endShape();
popMatrix();
}
class brPoints {
float x;
float y;
brPoints(float _x, float _y ) {
x = _x;
y = _y;
}
void display() {
pushMatrix();
translate(0,height/2);
stroke(255);
strokeWeight(2);
point(x, y);
point(x+1, y+1);
popMatrix();
}
}