# Simple example of 3d movement following a point?

with improved version now below!!!

Another approach, where the camera is on a circle around the player, with an easing when changing the angle of the camera on that circle.

Also, there is an easing on lookAt now also.

Chrisir

``````// new version with lookAtPVectorFollowCircle()

// 3D sketch: shows a ball that moves in the x/z plane (height = y = 500) and is followed by a camera.
// The goal of the sketch is to demonstrate the usage of a following camera.

// cubes
float angleCubes=45; // angle for the cubes in the scene

CameraClass cam;

PVector ballPos = new PVector (300, 500, 300);
PVector ballPosPrev =  new PVector (300, 500-10, 310);
PVector ballVel = new PVector (
random(1, 2),
0,
random(-2, -1));

final int heightAbovePlayer = 63;

float angle1 = 0; // angle
float angle2 = 0;

// when the ball is not running, we have a slight up and down movement of the camera
boolean ballIsStopped=false; // flag tells whether the ball is stopped or not
float angleHeight=0;   // angle for the slight up and down movement of the camera

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

void setup() {
size (1400, 800, P3D);

cam = new CameraClass ();

cam.camPos = ballPos.copy();
cam.camPos.x += 113;
cam.camPos.y -= heightAbovePlayer;
cam.camPos.z += 113;
} // func

void draw() {
// clear canvas
background(111);

// avoid clipping : https : // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
perspective(PI/3.0, (float) width/height, 1, 1000000);

// apply lights
lights();

// camera follows ball
if (! (keyPressed&&key==' ')) {
//cam.lookAtPVectorFollow(ballPos);
cam.lookAtPVectorFollowCircle(ballPos);
}

// apply the values of the class to the real camera
cam.set();

// move ball and draw it
ballPosPrev = ballPos.copy();
if (! (keyPressed&&key=='m')) {
}
if (ballPosPrev.dist(ballPos)==0) {
if (!ballIsStopped)
angleHeight=0;
ballIsStopped=true;
} else {
ballIsStopped=false;
}
angle1 = PI + atan2(ballPos.z - ballPosPrev.z, ballPos.x - ballPosPrev.x );

fill(0, 255, 0); // green
noStroke();
mySphere(ballPos.x, ballPos.y, ballPos.z, 10);

// contain ball in a invisible box
contain(ballPos, ballVel);

// make the scene with the boxes
scene();

// text upper left corner
fill(0, 255, 0);
cam.HUD_text("The green ball is contained in an "
+"red field."
+"\nHold space bar to stop camera."
+"\nHold 'm' to stop the Movement of the ball.");
// println(angle1+", "+angle2);
//
} // func

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

void scene () {

// Drawing of the red field

fill(255, 2, 2);//RED
noStroke();
mySphere(500, 500, 500, 20);
mySphere(100, 500, 100, 20);
mySphere(100, 500, 500, 20);
mySphere(500, 500, 100, 20);
//
// rect
stroke(255, 2, 2);//RED
line(500, 500, 500, 500, 500, 100);//up
line(500, 500, 100, 100, 500, 100); //left
line(100, 500, 100, 100, 500, 500 ); // down
line(100, 500, 500, 500, 500, 500 ); // down

stroke(0);
float z ;

// one wall of boxes
for (int x = 10; x < 600; x+= 100)
{
fill(x/3, 2, 2);
for (int y = 10; y < 600; y+= 100)
{
z = -600;
myBox(x, y, z, 24);
// println ( "Box: " + x + ", "+y+ " "+z);
}
fill(0, 0, 254);
z=-800;
myBox(x, 10, z, 24);
}
//
fill(0, 0, 254);
z=-400;
myBox(220, 10, z, 24);
myBox(600, 10, z, 24);
z=-400;
myBox(220, 510, z, 24);
myBox(600, 510, z, 24);
z=399;
myBox(220, 510, z, 24);
myBox(600, 510, z, 24);
z=900;
myBox(220, 510, z, 24);
myBox(600, 510, z, 24);
angleCubes++;
//
}

