i have tried to move my curve but it is not moving well, when it changes its direction from left to right or right to left then the movement is quite awkward. i want to move my curve like this video video of curve movement what i actually want. In this video when a it change its direction it is so graceful but in my case it change its direction and the curve gives a crazy shape at newly added point. Experts please solve this problem. here is my code
//create paths
private Bezier<Vector2> path1;
private CatmullRomSpline<Vector2> path2;
private ShapeRenderer sr;
int height,width;
Vector2 starting,ending,endingControl;
ArrayList<Vector2> listOfPoints;
Vector3 touchPos;
float timeDifference;
Boolean leftPos=false,rightPos=false;
Boolean isTouch=false,isTouchUp=false;
Vector2 mVector2;
private OrthographicCamera cam;
Vector2[] controlPoints;
@Override
public void create () {
width = Gdx.graphics.getWidth();
height = Gdx.graphics.getHeight();
ending=new Vector2(width/2,height/2);
endingControl=new Vector2(ending.x,ending.y+10);
starting=new Vector2(width/2,0);
controlPoints = new Vector2[]{starting,starting,ending,ending};
// set up the curves
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
listOfPoints=new ArrayList<Vector2>();
// setup ShapeRenderer
sr = new ShapeRenderer();
sr.setAutoShapeType(true);
sr.setColor(Color.BLACK);
cam=new OrthographicCamera();
cam.setToOrtho(false);
listOfPoints.add(new Vector2(width/2,0)); //starting
listOfPoints.add(new Vector2(width/2,0)); //starting
}
@Override
public void resize(int width, int height) {
// TODO Auto-generated method stub
super.resize(width, height);
cam.update();
}
@Override
public void render () {
cam.update();
Gdx.gl.glClearColor(1f, 1f, 1f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
sr.begin();
sr.set(ShapeType.Filled);
if(Gdx.input.isTouched())
{
if(!isTouch){
listOfPoints.add(new Vector2(ending.x+2, ending.y-4));
int s=listOfPoints.size();
controlPoints=new Vector2[s+2];
listOfPoints.toArray(controlPoints);
controlPoints[s]=ending;
//endingControl.x=ending.y;
controlPoints[s+1]=ending;
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
}
isTouch=true;
ending.x+=3;
}
else {
if(isTouch){
listOfPoints.add(new Vector2(ending.x-2, ending.y-4));
int s=listOfPoints.size();
controlPoints=new Vector2[s+2];
listOfPoints.toArray(controlPoints);
controlPoints[s]=ending;
controlPoints[s+1]=ending;
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
}
isTouch=false;
ending.x-=3;
}
moveAndReduce();
for(int i = 0; i < 100; ++i){
float t = i /100f;
Vector2 st = new Vector2();
Vector2 end = new Vector2();
path2.valueAt(st,t);
path2.valueAt(end, t-0.01f);
sr.rectLine(st.x, st.y, end.x, end.y,3);
}
sr.end();
}
@Override
public void dispose () {
sr.dispose();
}
public void moveAndReduce()
{
for(Vector2 vector2:listOfPoints)
{
vector2.y-=3 ;
}
if(listOfPoints.size()>3 && listOfPoints.get(3).y<-1)
{
listOfPoints.remove(0);
listOfPoints.set(0, listOfPoints.get(1));
int s=listOfPoints.size();
controlPoints=new Vector2[s+2];
listOfPoints.toArray(controlPoints);
controlPoints[s]=ending;
controlPoints[s+1]=ending;
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
}
}
Going by the video the curve does not look like it is constrained by control points but just a simple trace of and accelerating point.
You create an array of floats the length in pixels matching the length of the line in the x direction. For example if screen is 200 pixels wide the line can be 100 so the array is 100 in length. Set each float in the array to the start value half the screen height. I call the array line
in this answer. You call it what you like.
The you assign a head
index that is the index of the rightmost point. Each frame you move the head
index up by one. If it is over the array length-1
you set it to zero (beginning of array)
When you draw the line
you draw all the points from head + 1
Path p = new Path();
for(int i = 0; i < 100; ++i){
p.lineTo(i, line[(i + head + 1) % 100]); // add path points
}
// draw the path;
To make it move you have a movement float move
that is 0 for no movement or positive and negative values the move up or down.
When you want it to move increase the move
amount by a fixed value.
// moving down
if(move < maxMove){ // set a max move amount eg 10
move += moveAmount; // moveAmount 0.2 just as an example
}
Same for moving up, but subtract
When there is no input you move the move
amount back to zero by a fixed rate
// assume this is code run when no input
if(move != 0){
if(Math.abs(move) < moveAmount){ // if close to zero set to zero
move = 0;
}else{
move -= Math.sign(move) * moveAmount; // else move towards zero at
// fixed rate
}
}
The line does not move forward, just appears to do so as we move the head position up the array each frame.
Back to moving the line's head the following move the line head position up or down (but is not complete the last line is modified to create a smoother curve)
float pos = line[head]; // get the pos of line at head
head += 1; // move the head forward 1
head %= 100; // if past end of array move to 0
line[head] = pos + move; // set the new head position
This will move the head of the line up or down depending on move. The curve we get is not that nice so to make it a little smoother you need to change the rate the move value changes the head position.
// an sCurve for any value of move the result is from -1 to 1
// the greater or smaller move the closer to 1 or -1 the value gets
// the value -1.2 controls the rate at which the value moves to 1 or -1
// the closer to -1 the value is the slower the value moves to 1 or -1
float res = (2 / (1 + Math.pow(move,-1.2))) -1;
This in effect changes the shape of the lines curve to a almost sine wave when moving up and down
// so instead of
//line[head] = pos + move; // set the new head position
line[head] = pos + ( (2 / (1 + Math.pow(move,-1.2))) -1 ) * maxSpeed;
// max speed is the max speed the line head can move up or down
// per frame in pixels.
Below is a Javascript implementation that does it as outlined above (is not intended as answer code). Use the keyboard Arrow Up and Arrow down to move the line
If you are using a tablet or phone then the following image is what you will see as way to late for me to add and test touch for the example
const doFor = (count, callback) => {var i = 0; while (i < count) { callback(i ++) } };
const keys = {
ArrowUp : false,
ArrowDown : false,
};
function keyEvents(e){
if(keys[e.code] !== undefined){
keys[e.code] = event.type === "keydown";
e.preventDefault();
}
}
addEventListener("keyup", keyEvents);
addEventListener("keydown", keyEvents);
focus();
var gameOver = 0;
var gameOverWait = 100;
var score = 0;
var nextWallIn = 500
var nextWallCount = nextWallIn;
var wallHole = 50;
const wallWidth = 5;
const walls = [];
function addWall(){
var y = (Math.random() * (H - wallHole * 2)) + wallHole *0.5;
walls.push({
x : W,
top : y,
bottom : y + wallHole,
point : 1, // score point
});
}
function updateWalls(){
nextWallCount += 1;
if(nextWallCount >= nextWallIn){
addWall();
nextWallCount = 0;
nextWallIn -= 1;
wallHole -= 1;
}
for(var i = 0; i < walls.length; i ++){
var w = walls[i];
w.x -= 1;
if(w.x < -wallWidth){
walls.splice(i--,1);
}
if(w.x >= line.length- + wallWidth && w.x < line.length){
var pos = line[head];
if(pos < w.top || pos > w.bottom){
gameOver = gameOverWait;
}
}
if(w.point > 0 && w.x <= line.length){
score += w.point;
w.point = 0;
}
}
}
function drawWalls(){
for(var i = 0; i < walls.length; i ++){
var w = walls[i];
ctx.fillStyle = "red";
ctx.fillRect(w.x,0,wallWidth,w.top);
ctx.fillRect(w.x,w.bottom,wallWidth,H-w.bottom);
}
}
const sCurve = (x,p) => (2 / (1 + Math.pow(p,-x))) -1;
const ctx = canvas.getContext("2d");
var W,H; // canvas width and height
const line = [];
var move = 0;
var curvePower = 1.2;
var curveSpeed = 0.2;
var maxSpeed = 10;
var headMoveMultiply = 2;
var head;
function init(){
line.length = 0;
doFor(W / 2,i => line[i] = H / 2);
head = line.length - 1;
move = 0;
walls.length = 0;
score = 0;
nextWallIn = 500
nextWallCount = nextWallIn;
wallHole = 50;
ctx.font = "30px arial black";
}
function stepLine(){
var pos = line[head];
head += 1;
head %= line.length;
line[head] = pos + sCurve(move,curvePower)*headMoveMultiply ;
}
function drawLine(){
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.lineJoin = "round";
ctx.lineCap = "round";
for(var i = 0; i <line.length; i++){
ctx.lineTo(i,line[(i + head + 1) % line.length]);
}
ctx.stroke();
}
function mainLoop(time){
if(canvas.width !== innerWidth || canvas.height !== innerHeight){
W = canvas.width = innerWidth;
H = canvas.height = innerHeight;
init();
}
if(gameOver === 1){
gameOver = 0;
init();
}
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,W,H);
if(keys.ArrowUp){
if(move > - maxSpeed){
move -= curveSpeed;
}
}else if(keys.ArrowDown){
if(move < maxSpeed){
move += curveSpeed;
}
}else{
move -= Math.sign(move)*curveSpeed;
if(Math.abs(move) < curveSpeed){
move = 0;
}
}
if(gameOver === 0){
stepLine();
updateWalls();
}
drawLine();
drawWalls();
ctx.fillStyle = "Black";
ctx.textAlign = "left";
ctx.fillText("Score : " + score, 10,30);
if(gameOver > 0){
ctx.textAlign = "center";
ctx.fillText("Crashed !!", W / 2,H * 0.4);
gameOver -= 1;
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas {
position : absolute;
top : 0px;
left : 0px;
z-index : -10;
}
<br><br><br>Up down arrow keys to move line.
<canvas id=canvas></canvas>