3
\$\begingroup\$

I am working on a custom game engine and have been fleshing out the various submodules (meshes, physics, shading, etc.)

Here is a sandbox program that exercises the newly finished camera system. After much testing, the camera system will be integrated into the rest of the engine.

This program loads three wireframe worlds and places the camera in them where the user may take control and move freely, viewing from the camera's perspective.

The code relies on other parts of the engine which I am not releasing yet. Therefore this code, while it does compile on my machine, is technically incomplete.

Camera3D.h

#ifndef Camera3D_H
#define Camera3D_H


#include <Evector.h>
#include <Vmath.h>
#include <SpatialObject.h>


class Camera3D: public gen::SpatialObject {
private:
    // A point in 3D space where the camera is currently focused on
    gen::Vector look;

    // A vector pointing in the "above" direction from the camera's
    // perspective
    gen::Vector up;

    inline void setLook(gen::Vector aim)
    {
        look = pos + aim;
    }

public:
    double maxSpeed;
    double lookMax;

    // Set the camera in a new position.
    //
    // Input: Vector pointing to the new position
    //
    void set(gen::Vector newPosition);

    // Translate the camera by some amount.
    //
    // Input: Triplet forming the displacement to translate by
    //
    void translate(double dx, double dy, double dz);

    // Move the camera along the "forward" direction by some amount,
    // either positively or negatively.
    //
    // Input: One-dimensional displacement to move along the "forward"
    // direction by
    //
    void walk(double velocity);

    // Set the azimuthal angle.
    //
    void setAzi(double newAzimuth);

    // Set the zenith angle.
    //
    void setZen(double newZenith);

    // Rotate about the Y axis. On the X-Z plane, positive rotations
    // are counterclockwise.
    //
    // Input: The angular displacement to change the azimuth by
    //
    // @todo This method seems to be slow. Optimize later.
    //
    void turn(double deltaAngle);

    //
    //
    void aim(double x, double y, double z);

    // Rotate about the "side" axis. The "side" axis points "east".
    //
    void pivot(double deltaAngle);

    // Roll the camera by some amount.
    //
    void roll(double deltaAngle);

    // Set the camera to a new elevation.
    //
    void hang(double newElev);

    // Raise the camera in elevation by some amount.
    //
    void raise(double deltaElev);


    inline gen::Vector getPosition() const {return pos;}

    inline gen::Vector getLook() const {return look;}

    inline gen::Vector getUp() const {return up;}

    inline gen::Vector getAim() const
    {
        return gen::displacement(getPosition(), getLook());
    }


    Camera3D(const double lookScale):
    gen::SpatialObject(),
    lookMax(lookScale)
    {
        //
        // Forward  = +Z
        // Up       = +Y
        //
        look = gen::Vector(0, 0, lookMax);
        up = gen::Vector(0, 1, 0);

        maxSpeed = 0;
    }
};


#endif

Camera3D.cpp

#include <Camera3D.h>
#include <Evector.h>
#include <Vmath.h>
#include <Globals.h>


extern const double PI;


// Set the camera in a new position.
//
// Input: Vector pointing to the new position
//
void Camera3D::set(gen::Vector newPosition)
{
    // We're not changing the direction the camera is looking,
    // just the position. Restore the aim after the move.
    gen::Vector oldAim = getAim();
    pos = newPosition;
    setLook(oldAim);
}


// Translate the camera by some amount.
//
// Input: Triplet forming the displacement to translate by
//
void Camera3D::translate(double dx, double dy, double dz)
{
    set(pos + gen::Vector(dx, dy, dz));
}


// Move the camera along the "forward" direction by some amount,
// either positively or negatively.
//
// Input: One-dimensional displacement to move along the "forward"
// direction by
//
void Camera3D::walk(double velocity)
{
    // Here, we're just stepping some distance either forward or back.
    gen::Vector step = getAim();
    gen::resize(step, velocity);
    set(pos + step);
}