void myBox(float x, float y, float z,
float size1) {
// one nice wrapper for build in box-command
pushMatrix();
translate(x, y, z);
box(size1);
popMatrix();
}

void mySphere(float x, float y, float z,
float size1) {
// one nice wrapper for build in sphere-command
pushMatrix();
translate(x, y, z);
sphere(size1);
popMatrix();
}

void contain(PVector ballPos, PVector ballVel) {
// contain ball

if (ballPos.x>500) {
ballVel.x=abs(ballVel.x)*-1;
println("---");
}
if (ballPos.z>500) {
ballVel.z=abs(ballVel.z)*-1;
println("---");
}

if (ballPos.x<100) {
ballVel.x=abs(ballVel.x);
println("---");
}
if (ballPos.z<100) {
ballVel.z=abs(ballVel.z);
println("---");
}
}

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

class CameraClass {

// capsules the normal camera() command and its vectors

PVector camPos;     // its vectors
PVector camLookAt;
PVector camUp;

PVector camPosInitial;     // its vectors - the default (unchanged)
PVector camLookAtInitial;
PVector camUpInitial;

// for follow
PVector camWhereItShouldBe = new PVector(0, 0, 0);
PVector camAdd = new PVector(0, -60, 0);
float easing = .019; // .07; // how fast it changes

float camCurrentAngle=0;// -90;   // for cam rotation around itself (around Y-axis)

boolean firstTime=true;

// constructor without parameters
CameraClass() {
// constr
// set vectors
camPos    = new PVector(width/2.0, height/2.0, 990);
camLookAt = new PVector(width/2.0, height/2.0, -600);
camUp     = new PVector( 0, 1, 0 );
// save the initial values
camPosInitial    = camPos.get();
camLookAtInitial = camLookAt.get();
camUpInitial     = camUp.get();
}  // constr

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

void set() {
// apply vectors to actual camera
camera (camPos.x, camPos.y, camPos.z,
camLookAt.x, camLookAt.y, camLookAt.z,
camUp.x, camUp.y, camUp.z);
}

// ---

void setLookAt (float x1, float y1, float z1) {
camLookAt = new PVector(x1, y1, z1);
}

// ---

void lookAtPVectorFollowOnlyLookAt(PVector followMe) {
// Two versions of this. Without easing.
// Follows a player (e.g.),
// change only look at.
camLookAt = followMe.get();
}

void lookAtPVectorFollowOnlyLookAtWithEasing(PVector followMe) {
// Version  with easing.
// Follows a player (e.g.),
// change only look at.
float easing = 0.0398;
camLookAt.x += (followMe.x-camLookAt.x) * easing;   // followMe.get();
//camLookAt.y += (followMe.y-camLookAt.y) * easing;
camLookAt.y  =  followMe.y; // other principle
camLookAt.z += (followMe.z-camLookAt.z) * easing;
}

// ---

void lookAtPVectorFollowCircle(PVector followMe) {
// Two versions of this.
// One version of doing it.
// The camera moves on a circle around the player.

float RadiusCam=170.0;  // dist camera from the player (radius of the cam around the player)

if (firstTime) {
lookAtPVectorFollowOnlyLookAt(followMe);
firstTime=false;
} else {
lookAtPVectorFollowOnlyLookAtWithEasing(followMe);
}

float easing = 0.0198;
angle2 += (angle1-angle2) * easing;

// the camera is around the avatar/player
// in a distance RadiusCam from the avatar/player

// other principles for y, no circle calculation
if (ballIsStopped)
camPos.y  = (followMe.y - heightAbovePlayer) + (sin(angleHeight) * heightAbovePlayer/2); // slight movement of the cam up and down
else
camPos.y = followMe.y - heightAbovePlayer;  // other principle for y, no circle calculation

angleHeight+=.021;
} //

// second version of doing it
// camera is trying to get to player position, but with easing.

// follows a ball / player (e.g.),
// complete following (lookAt and camPos).

// manage Look At
camLookAt =  followMe.get();

// manage position of the camera
float easing = 0.0198;
// x
camPos.x    += (followMe.x-camPos.x) * easing;

// y
// camPos.y    += (followMe.y-camPos.y) * easing -20;
camPos.y    = followMe.y - heightAbovePlayer; // other principle for y !

// z
camPos.z    += (followMe.z-camPos.z) * easing;
}

