# Align the arrows with the route or how to follow a path (planes)

This question is specially for “quark”, but anyone can answer it.

I have seen a video on the quark youtube channel where some arrows move along some paths, I would like to know how you did that and if you could show me an example as simple as possible of how I could do it.

https://youtu.be/UbhWb3gDtK8

The code and explanation for this animation can be found in the this discussion

If you want to make a forum member aware of your post simply include their name preceded with an @ somewhere in the text e.g. @quark

2 Likes

The code is quite complicated, could you explain it to me in the simplest way you can or show me an easier example to follow where it only shows an object and a path to follow.

If I am going to explain the code then I need to know where you are starting from

1. Do you know what parametric curves are?
2. Can you determine the 2 parametric equations that define a circle?
3. Ignoring the code, did you understand the discussion I linked to?
1 Like

I don’t understand the first and second questions, the third I followed the discussion and there is a question about how to make planes follow a curved path with the plane’s nose pointing forward.

I suppose that the plane will have to rotate according to the direction it takes on the road, but I have no idea what you’re saying, I’ve only tried the Pvector class a bit but I don’t have much practice with it.

In short, I start from zero.

What I’ve always wanted to know how to do is give realistic movement to planes like the ones in this game, but I’ve never found anyone who knew how to explain how to do it.

https://youtu.be/9NcTNoGjFvE

We will start with a simple example of moving a plane (we will use a triangle) between two positions (v0 and v1) using the shortest route i.e. a straight line. The PVector class will be used to store the point’s coordinates. So we start with.

// Start and end of our path
PVector v0 = new PVector(35, 22);
PVector v1 = new PVector(245, 239);

void setup() {
size(300, 300);
}

void draw() {
background(220, 225, 255);
noStroke();
fill(50, 255, 50);
ellipse(v0.x, v0.y, 10, 10); // Green = start
fill(255, 50, 50);
ellipse(v1.x, v1.y, 10, 10); // Red = end
stroke(0);
line(v0.x, v0.y, v1.x, v1.y);
}

The next step is to add our aeroplane onto the path and have it facing the direction of travel.

As well as storing positions the PVector class can be used to store a direction. In this scenario the plane is traveling in the direction v1 - v0 so immediately after declaring the path end points we can add some more useful variables.

PVector dir = PVector.sub(v1, v0); //  direction of travel or facing direction
PVector pos = new PVector(); // position of plane
float t; // parametric parameter defining position on path

The variable t will be used to specify how far along the path the plane has moved so that
when t = 0 plane is at v0
when t = 1 plane is at v1

Lets create a method that draws the plane at a any given position and facing any given direction.

void drawPlane(PVector pos, PVector facing) {
push(); // save current drawing attributes
float angle = atan2(facing.y, facing.x); // path angle
translate(pos.x, pos.y);
rotate(angle);
stroke(0, 0, 200);
fill(255, 255, 0);
triangle(-5, -4, -5, 4, 8, 0);
pop(); // restore previous drawing attributes
}

The final part is to calculate the planes position along the path for any given value of t so we will add code in draw to do that and to change the value of t based on the horizontal position of the mouse.

The final code is

// Start and end of our path
PVector v0 = new PVector(35, 22);
PVector v1 = new PVector(245, 239);
PVector dir = PVector.sub(v1, v0); //  direction of travel / facing direction
PVector pos = new PVector(); // position of plane
float t; // parametric parameter defining position on path

void setup() {
size(300, 300);
}

void draw() {
background(220, 225, 255);
noStroke();
fill(50, 255, 50);
ellipse(v0.x, v0.y, 10, 10); // Green = start
fill(255, 50, 50);
ellipse(v1.x, v1.y, 10, 10); // Red = end
stroke(0);
line(v0.x, v0.y, v1.x, v1.y);
// Change t according to horizontal mouse position mouse position
t = map(mouseX, 10, width - 10, 0, 1);
t = constrain(t, 0, 1);
// Calculate planes position
pos.set(v0.x + t * dir.x, v0.y + t * dir.y);
drawPlane(pos, dir);
}

void drawPlane(PVector pos, PVector facing) {
push(); // save current drawing attributes
float angle = atan2(facing.y, facing.x); // path angle
translate(pos.x, pos.y);
rotate(angle);
stroke(0, 0, 200);
fill(255, 255, 0);
triangle(-5, -4, -5, 4, 8, 0);
pop(); // restore previous drawing attributes
}

Hope this helps

2 Likes

In this game (in the video) we have a background (the ground with the ships and buildings) that moves down.

The plane moves mainly left / right and is stationary in y direction (almost). Use cursor keys to move it left / down / right / up.

Totally different program from planes on a curve (like in your first post or what quark discusses). No curve involved.

Thanks for the help @quark , I’m going to try it and study until I understand it and maybe I’ll ask some more questions.

@Chrisir You should have watched the video for a longer time, there are many enemy ships with different types of movement but at minutes 1:23, 1:41 and 1:48 some red planes appear that follow a curved path.