// Set the azimuthal angle.
//
void Camera3D::setAzi(double newAzimuth)
{

}


// Set the zenith angle.
//
void Camera3D::setZen(double newZenith)
{

}


// Rotate about the Y axis. On the X-Z plane, positive rotations
// are counterclockwise.
//
// Input: The angular displacement to change the azimuth by
//
// @todo This method seems to be slow. Optimize later.
//
void Camera3D::turn(double deltaAngle)
{
    // Camera position does not change when turning, only the "forward"
    // vector.
    gen::Vector newAim = getAim();
    gen::rotate(newAim, deltaAngle, 2);
    setLook(newAim);
}


//
//
void Camera3D::aim(double x, double y, double z)
{

}


// Rotate about the "side" axis. The "side" axis points "east".
//
void Camera3D::pivot(double deltaAngle)
{
    // Get the "side" vector which is perpendicular to the "forward" and "up"
    // vectors.
    gen::Vector currentAim = getAim();
    double currentPhi = getPhi(currentAim);

    // If the caller is trying to look further than "down", do nothing.
    if((currentPhi + deltaAngle) > PI) {
        gen::Vector straightDown(gen::Vector(0, -lookMax, 0));
        setLook(straightDown);
    }

    // If the caller is trying to look past zero radians, do nothing.
    else if((currentPhi + deltaAngle) < 0) {
        gen::Vector straightUp(gen::Vector(0, lookMax, 0));
        setLook(straightUp);
    }
    else {
        gen::Vector axis = gen::cross(up, currentAim);
        gen::Vector newAim = currentAim;
        gen::rotateArb(newAim, deltaAngle, axis);
        setLook(newAim);
    }
}


// Roll the camera by some amount.
//
void Camera3D::roll(double deltaAngle)
{
    gen::rotateArb(up, deltaAngle, getAim());
}


// Set the camera to a new elevation.
//
void Camera3D::hang(double newElev)
{
    gen::Vector newPosition = pos;
    newPosition.y = newElev;
    set(newPosition);
}


// Raise the camera in elevation by some amount.
//
void Camera3D::raise(double deltaElev)
{
    translate(0, deltaElev, 0);
}

Cam3D.h

/*
    File: Cam3D.h
    Date: Sunday, September 29, 2019
*/


#ifndef Cam3D_H
#define Cam3D_H


#include <AllegApp.h>
#include <Camera3D.h>
#include <InputHandler.h>
#include <Mesh3D.h>
#include <allegro5/allegro.h>


class Cam3D: public lat::AllegApp {
private:
    meshx::Mesh3D plane1;
    meshx::Mesh3D plane2;
    meshx::Mesh3D plane3;
    Camera3D camera;

    InputHandler inputHandler;
    ALLEGRO_TRANSFORM transform;
    ALLEGRO_TRANSFORM camTrans;

    ALLEGRO_EVENT event;

    int doUpdate();
    int draw();

protected:
    virtual int update() override;

public:
    void init() override;
    Cam3D(int width, int height);
    ~Cam3D();
};


#endif

Cam3D.cpp

/*
    File: Cam3D.cpp
    Date: Sunday, September 29, 2019
*/


#include <Cam3D.h>
#include <Camera3D.h>
#include <AllegApp.h>
#include <Mesh3D.h>
#include <MeshLoader.h>
#include <HardRenderer.h>
#include <MeshOps.h>
#include <ColorsA5.h>
#include <InputHandler.h>
#include <Globals.h>

#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_color.h>

#include <string>


const double PI = 3.141592654;
const double RADIANS_PER_DEGREE = PI / 180.0;

const double FOV = (90 * RADIANS_PER_DEGREE / 2.0);

// Window aspect ratio
double ASPECT_RATIO;

// Camera zoom factor
const double ZOOM = 1.0;

// Frame rate limit (in quarters of 60fps)
const int FRAME_RATE_LIMIT = 4;

const double TERRAIN_SCALE = 1000000;