// ---

void printData() {
println ( "Cam at " + camPos
+ " looking at " + camLookAt
+ " (angle = "
+camCurrentAngle
+").");
}

void HUD_text (String a1) {
// HUD text upper left corner
// this must be called at the very end of draw()

// this is a 2D HUD
camera();
hint(DISABLE_DEPTH_TEST);
noLights();
// ------------------
textSize(16);
text (a1, 20, 20);
// ------------------
// reset all parameters to defaults
textAlign(LEFT, BASELINE);
rectMode(CORNER);
textSize(32);
hint(ENABLE_DEPTH_TEST); // no HUD anymore
lights();
} // method
//
} // class
// ======================================
``````

improved version

``````
//
// new version with lookAtPVectorFollowCircle().

// 3D sketch: shows a ball that moves in the x/z plane (height = y = GENERAL_Y_HEIGHT = 500) and is followed by a camera.
// The goal of the sketch is to demonstrate the usage of a **following** camera.
// The camera moves on a circle (in x/z plane) around the ball/Player.
// The camera Y is located HEIGHT_ABOVE_PLAYER = 63 above the ball/Player.

// How is the angle of the cam on the circle around the player calculated?

// ball.atan2FromBall() is an important function here that returns the angle between the ball and its previous position;
// this angle makes a line from current ball pos to the previous ball pos. When you prolongue the line
// you come to the x,z pos of the camera (y pos of the ball is "above" than the ball).

// The angle is used to place the cam on a circle with a fixed radius around the player/ball. The angle is changed with an
// constrained easing to make the cam movement smooth.
// So the cam rotates around the ball and follows the ball.

// The cam lookAt is towards the ball but also with a 2nd easing.

// When the ball stops, the cam slightly moves up and down (during waiting).

// Search "debug" and "println" to find vars etc. to delete when debugging is over.
// https://discourse.processing.org/t/simple-example-of-3d-movement-following-a-point/12361/21

// CONSTANTS: the floor / field (y value)
final int GENERAL_Y_HEIGHT = 500;

// Classes: The camera
CameraClass cam;

// The ball
Ball ball = new Ball();

// The scene (background, field, boxes...)
Scene scene = new Scene();

// tools
Tools tools = new Tools();

// angle stuff
float angle2 = 0;   // actually used to calc cam pos
float angleToWorkWith; // that's used to calc the delta

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

void setup() {
size (1400, 800, P3D);

// setup camera
cam = new CameraClass ();
cam.camPos = ball.ballPos.copy();
cam.camPos.x += 113;
cam.camPos.y -= cam.HEIGHT_ABOVE_PLAYER;
cam.camPos.z += 113;
} // func

void draw() {
// set Canvas and Environment
scene.setCanvasAndEnvironment();

// camera follows ball (when Space Bar is not pressed)
cam.lookAtPVectorFollowCircle(ball.ballPos);   // old Version: cam.lookAtPVectorFollow(ballPos);
cam.set(); // apply the values of the class cam to the real camera

// move ball (when m is not pressed)
ball.move();      // move ball

// manage angles
manageAngles();

// ball display and constrain
ball.script();

// display the scene with the boxes and HUD text
scene.displayScene();
//
} // func draw

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