But it’s true that the player’s ship destroys them very fast and it doesn’t look good how they move.

But if you want to see both that movement and others well, it is better to play that game with the mame emulator. Greetings

1 Like

ok, thanks for clearing this up.

Here is Sketch for the main plane (the player)

// plane
PVector plane = new PVector (300, 500);

float speed=13; // for steering

void setup() {
size(600, 600);
}//setup

void draw() {
background(150);

// help text
text("Use Cursor keys", 13, 13);

// plane
rect (plane.x, plane.y,
20, 50);

rect (plane.x-6, plane.y-3,
9, 7);

rect (plane.x+20, plane.y-3,
9, 7);
}

void keyPressed() {
if (key==CODED) {

switch(keyCode) {
case LEFT:
plane.x-=speed;
break;
case RIGHT:
plane.x+=speed;
break;
//--
case UP:
plane.y-=speed;
break;
case DOWN:
plane.y+=speed;
break;
}//switch
}//if
}//func
//

2 Likes

here a simple shoot Sketch

//

Ship ship = new Ship();
ArrayList<Beam> beams = new ArrayList();

void setup() {
size(777, 888);
background(0);
}

void draw() {
background(0);

showLevel() ;

ship.display();

// the laser beams
for (Beam currentBeam : beams) {
currentBeam.display();
}

for (int i=beams.size()-1; i>=0; i--) {
if (!beams.get(i).isAlive) {
beams.remove(i);
}
}
println(beams.size());
}

// -----------------------------------------------------------

void keyPressed() {
if (key==' ') {
}
}

// -----------------------------------------------------------

void showLevel() {
fill(255);
textSize(22);
text ("Hit space to fire! Hold space to fire.", 12, 25);
}

// ===============================================================

class Ship {

float shipPosX= 66,
shipPosY=166;

// no explizit constructor

void display() {

// display and move

noStroke();
fill(0, 255, 0); // green
ellipse (shipPosX,
shipPosY, 33, 33);

textSize(15);
shipPosY-33);

if (shipPosY>height-33)

if (shipPosY<33)

// ---
if (shipPosX>width) {
shipPosX=-22;
}

if (shipPosX<33)
}
//
}//class

// =========================================================================