const double DEPTH = 5000000;
const double CAMERA_LOOK_DEPTH = 1000000;

// Starting camera elevation percentage
const double INITIAL_ELEV_FACTOR = 0.1;

//
const double SPACE_FABRIC_CONSTANT = 1.0 / 128;



inline double degToRad(double deg) {return PI * deg / 180.0;}





Cam3D::Cam3D(int width, int height):
AllegApp(width, height),
camera(CAMERA_LOOK_DEPTH)
{
}





Cam3D::~Cam3D()
{
}





void Cam3D::init()
{
    AllegApp::init();

    ASPECT_RATIO = ((double)getWindowHeight()) / getWindowWidth();



    // setDisplayFullscreen();
    setWindowTitle("Cam3D");
    setFrameRateFrac(FRAME_RATE_LIMIT);
    flLockFrameRate = true;
    flAllowQuitOnEscape = true;



    // Monitor the movement keys.
    inputHandler.monitorKey(ALLEGRO_KEY_PAD_2);
    inputHandler.monitorKey(ALLEGRO_KEY_PAD_8);
    inputHandler.monitorKey(ALLEGRO_KEY_PAD_4);
    inputHandler.monitorKey(ALLEGRO_KEY_PAD_6);

    inputHandler.monitorKey(ALLEGRO_KEY_LEFT);
    inputHandler.monitorKey(ALLEGRO_KEY_RIGHT);
    inputHandler.monitorKey(ALLEGRO_KEY_DOWN);
    inputHandler.monitorKey(ALLEGRO_KEY_UP);

    inputHandler.monitorKey(ALLEGRO_KEY_A);
    inputHandler.monitorKey(ALLEGRO_KEY_D);
    inputHandler.monitorKey(ALLEGRO_KEY_S);
    inputHandler.monitorKey(ALLEGRO_KEY_W);

    inputHandler.monitorKey(ALLEGRO_KEY_SPACE);



    // Load the world.
    plane1 = meshx::loadMesh("H:/testscapes/plane/plane.obj");
    meshx::scaleMesh(plane1, TERRAIN_SCALE);

    plane2 = meshx::loadMesh("H:/testscapes/plane/plane.obj");
    meshx::scaleMesh(plane2, TERRAIN_SCALE / 10.0);
    plane2.offset.y = 100000;

    plane3 = meshx::loadMesh("H:/testscapes/plane/plane.obj");
    meshx::scaleMesh(plane3, TERRAIN_SCALE / 100.0);
    plane3.offset.y = 200000;

    // Set up the camera.
    camera.hang(TERRAIN_SCALE * INITIAL_ELEV_FACTOR);
}





int Cam3D::update()
{
    int result = doUpdate();
    draw();
    return result;
}





int Cam3D::doUpdate()
{
    double deltaTheta = 0;
    double deltaPhi = 0;
    int result = 0;

    camera.maxSpeed = SPACE_FABRIC_CONSTANT * TERRAIN_SCALE * 0.01;



    inputHandler.getKeyboardInput();

    // ------------------------[ROTATION]------------------------

    // Counterclockwise on the X-Z plane

    if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_PAD_4)) {
        deltaTheta = degToRad(+1);
        camera.turn(deltaTheta);
    }

    // Clockwise on the X-Z plane

    else if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_PAD_6)) {
        deltaTheta = degToRad(-1);
        camera.turn(deltaTheta);
    }

    // Look down

    if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_PAD_2)) {
        deltaPhi = degToRad(-1);
        camera.pivot(deltaPhi);
    }

    // Look up

    else if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_PAD_8)) {
        deltaPhi = degToRad(+1);
        camera.pivot(deltaPhi);
    }



    // ------------------------[TRANSLATION]------------------------

    // Speed up

    if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_SPACE)) {
        camera.maxSpeed = SPACE_FABRIC_CONSTANT * TERRAIN_SCALE * 0.1;
    }

    // Forward

    if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_W)) {
        camera.walk(+camera.maxSpeed);
    }

    // Backward

    else if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_S)) {
        camera.walk(-camera.maxSpeed);
    }

    // Translate left

    if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_LEFT)) {
        camera.translate(-camera.maxSpeed, 0, 0);
    }

    // Translate right

    if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_RIGHT)) {
        camera.translate(+camera.maxSpeed, 0, 0);
    }

    // Translate down

    if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_DOWN)) {
        camera.translate(0, 0, -camera.maxSpeed);
    }

    // Translate up

    if(inputHandler.getKeyFlagByCode(ALLEGRO_KEY_UP)) {
        camera.translate(0, 0, +camera.maxSpeed);
    }

    return result;
}