void manageAngles() {
// manage Angles

// calc the angle from the line between the ball pos and tha balls previous position
float angle1 = PI + ball.atan2FromBall();    // gets atan2FromBall + PI

angle1 = tools.fixAngle(angle1);

angleToWorkWith=angle1;

if ( (angle1-angle2) > PI ) {
angleToWorkWith=angle1-TWO_PI;
//println("Here 13 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++###");
}//if

if ( (angle2-angle1) > PI ) {
angleToWorkWith=angle1+TWO_PI;
//println("Here 14 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++###");
}//if

// debug
//println(degrees(angle1)
//  +", "
//  +degrees(angle2));
//
}//func

// =================================================================
// classes

class Scene {

// cubes
float angleCubes=45; // angle for the cubes in the scene

void setCanvasAndEnvironment() {
// set Canvas and Environment

// clear canvas
background(111);

// avoid clipping : https : // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
perspective(PI/3.0, (float) width/height, 1, 1000000);

// apply lights
lights();
}

void displayScene() {

// Drawing of the scene / decoration

// Drawing of the red field  ----
fill(255, 2, 2);//RED
noStroke();
tools.mySphere(500, GENERAL_Y_HEIGHT, 500, 20);
tools.mySphere(100, GENERAL_Y_HEIGHT, 100, 20);
tools.mySphere(100, GENERAL_Y_HEIGHT, 500, 20);
tools.mySphere(500, GENERAL_Y_HEIGHT, 100, 20);

// rect of lines
stroke(255, 2, 2);//RED
// using 3D lines:
line(500, GENERAL_Y_HEIGHT, 500, 500, GENERAL_Y_HEIGHT, 100);  // up |
line(500, GENERAL_Y_HEIGHT, 100, 100, GENERAL_Y_HEIGHT, 100);  // left <-
line(100, GENERAL_Y_HEIGHT, 100, 100, GENERAL_Y_HEIGHT, 500 ); // down |
line(100, GENERAL_Y_HEIGHT, 500, 500, GENERAL_Y_HEIGHT, 500 ); // right ->

// one small wall of boxes
stroke(0);
int z = -60;
for (int x = 10; x < 600; x+= 100) {
fill(x/3, 2, 2);
for (int y = 410; y < 600; y+= 100) {
tools.myBox(x, y, z, 24, angleCubes);
}
}

// a few additional blue boxes
fill(0, 0, 255);
z=-400;
tools.myBox(220, 10, z, 24, angleCubes);
tools.myBox(600, 10, z, 24, angleCubes);
z=-400;
tools.myBox(220, 510, z, 24, angleCubes);
tools.myBox(600, 510, z, 24, angleCubes);
z=399;
tools.myBox(220, 510, z, 24, angleCubes);
tools.myBox(600, 510, z, 24, angleCubes);
z=900;
tools.myBox(220, 510, z, 24, angleCubes);
tools.myBox(600, 510, z, 24, angleCubes);
angleCubes++;
//

// text upper left corner (HUD)
fill(0, 255, 0);
cam.HUD_text("The green ball is contained in a red field."
+"\nHold Space Bar to stop the camera."
+"\nHold 'm' to stop the ball.\n" );  // Hold Space Bar and m has debug purpose
}
//
} // class

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