class Beam {

float startX, startY;

int startPoint=0,
length1=33;
boolean fire = true;

boolean isAlive=true;

// for decoration
boolean showSideLinesOfLaser = true;

// const
final int speed=9;
final int theMaximalLengthOfTheBeam = 42;

// constr
Beam( float startX_, float startY_) {
startX=startX_;
startY=startY_;
} // constr

void display() {

// display and move

strokeWeight(2);
stroke(#29C9F0);

//line (width/2, height-33-startPoint,
//  width/2, height-length1);

laser (startX+startPoint, startY,
startX+length1, startY,
color(#29C9F0));

if (showSideLinesOfLaser) {

strokeWeight(1);
int a1=3;

laser (startX+startPoint, startY+a1,
startX+length1, startY+a1,
color(#29C9F0, 22));

laser (startX+startPoint, startY-a1,
startX+length1, startY-a1,
color(#29C9F0));
}

// move laser
if (fire) {
if (frameCount%1==0) {
length1+=speed;
}
if (length1>theMaximalLengthOfTheBeam)
startPoint+=speed;
}

// kill laser
if (length1>theMaximalLengthOfTheBeam) {
if (startX+startPoint>width&&      startX+length1>width) {
isAlive=false;
}
}
}//func

// help function
void laser(float x1, float y1,
float x2, float y2,
color c) {

// one line of the laser

stroke(red(c), green(c), blue(c), 75);//I'm sure there are better ways of doing this
strokeWeight(3);//again, there are better ways, but this is just an example

line(x1, y1, x2, y2);
strokeWeight(1);
stroke(c);
line(x1, y1, x2, y2);
}
}// class

//

2 Likes

@quark I liked the example, I would like to know how I can make the path be curved instead of a straight line and if it is possible to make two or three objects follow the first one.

@Chrisir Thanks for the example, it helps me to learn some things. But there is no object that follows a path, it only appears on the left and moves up or down to then bounce and disappear on the right.

Yeah, it’s just the main plane at the lower screen border, that you steer. nvm

2 Likes

The code for this is much more challenging as you can see in my youtube example but the principle is the same.

Given a path you need to be able to calculate 2 things

1. the objects position on the path for any given time
2. the path tangent for the position calculated in (1)

How we calculate these depend on the type of curve but we would need a formula to describe the shape of the path. For a straight line path this is easy since the path direction is also the tangent.

The approach I took in my youtube example is to define the 2D path with a pair of parametric equations.

The code used in my youtube example is the simplest implementation I could come up with for curve following that was extensible. I suggest you study it and play with the code. Some of the syntax might be new to you but then ask on this forum.

This is possible but I can think of three ways you might follow a leader

1. one behind the other each following the same path
2. at any given time the following objects have the same velocity and direction as the leader. Synchronised movement
3. the following objects are on ‘parallel’ paths to the leader travel at the same speed but may or may not be facing the same direction

Each method would be implemented in code differently.

2 Likes

Thanks for the help @quark , @Chrisir .

Wanted to show you something

Here you have a curve with a plane.

When you drag the 4 red points you can change the curve

You can place the anchors (aka start/stop) OUTSIDE the canvas and move the control points

like with this example:

[ 1417.0, 181.0, 0.0 ]
[ -108.0, -4.0, 0.0 ]
[ 1509.0, 531.0, 0.0 ]
[ 376.0, -107.0, 0.0 ]

or here

[ 272.0, -19.0, 0.0 ]
[ -126.0, 255.0, 0.0 ]
[ 1793.0, 861.0, 0.0 ]
[ -2.0, -31.0, 0.0 ]

Remark

This Sketch can be seen as an curve editor, that printlns curve data that you then use in your main program

Full Sketch

// https://discourse.processing.org/t/align-the-arrows-with-the-route-or-how-to-follow-a-path-planes/41188

int index=-1;
float i_Plane;

PVector[] pvListDef = {
new PVector(340, 80),
new PVector(40, 40),
new PVector(360, 360),
new PVector( 60, 320)
};

PVector[] pvList = {
new PVector(340, 80),
new PVector(40, 40),
new PVector(360, 360),
new PVector( 60, 320)
};

//-------------------------------------------------------------------------------------

void setup() {
size(1400, 1400);
}

void draw() {
background(111);
noFill();

// show curve
bezier(pvList[0].x, pvList[0].y,
pvList[1].x, pvList[1].y,
pvList[2].x, pvList[2].y,
pvList[3].x, pvList[3].y);

// show points on curve
fill(255);
int steps = 100;
for (int i = 0; i <= steps; i++) {
float t = i / float(steps);
float x = bezierPoint(pvList[0].x, pvList[1].x, pvList[2].x, pvList[3].x, t);
float y = bezierPoint(pvList[0].y, pvList[1].y, pvList[2].y, pvList[3].y, t);
ellipse(x, y, 10, 10);
}

// PLANE
PVector posPlane=new PVector(0, 0),
facingPlane=new PVector(0, 0);
float t2 = i_Plane / float(steps);
float x2 = bezierPoint(pvList[0].x, pvList[1].x, pvList[2].x, pvList[3].x, t2);
float y2 = bezierPoint(pvList[0].y, pvList[1].y, pvList[2].y, pvList[3].y, t2);
posPlane=new PVector(x2, y2);

float t3 = (i_Plane+1) / float(steps);
float x3 = bezierPoint(pvList[0].x, pvList[1].x, pvList[2].x, pvList[3].x, t3);
float y3 = bezierPoint(pvList[0].y, pvList[1].y, pvList[2].y, pvList[3].y, t3);

// Start and end of our path
PVector v0 = new PVector(x2, y2);
PVector v1 = new PVector(x3, y3);
facingPlane = PVector.sub(v1, v0); //  direction of travel / facing direction

fill(0, 255, 0);
drawPlane(posPlane, facingPlane);
i_Plane++;
if (i_Plane > steps)
i_Plane=0;

// show 4 RED points
int i=0;
for (PVector pv : pvList) {
fill(255, 0, 0);
ellipse(pv.x, pv.y, 16, 16);
if (i==0||i==3) {
fill(255);
text("anchor", pv.x, pv.y-10);
}
if (i==1||i==2) {
fill(255);
text("control", pv.x, pv.y-10);
}
i++;
}
fill(255);

// Allow drag
if (index>-1) {
fill(255);
text("Hit ESC to leave drag mode", 19, 19);
pvList[index].x=mouseX;
pvList[index].y=mouseY;
} else {
fill(255);
text("click RED points to drag ", 19, 19);
}
}//draw

//-------------------------------------------------------------------------------------
// Inputs

void keyPressed() {
index=-1;
key=0;
}

void mousePressed() {
// search point
int i=0;
index=-1;
for (PVector pv : pvList) {
if (dist(pv.x, pv.y, mouseX, mouseY)<19) {
index=i;
return;
}
i++;
}
}

void mouseReleased () {
// reset
index=-1;
// dump
for (PVector pv : pvList) {
println(pv);
}
}

//-------------------------------------------------------------------------------------
// Misc

void drawPlane(PVector pos, PVector facing) {
push(); // save current drawing attributes
float angle = atan2(facing.y, facing.x); // path angle
translate(pos.x, pos.y);
rotate(angle);
stroke(0, 0, 200);
//fill(255, 255, 0);
triangle(-5, -4, -5, 4, 8, 0);
pop(); // restore previous drawing attributes
}
//

2 Likes

@Chrisir Thank you very much, it is an impressive example, with all this information I have more than enough to get what I want. Greetings.

1 Like