int Cam3D::draw()
{
    // Clear the screen.
    al_clear_to_color(BLACK);

    // Used to restore the previous transform
    ALLEGRO_TRANSFORM old = *al_get_current_projection_transform();

    // Start with the identity transform.
    al_identity_transform(&transform);

    // Set up the perspective projection matrix.
    // Left, Top, Near, Right, Bottom, Far
    al_perspective_transform(&transform,
        (-1.0 / ZOOM), (-ASPECT_RATIO / ZOOM),
        (+1.0 / FOV),
        (+1.0 / ZOOM), (+ASPECT_RATIO / ZOOM),
        DEPTH);

    // Flip the X and Y axes.
    al_scale_transform_3d(&transform, -1, -1, +1);

    al_use_projection_transform(&transform);

    // Set up a matrix to transform from world coordinates to camera
    // coordinates.
    al_build_camera_transform(&camTrans,
        camera.getPosition().x,
        camera.getPosition().y,
        camera.getPosition().z,
        camera.getLook().x,
        camera.getLook().y,
        camera.getLook().z,
        camera.getUp().x,
        camera.getUp().y,
        camera.getUp().z);

    al_use_transform(&camTrans);

    al_set_render_state(ALLEGRO_DEPTH_TEST, 1);
    al_clear_depth_buffer(1);

    // Draw the planes.
    drawMeshWire3D(plane1, GREEN);
    drawMeshWire3D(plane2, CYAN);
    drawMeshWire3D(plane3, MAGENTA);

    // Reset the projection transforms.
    al_identity_transform(&camTrans);
    al_use_transform(&camTrans);

    al_use_projection_transform(&old);

    al_set_render_state(ALLEGRO_DEPTH_TEST, 0);

    al_draw_textf(getSystemFont(), ICEBERG, 0,  0, 0, "%f", getFrameRate());

    al_draw_textf(getSystemFont(), ICEBERG, 0, 10, 0, "%f",
        camera.getPosition().y);

    return 0;
}

main.cpp

/*
    File: main.cpp
    Date: Sunday, September 29, 2019
*/


#include <AllegApp.h>
#include <Cam3D.h>


int main(int argc, char** argv)
{
    lat::AllegApp* x = new Cam3D(16 * 75, 9 * 75);
    x->init();
    x->run();
    delete x;
    x = 0;
    return 0;
}

Globals.h

#ifndef Globals_H
#define Globals_H


extern const double PI;


#endif

\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to Code Review! \$\endgroup\$ – L. F. Oct 11 '19 at 10:29
  • 1
    \$\begingroup\$ While we appreciate your need to keep some code private, there are classes used here that are not defined. This makes the question off-topic. \$\endgroup\$ – pacmaninbw Nov 10 '19 at 15:37
  • \$\begingroup\$ @pacmaninbw Thank you for the advice. Yes, there are many classes used here, however, if I included those class definitions, then the code wouldn't be private anymore. \$\endgroup\$ – Mode77 Nov 11 '19 at 18:28
  • 1
    \$\begingroup\$ You're correct, it wouldn't be private anymore. The code you have posted has now become public domain. Suggestion because I won't be answering this question, in Camera3D.cpp you already include the Globals header, you don't need extern const double PI; in the code again. \$\endgroup\$ – pacmaninbw Nov 11 '19 at 18:32