class CameraClass {

// capsules the normal camera() command and its vectors

PVector camPos;     // its vectors
PVector camLookAt;
PVector camUp;

PVector camPosInitial;     // its vectors - the default (unchanged)
PVector camLookAtInitial;
PVector camUpInitial;

// for follow
PVector camWhereItShouldBe = new PVector(0, 0, 0);
PVector camAdd = new PVector(0, -60, 0);
float easing = .019; // .07; // how fast it changes

float camCurrentAngle=0;// -90;   // for cam rotation around itself (around Y-axis)

// how high is the cam above the player / ball
final int HEIGHT_ABOVE_PLAYER = 63;

boolean firstTime=true;

float angleForCamerasUpAndDownMovement=0;   // angle for the slight up and down movement of the camera

// constructor without parameters
CameraClass() {
// constr
// set vectors
camPos    = new PVector(width/2.0, height/2.0, 990);
camLookAt = new PVector(width/2.0, height/2.0, -600);
camUp     = new PVector( 0, 1, 0 );
// save the initial values
camPosInitial    = camPos.copy();
camLookAtInitial = camLookAt.copy();
camUpInitial     = camUp.copy();
}  // constr

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

void set() {
// apply internal class vectors to actual camera
camera (camPos.x, camPos.y, camPos.z,
camLookAt.x, camLookAt.y, camLookAt.z,
camUp.x, camUp.y, camUp.z);
}

// ---

void setLookAt (float x1, float y1, float z1) {
camLookAt = new PVector(x1, y1, z1);
}

// ---

void lookAtPVectorFollowOnlyLookAt(PVector followMe) {
// Two versions of this. Without easing.
// Follows a player (e.g.),
// change only look at.
camLookAt = followMe.copy();
}

void lookAtPVectorFollowOnlyLookAtWithEasing(PVector followMe) {
// Version  with easing.
// Follows a player (e.g.),
// change only look at.
float easing = 0.0398;
camLookAt.x += (followMe.x-camLookAt.x) * easing;   // followMe.copy();
//camLookAt.y += (followMe.y-camLookAt.y) * easing;
camLookAt.y  =  followMe.y; // other principle
camLookAt.z += (followMe.z-camLookAt.z) * easing;
}

// ---

void lookAtPVectorFollowCircle(PVector followMe) {
// Two versions of this.
// One version of doing it:
// The camera moves on a circle around the player (fixed radius = RadiusCam).

// space key leaves the function (camera stands still)
if ((keyPressed&&key==' ')) {    // debug purpose
return;
}//if ---------

float RadiusCam=170.0;  // dist camera from the player (radius of the cam around the player)

if (firstTime) {
lookAtPVectorFollowOnlyLookAt(followMe);
firstTime=false;
} else {
lookAtPVectorFollowOnlyLookAtWithEasing(followMe);
}

// calc delta with easing (the classic easing formula is stretched over a few lines here)
float easing = 0.0198;
float delta = (angleToWorkWith-angle2) * easing;

// constrain delta:
delta=constrain(delta, -0.04, 0.04);

angle2 += delta;

angle2 = tools.fixAngle(angle2);

// the camera is around the avatar/player
// in a distance RadiusCam from the avatar/player

// other principles for y, no circle calculation
if (ball.ballIsStopped)
camPos.y  = (followMe.y - HEIGHT_ABOVE_PLAYER) + (sin(angleForCamerasUpAndDownMovement) * HEIGHT_ABOVE_PLAYER/2); // slight movement of the cam up and down
else
camPos.y = followMe.y - HEIGHT_ABOVE_PLAYER;  // other principle for y, no circle calculation

angleForCamerasUpAndDownMovement+=.021;
} //

// second version of doing it
// camera is trying to get to player position, but with easing.

// follows a ball / player (e.g.),
// complete following (lookAt and camPos).

// manage Look At
camLookAt =  followMe.copy();

// manage position of the camera
float easing = 0.0198;
// x
camPos.x    += (followMe.x-camPos.x) * easing;

// y
// camPos.y    += (followMe.y-camPos.y) * easing -20;
camPos.y    = followMe.y - HEIGHT_ABOVE_PLAYER; // other principle for y !

// z
camPos.z    += (followMe.z-camPos.z) * easing;
}

// ---

void printData() {
println ( "Cam at " + camPos
+ " looking at " + camLookAt
+ " (angle = "
+camCurrentAngle
+").");
}

void HUD_text (String a1) {
// HUD text upper left corner
// this must be called at the very end of draw()

// this is a 2D HUD
camera();
hint(DISABLE_DEPTH_TEST);
noLights();
// ------------------
textSize(16);
text (a1, 20, 20);
// ------------------
// reset all parameters to defaults
textAlign(LEFT, BASELINE);
rectMode(CORNER);
textSize(32);
hint(ENABLE_DEPTH_TEST); // no HUD anymore
lights();
} // method
//
} // class

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

class Ball {

// position and previous position
PVector ballPos = new PVector (300, GENERAL_Y_HEIGHT, 300);
PVector ballPosPrev =  new PVector (300, GENERAL_Y_HEIGHT-10, 310);

// ball velocity - see constructor
PVector ballVel;

// when the ball is not running, we have a slight up and down movement of the camera
boolean ballIsStopped=false; // flag tells whether the ball is stopped or not
boolean keyMhasBallStopped=false; // key M

Ball() {
// constructor  ----

// ball vel
ballVel = new PVector (
random(1, 2),
0,
random(1, 2));

if (random(100) > 50)
ballVel.x*=-1;
if (random(100) > 50)
ballVel.z*=-1;
} // constructor  -----

void move() {
ballPosPrev = ballPos.copy(); // store previous ball pos

// key 'm' leaves the function (ball stands still)
if (keyPressed&&key=='m') {  // debug
keyMhasBallStopped=true;
return;
}//if

keyMhasBallStopped=false;
}

void script() {
// display ball
fill(0, 255, 0); // green
noStroke();
tools.mySphere(ballPos.x, ballPos.y, ballPos.z, 10);

// contain ball in a field
contain(ballPos, ballVel);
}

// Tools within the class ----

boolean isMoving() {
// Returns whether the ball is moving.
// It's moving when the prev pos is different from the current pos.
return
ballPosPrev.dist(ballPos) != 0.0;
}

void checkStop() {
if (ball.keyMhasBallStopped && !ball.isMoving()) {
//  ball is stopped

// the first time we detect that the ball has stopped (only the first time)
if (!ball.ballIsStopped)
cam.angleForCamerasUpAndDownMovement=0;     // we reset the angle height

ball.ballIsStopped=true; // set flag
} else {
ball.ballIsStopped=false;
}
}

float atan2FromBall() {
// ball.atan2FromBall() is an important function that returns the angle between the ball and its previous position;
// this angle makes a line from current ball pos to prev ball pos. When you prolongue the line
// you come to the x,z pos of the camera (y pos of the ball is "higher" than the ball).
// The angle is used to place the cam on a circle with a fixed radius around the player/ball. The angle is changed with an
// constrained easing to make the cam movement smooth. So the cam rotates around the ball and follows the ball.

return
atan2(ballPos.z - ballPosPrev.z, ballPos.x - ballPosPrev.x );
}

void contain(PVector ballPos, PVector ballVel) {
// contain ball / bouncing on field borders

if (ballPos.x>500) {
ballVel.x=abs(ballVel.x)*-1;
println("---"); // 4x println for debug
}
if (ballPos.z>500) {
ballVel.z=abs(ballVel.z)*-1;
println("---");
}

if (ballPos.x<100) {
ballVel.x=abs(ballVel.x);
println("---");
}
if (ballPos.z<100) {
ballVel.z=abs(ballVel.z);
println("---");
}
}//method
//
}//class

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

class Tools {
// tools

float fixAngle(float angle1) {
// if > 2xPI
if (angle1>TWO_PI)
angle1-=TWO_PI;
if (angle1>TWO_PI)
angle1-=TWO_PI;

// if < 0 (negative angles like -40 should be expressed as positive angles like 320)
if (angle1<0.0) {
angle1 = TWO_PI + angle1;
// println("Here 1 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++###");
}
if (angle1<0.0)
angle1 = TWO_PI + angle1;

return angle1;
}

void myBox(float x, float y, float z,
float size1,
float angleCube) {
// one nice wrapper for build in box-command
pushMatrix();
translate(x, y, z);
box(size1);
popMatrix();
}

void mySphere(float x, float y, float z,
float size1) {
// one nice wrapper for build in sphere-command
pushMatrix();
translate(x, y, z);
sphere(size1);
popMatrix();
}
}//class
//
``````
2 Likes