Reorganize project files

This commit is contained in:
2022-03-07 08:20:32 -05:00
parent 22d6ac1b06
commit e2086fa2dd
24 changed files with 28 additions and 23 deletions

59
src/camera3d.cpp Normal file
View File

@@ -0,0 +1,59 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Fly camera class from tutorials followed at trentreed.net ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <camera3d.h>
const QVector3D Camera3D::LocalForward(0.0f, 0.0f, -1.0f);
const QVector3D Camera3D::LocalUp(0.0f, 1.0f, 0.0f);
const QVector3D Camera3D::LocalRight(1.0f, 0.0f, 0.0f);
/*******************************************************************************
* Accessors
******************************************************************************/
// Produces worldToView matrix
const QMatrix4x4 & Camera3D::toMatrix()
{
mWorld.setToIdentity();
// Qt6 renamed QMatrix4x4::conjugate() to conjugated()
mWorld.rotate(mTransform.rotation().conjugated());
mWorld.translate(-mTransform.translation());
return mWorld;
}
/*******************************************************************************
* Qt Streams
******************************************************************************/
QDataStream & operator<<(QDataStream & out, Camera3D & transform)
{
out << transform.transform();
return out;
}
QDataStream & operator>>(QDataStream & in, Camera3D & transform)
{
in >> transform.transform();
return in;
}
QDebug operator<<(QDebug dbg, const Camera3D & transform)
{
dbg << "Camera3D\n{\n";
dbg << "Position: <" << transform.translation().x() << ", "
<< transform.translation().y() << ", "
<< transform.translation().z() << ">\n";
dbg << "Rotation: <" << transform.rotation().x() << ", "
<< transform.rotation().y() << ", "
<< transform.rotation().z() << " | "
<< transform.rotation().scalar() << ">\n}";
return dbg;
}

62
src/camera3d.h Normal file
View File

@@ -0,0 +1,62 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Fly camera class from tutorials followed at trentreed.net ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTK_CAMERA3D_H
#define QTK_CAMERA3D_H
#include <QDebug>
#include <transform3D.h>
class Camera3D {
public:
// Constants
static const QVector3D LocalForward;
static const QVector3D LocalUp;
static const QVector3D LocalRight;
// Accessors
inline Transform3D & transform() { return mTransform;}
inline const QVector3D & translation() const
{ return mTransform.translation();}
inline const QQuaternion & rotation() const
{ return mTransform.rotation();}
const QMatrix4x4 & toMatrix();
// Queries
inline QVector3D forward() const
{ return mTransform.rotation().rotatedVector(LocalForward);}
inline QVector3D right() const
{ return mTransform.rotation().rotatedVector(LocalRight);}
inline QVector3D up() const
{ return mTransform.rotation().rotatedVector(LocalUp);}
private:
Transform3D mTransform;
QMatrix4x4 mWorld;
#ifndef QT_NO_DATASTREAM
friend QDataStream & operator<<(QDataStream & out, Camera3D & transform);
friend QDataStream & operator>>(QDataStream & in, Camera3D & transform);
#endif
};
Q_DECLARE_TYPEINFO(Camera3D, Q_MOVABLE_TYPE);
// Qt Streams
#ifndef QT_NO_DATASTREAM
QDataStream & operator<<(QDataStream & out, const Camera3D & transform);
QDataStream & operator>>(QDataStream & in, Camera3D & transform);
#endif
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const Camera3D & transform);
#endif
#endif // QTK_CAMERA3D_H

185
src/input.cpp Normal file
View File

@@ -0,0 +1,185 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Input class from tutorials followed at trentreed.net ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <algorithm>
#include <vector>
#include <QCursor>
#include <input.h>
/*******************************************************************************
* Static Helper Structs
******************************************************************************/
template <typename T>
struct InputInstance : std::pair<T, Input::InputState>
{
typedef std::pair<T, Input::InputState> base_class;
inline InputInstance(T value)
: base_class(value, Input::InputInvalid) {}
inline InputInstance(T value, Input::InputState state)
: base_class(value, state) {}
inline bool operator==(const InputInstance & rhs) const
{ return this->first == rhs.first;}
};
// Key, button instance typedefs
typedef InputInstance<Qt::Key> KeyInstance;
typedef InputInstance<Qt::MouseButton> ButtonInstance;
// Key, button instance container typedefs
typedef std::vector<KeyInstance> KeyContainer;
typedef std::vector<ButtonInstance> ButtonContainer;
// Static containers for key, button instances
static KeyContainer sg_keyInstances;
static ButtonContainer sg_buttonInstances;
// Static containers for mouse data
static QPoint sg_mouseCurrPosition;
static QPoint sg_mousePrevPosition;
static QPoint sg_mouseDelta;
/*******************************************************************************
* Static Inline Helper Functions
******************************************************************************/
static inline KeyContainer::iterator FindKey(Qt::Key value)
{
return std::find(sg_keyInstances.begin(), sg_keyInstances.end(), value);
}
static inline ButtonContainer::iterator FindButton(Qt::MouseButton value)
{
return std::find(sg_buttonInstances.begin(), sg_buttonInstances.end(), value);
}
template <typename TPair>
static inline void UpdateStates(TPair & instance)
{
switch (instance.second)
{
case Input::InputRegistered:
instance.second = Input::InputTriggered;
break;
case Input::InputTriggered:
instance.second = Input::InputPressed;
break;
case Input::InputUnregistered:
instance.second = Input::InputReleased;
break;
default:
break;
}
}
template <typename TPair>
static inline bool CheckReleased(const TPair & instance)
{
return instance.second == Input::InputReleased;
}
template <typename Container>
static inline void Update(Container & container)
{
typedef typename Container::iterator Iter;
typedef typename Container::value_type TPair;
// Remove old data
Iter remove =
std::remove_if(container.begin(), container.end(), &CheckReleased<TPair>);
container.erase(remove, container.end());
// Update existing data
std::for_each(container.begin(), container.end(), &UpdateStates<TPair>);
}
/*******************************************************************************
* Input Implementation
******************************************************************************/
Input::InputState Input::keyState(Qt::Key k)
{
KeyContainer::iterator it = FindKey(k);
return (it != sg_keyInstances.end()) ? it->second : InputInvalid;
}
Input::InputState Input::buttonState(Qt::MouseButton k)
{
ButtonContainer::iterator it = FindButton(k);
return (it != sg_buttonInstances.end()) ? it->second : InputInvalid;
}
QPoint Input::mousePosition()
{
return QCursor::pos();
}
QPoint Input::mouseDelta()
{
return sg_mouseDelta;
}
void Input::update()
{
// Update Mouse Delta
sg_mousePrevPosition = sg_mouseCurrPosition;
sg_mouseCurrPosition = QCursor::pos();
sg_mouseDelta = sg_mouseCurrPosition - sg_mousePrevPosition;
// Update KeyState values
Update(sg_buttonInstances);
Update(sg_keyInstances);
}
void Input::registerKeyPress(int k)
{
KeyContainer::iterator it = FindKey((Qt::Key)k);
if (it == sg_keyInstances.end())
{
sg_keyInstances.push_back(KeyInstance((Qt::Key)k, InputRegistered));
}
}
void Input::registerKeyRelease(int k)
{
KeyContainer::iterator it = FindKey((Qt::Key)k);
if (it != sg_keyInstances.end())
{
it->second = InputUnregistered;
}
}
void Input::registerMousePress(Qt::MouseButton btn)
{
ButtonContainer::iterator it = FindButton(btn);
if (it == sg_buttonInstances.end())
{
sg_buttonInstances.push_back(ButtonInstance(btn, InputRegistered));
}
}
void Input::registerMouseRelease(Qt::MouseButton btn)
{
ButtonContainer::iterator it = FindButton(btn);
if (it != sg_buttonInstances.end())
{
it->second = InputUnregistered;
}
}
void Input::reset()
{
sg_keyInstances.clear();
sg_buttonInstances.clear();
}

63
src/input.h Normal file
View File

@@ -0,0 +1,63 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Input class from tutorials followed at trentreed.net ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTOPENGL_INPUT_H
#define QTOPENGL_INPUT_H
#include <QPoint>
#include <Qt>
class Input {
friend class MainWidget;
public:
// Possible key states
enum InputState
{
InputInvalid,
InputRegistered,
InputUnregistered,
InputTriggered,
InputPressed,
InputReleased
};
// State checking
inline static bool keyTriggered(Qt::Key key)
{ return keyState(key) == InputTriggered;}
inline static bool keyPressed(Qt::Key key)
{ return keyState(key) == InputPressed;}
inline static bool keyReleased(Qt::Key key)
{ return keyState(key) == InputReleased;}
inline static bool buttonTriggered(Qt::MouseButton button)
{ return buttonState(button) == InputTriggered;}
inline static bool buttonPressed(Qt::MouseButton button)
{ return buttonState(button) == InputPressed;}
inline static bool buttonReleased(Qt::MouseButton button)
{ return buttonState(button) == InputReleased;}
// Implementation
static InputState keyState(Qt::Key key);
static InputState buttonState(Qt::MouseButton button);
static QPoint mousePosition();
static QPoint mouseDelta();
private:
// State updating
static void update();
static void registerKeyPress(int key);
static void registerKeyRelease(int key);
static void registerMousePress(Qt::MouseButton button);
static void registerMouseRelease(Qt::MouseButton button);
static void reset();
};
#endif // QTOPENGL_INPUT_H

353
src/mainwidget.cpp Normal file
View File

@@ -0,0 +1,353 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Main window for Qt6 OpenGL widget application ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <QKeyEvent>
#include <input.h>
#include <mesh.h>
#include <object.h>
#include <scene.h>
#include <mainwidget.h>
/*******************************************************************************
* Constructors, Destructors
******************************************************************************/
MainWidget::MainWidget() : mDebugLogger(Q_NULLPTR)
{
QSurfaceFormat format;
format.setRenderableType(QSurfaceFormat::OpenGL);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setVersion(4, 5);
format.setDepthBufferSize(16);
// If QTK_DEBUG is set, enable debug context
#ifdef QTK_DEBUG
format.setOption(QSurfaceFormat::DebugContext);
#endif
}
MainWidget::MainWidget(const QSurfaceFormat &format)
: mDebugLogger(Q_NULLPTR)
{
setFormat(format);
resize(QSize(800, 600));
show();
}
MainWidget::~MainWidget()
{
makeCurrent();
teardownGL();
}
/*******************************************************************************
* Private Member Functions
******************************************************************************/
void MainWidget::teardownGL()
{
// Nothing to teardown yet...
}
void MainWidget::initObjects()
{
mScene = new Scene;
// Drawing a primitive object using Qt and OpenGL
// The Object class only stores basic QOpenGL* members and shape data
// + Within mainwidget, mObject serves as a basic QOpenGL example
mObject = new Object("testObject");
mObject->setVertices(Cube(QTK_DRAW_ELEMENTS).vertices());
mObject->setIndices(Cube(QTK_DRAW_ELEMENTS).indices());
mObject->mProgram.create();
mObject->mProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,
":/solid-ambient.vert");
mObject->mProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,
":/solid-ambient.frag");
mObject->mProgram.link();
mObject->mProgram.bind();
mObject->mVAO.create();
mObject->mVAO.bind();
mObject->mVBO.create();
mObject->mVBO.setUsagePattern(QOpenGLBuffer::StaticDraw);
mObject->mVBO.bind();
mObject->mVBO.allocate(mObject->vertices().data(),
mObject->vertices().size()
* sizeof(mObject->vertices()[0]));
mObject->mProgram.enableAttributeArray(0);
mObject->mProgram.setAttributeBuffer(0, GL_FLOAT, 0,
3, sizeof(mObject->vertices()[0]));
mObject->mProgram.setUniformValue("uColor", QVector3D(0.0f, 0.25f, 0.0f));
mObject->mProgram.setUniformValue("uLightColor", WHITE);
mObject->mProgram.setUniformValue("uAmbientStrength", 0.2f);
mObject->mVBO.release();
mObject->mVAO.release();
mObject->mProgram.release();
mObject->mTransform.setTranslation(13.0f, 0.0f, -2.0f);
}
/*******************************************************************************
* Inherited Virtual Member Functions
******************************************************************************/
void MainWidget::paintGL()
{
// Clear buffers
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
// Draw the scene first, since it handles drawing our skybox
mScene->draw();
// Draw any additional objects within mainwidget manually
mObject->mProgram.bind();
mObject->mVAO.bind();
mObject->mProgram.setUniformValue("uModel", mObject->mTransform.toMatrix());
mObject->mProgram.setUniformValue("uView", Scene::Camera().toMatrix());
mObject->mProgram.setUniformValue("uProjection", Scene::Projection());
glDrawElements(GL_TRIANGLES, mObject->indices().size(),
GL_UNSIGNED_INT, mObject->indices().data());
mObject->mVAO.release();
mObject->mProgram.release();
}
void MainWidget::initializeGL()
{
initializeOpenGLFunctions();
// Connect the frameSwapped signal to call the update() function
connect(this, SIGNAL(frameSwapped()), this, SLOT(update()));
// Initialize OpenGL debug context
#ifdef QTK_DEBUG
mDebugLogger = new QOpenGLDebugLogger(this);
if (mDebugLogger->initialize()) {
qDebug() << "GL_DEBUG Debug Logger" << mDebugLogger << "\n";
connect(mDebugLogger, SIGNAL(messageLogged(QOpenGLDebugMessage)),
this, SLOT(messageLogged(QOpenGLDebugMessage)));
mDebugLogger->startLogging();
}
#endif // QTK_DEBUG
printContextInformation();
// Initialize opengl settings
glEnable(GL_MULTISAMPLE);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LEQUAL);
glDepthRange(0.1f, 1.0f);
glClearDepth(1.0f);
glClearColor(0.0f, 0.25f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Initialize default objects within the scene
initObjects();
}
void MainWidget::resizeGL(int width, int height)
{
Scene::Projection().setToIdentity();
Scene::Projection().perspective(45.0f,
float(width) / float(height),
0.1f, 1000.0f);
}
/*******************************************************************************
* Protected Slots
******************************************************************************/
void MainWidget::update()
{
updateCameraInput();
mScene->update();
QWidget::update();
}
void MainWidget::messageLogged(const QOpenGLDebugMessage &msg)
{
QString error;
// Format based on severity
switch (msg.severity())
{
case QOpenGLDebugMessage::NotificationSeverity:
error += "--";
break;
case QOpenGLDebugMessage::HighSeverity:
error += "!!";
break;
case QOpenGLDebugMessage::MediumSeverity:
error += "!~";
break;
case QOpenGLDebugMessage::LowSeverity:
error += "~~";
break;
}
error += " (";
// Format based on source
#define CASE(c) case QOpenGLDebugMessage::c: error += #c; break
switch (msg.source())
{
CASE(APISource);
CASE(WindowSystemSource);
CASE(ShaderCompilerSource);
CASE(ThirdPartySource);
CASE(ApplicationSource);
CASE(OtherSource);
CASE(InvalidSource);
}
#undef CASE
error += " : ";
// Format based on type
#define CASE(c) case QOpenGLDebugMessage::c: error += #c; break
switch (msg.type())
{
CASE(InvalidType);
CASE(ErrorType);
CASE(DeprecatedBehaviorType);
CASE(UndefinedBehaviorType);
CASE(PortabilityType);
CASE(PerformanceType);
CASE(OtherType);
CASE(MarkerType);
CASE(GroupPushType);
CASE(GroupPopType);
}
#undef CASE
error += ")";
qDebug() << qPrintable(error) << "\n" << qPrintable(msg.message()) << "\n";
}
/*******************************************************************************
* Protected Helpers
******************************************************************************/
void MainWidget::keyPressEvent(QKeyEvent *event)
{
if (event->isAutoRepeat()) {
event->ignore();
} else {
Input::registerKeyPress(event->key());
}
}
void MainWidget::keyReleaseEvent(QKeyEvent *event)
{
if (event->isAutoRepeat()) {
event->ignore();
} else {
Input::registerKeyRelease(event->key());
}
}
void MainWidget::mousePressEvent(QMouseEvent *event)
{
Input::registerMousePress(event->button());
}
void MainWidget::mouseReleaseEvent(QMouseEvent *event)
{
Input::registerMouseRelease(event->button());
}
/*******************************************************************************
* Private Helpers
******************************************************************************/
void MainWidget::printContextInformation()
{
QString glType;
QString glVersion;
QString glProfile;
QString glVendor;
QString glRenderer;
// Get Version Information
glType = (context()->isOpenGLES()) ? "OpenGL ES" : "OpenGL";
glVersion = reinterpret_cast<const char *>(glGetString(GL_VERSION));
glVendor =
reinterpret_cast<const char *>(glGetString(GL_VENDOR));
glRenderer =
reinterpret_cast<const char *>(glGetString(GL_RENDERER));
// Get Profile Information
#define CASE(c) case QSurfaceFormat::c: glProfile = #c; break
switch (format().profile()) {
CASE(NoProfile);
CASE(CoreProfile);
CASE(CompatibilityProfile);
}
#undef CASE
// qPrintable() will print our QString w/o quotes around it.
qDebug() << qPrintable(glType) << qPrintable(glVersion) << "("
<< qPrintable(glProfile) << ")"
<< "\nOpenGL Vendor: " << qPrintable(glVendor)
<< "\nRendering Device: " << qPrintable(glRenderer) << "\n";
}
void MainWidget::updateCameraInput()
{
Input::update();
// Camera Transformation
if (Input::buttonPressed(Qt::RightButton)) {
static const float transSpeed = 0.1f;
static const float rotSpeed = 0.5f;
// Handle rotations
Scene::Camera().transform().rotate(-rotSpeed * Input::mouseDelta().x(),
Camera3D::LocalUp);
Scene::Camera().transform().rotate(-rotSpeed * Input::mouseDelta().y(),
Scene::Camera().right());
// Handle translations
QVector3D translation;
if (Input::keyPressed(Qt::Key_W)) {
translation += Scene::Camera().forward();
}
if (Input::keyPressed(Qt::Key_S)) {
translation -= Scene::Camera().forward();
}
if (Input::keyPressed(Qt::Key_A)) {
translation -= Scene::Camera().right();
}
if (Input::keyPressed(Qt::Key_D)) {
translation += Scene::Camera().right();
}
if (Input::keyPressed(Qt::Key_Q)) {
translation -= Scene::Camera().up() / 2.0f;
}
if (Input::keyPressed(Qt::Key_E)) {
translation += Scene::Camera().up() / 2.0f;
}
Scene::Camera().transform().translate(transSpeed * translation);
}
}

69
src/mainwidget.h Normal file
View File

@@ -0,0 +1,69 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Main window for Qt6 OpenGL widget application ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTK_MAINWIDGET_H
#define QTK_MAINWIDGET_H
#include <iostream>
#include <QMatrix4x4>
#include <QOpenGLDebugLogger>
#include <QOpenGLFunctions>
#include <QOpenGLWidget>
#define QTK_DEBUG
class MeshRenderer;
class Model;
class Object;
class Scene;
class Skybox;
class Texture;
class MainWidget : public QOpenGLWidget,
protected QOpenGLFunctions {
Q_OBJECT;
public:
// Constructors
MainWidget();
explicit MainWidget(const QSurfaceFormat &format);
~MainWidget() override;
private:
void teardownGL();
void initObjects();
public:
// Inherited virtual Members
void paintGL() override;
void initializeGL() override;
void resizeGL(int width, int height) override;
protected slots:
void update();
void messageLogged(const QOpenGLDebugMessage &msg);
// Protected Helpers
protected:
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
private:
// Private helpers
void printContextInformation();
void updateCameraInput();
Scene * mScene;
Object * mObject;
QOpenGLDebugLogger * mDebugLogger;
};
#endif // QTK_MAINWIDGET_H

336
src/mesh.cpp Normal file
View File

@@ -0,0 +1,336 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Collection of static mesh data for quick initialization ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <mesh.h>
Cube::Cube(DrawMode mode)
{
mDrawMode = mode;
switch(mode) {
// Cube data for use with glDrawArrays
case QTK_DRAW_ARRAYS:
mIndices = { /* No indices needed for glDrawArrays */ };
mNormals =
{FACE_FRONT, FACE_BACK, FACE_TOP, FACE_BOTTOM, FACE_LEFT, FACE_RIGHT};
mVertices = {
// Face 1 (Front)
VERTEX_FTR, VERTEX_FTL, VERTEX_FBL,
VERTEX_FBL, VERTEX_FBR, VERTEX_FTR,
// Face 2 (Back)
VERTEX_BBR, VERTEX_BTL, VERTEX_BTR,
VERTEX_BTL, VERTEX_BBR, VERTEX_BBL,
// Face 3 (Top)
VERTEX_FTR, VERTEX_BTR, VERTEX_BTL,
VERTEX_BTL, VERTEX_FTL, VERTEX_FTR,
// Face 4 (Bottom)
VERTEX_FBR, VERTEX_FBL, VERTEX_BBL,
VERTEX_BBL, VERTEX_BBR, VERTEX_FBR,
// Face 5 (Left)
VERTEX_FBL, VERTEX_FTL, VERTEX_BTL,
VERTEX_FBL, VERTEX_BTL, VERTEX_BBL,
// Face 6 (Right)
VERTEX_FTR, VERTEX_FBR, VERTEX_BBR,
VERTEX_BBR, VERTEX_BTR, VERTEX_FTR
};
mColors = {
// Face 1 (Front)
RED, GREEN, BLUE,
BLUE, WHITE, RED,
// Face 2 (Back)
YELLOW, CYAN, MAGENTA,
CYAN, YELLOW, BLACK,
// Face 3 (Top)
RED, MAGENTA, CYAN,
CYAN, GREEN, RED,
// Face 4 (Bottom)
WHITE, BLUE, BLACK,
BLACK, YELLOW, WHITE,
// Face 5 (Left)
BLUE, GREEN, CYAN,
BLUE, CYAN, BLACK,
// Face 6 (Right)
RED, WHITE, YELLOW,
YELLOW, MAGENTA, RED
};
mTexCoords = {
// Face 1 (Front)
UV_TOP, UV_ORIGIN, UV_RIGHT,
UV_RIGHT, UV_CORNER, UV_TOP,
// Face 2 (Back)
UV_TOP, UV_RIGHT, UV_CORNER,
UV_RIGHT, UV_TOP, UV_ORIGIN,
// Face 3 (Top)
UV_CORNER, UV_TOP, UV_ORIGIN,
UV_ORIGIN, UV_RIGHT, UV_CORNER,
// Face 4 (Bottom)
UV_TOP, UV_ORIGIN, UV_RIGHT,
UV_RIGHT, UV_CORNER, UV_TOP,
// Face 5 (Left)
UV_TOP, UV_CORNER, UV_RIGHT,
UV_TOP, UV_RIGHT, UV_ORIGIN,
// Face 6 (Right)
UV_TOP, UV_CORNER, UV_RIGHT,
UV_RIGHT, UV_ORIGIN, UV_TOP
};
break;
// Cube data for use with glDrawElements
case QTK_DRAW_ELEMENTS:
mNormals =
{/* For normals and glDrawElements, see QTK_DRAW_ELEMENTS_NORMALS */};
mTexCoords =
{ /* For UVs and glDrawElements, see QTK_DRAW_ELEMENTS_NORMALS */ };
mColors = {RED, GREEN, BLUE, WHITE, YELLOW, CYAN, MAGENTA, BLACK};
mVertices = {
// 0 1 2 3
VERTEX_FTR, VERTEX_FTL, VERTEX_FBL, VERTEX_FBR,
// 4 5 6 7
VERTEX_BTR, VERTEX_BTL, VERTEX_BBL, VERTEX_BBR
};
mIndices = {
// Face 1 (Front)
0, 1, 2, 2, 3, 0,
// Face 2 (Back)
7, 5, 4, 5, 7, 6,
// Face 3 (Top)
0, 4, 5, 5, 1, 0,
// Face 4 (Bottom)
3, 2, 6, 6, 7, 3,
// Face 5 (Left)
2, 1, 5, 2, 5, 6,
// Face 6 (Right)
0, 3, 7, 7, 4, 0
};
break;
// Cube shape data for using normals and UVs with glDrawElements
case QTK_DRAW_ELEMENTS_NORMALS:
mColors = {RED, GREEN, BLUE, WHITE, YELLOW, CYAN, MAGENTA, BLACK};
mVertices = {
// Face 1 (Front)
// 0 1 2 3
VERTEX_FTL, VERTEX_FBL, VERTEX_FBR, VERTEX_FTR,
// Face 2 (Back)
// 4 5 6 7
VERTEX_BTL, VERTEX_BBL, VERTEX_BBR, VERTEX_BTR,
// Face 3 (Top)
// 8 9 10 11
VERTEX_FTL, VERTEX_BTL, VERTEX_BTR, VERTEX_FTR,
// Face 4 (Bottom)
// 12 13 14 15
VERTEX_FBL, VERTEX_BBL, VERTEX_BBR, VERTEX_FBR,
// Face 5 (Left)
// 16 17 18 19
VERTEX_FBL, VERTEX_BBL, VERTEX_BTL, VERTEX_FTL,
// Face 6 (Right)
// 20 21 22 23
VERTEX_FBR, VERTEX_BBR, VERTEX_BTR, VERTEX_FTR
};
mIndices = {
// Face 1 (Front)
0, 1, 2, 2, 3, 0,
// Face 2 (Back)
4, 5, 6, 6, 7, 4,
// Face 3 (Top)
8, 9, 10, 10, 11, 8,
// Face 4 (Bottom)
12, 13, 14, 14, 15, 12,
// Face 5 (Left)
16, 17, 18, 18, 19, 16,
// Face 6 (Right)
20, 21, 22, 22, 23, 20
};
mNormals = {
VECTOR_FORWARD, VECTOR_FORWARD, VECTOR_FORWARD, VECTOR_FORWARD,
VECTOR_BACK, VECTOR_BACK, VECTOR_BACK, VECTOR_BACK,
VECTOR_UP, VECTOR_UP, VECTOR_UP, VECTOR_UP,
VECTOR_DOWN, VECTOR_DOWN, VECTOR_DOWN, VECTOR_DOWN,
VECTOR_LEFT, VECTOR_LEFT, VECTOR_LEFT, VECTOR_LEFT,
VECTOR_RIGHT, VECTOR_RIGHT, VECTOR_RIGHT, VECTOR_RIGHT,
};
mTexCoords = {
// Face 1 (Front)
UV_TOP, UV_RIGHT, UV_CORNER,
UV_RIGHT, UV_TOP, UV_ORIGIN,
// Face 2 (Back)
UV_TOP, UV_RIGHT, UV_CORNER,
UV_RIGHT, UV_TOP, UV_ORIGIN,
// Face 3 (Top)
UV_TOP, UV_RIGHT, UV_CORNER,
UV_RIGHT, UV_TOP, UV_ORIGIN,
// Face 4 (Bottom)
UV_TOP, UV_RIGHT, UV_CORNER,
UV_RIGHT, UV_TOP, UV_ORIGIN,
// Face 5 (Left)
UV_TOP, UV_RIGHT, UV_CORNER,
UV_RIGHT, UV_TOP, UV_ORIGIN,
// Face 6 (Right)
UV_TOP, UV_RIGHT, UV_CORNER,
UV_RIGHT, UV_TOP, UV_ORIGIN,
};
break;
}
}
Triangle::Triangle(DrawMode mode)
{
mDrawMode = mode;
const QVector3D triangleTop = QVector3D(0.0f, 0.5f, 0.0f);
switch(mode) {
case QTK_DRAW_ARRAYS:
mIndices = { /* No indices needed for glDrawArrays */ };
mColors = { RED, GREEN, BLUE, WHITE, YELLOW, CYAN, MAGENTA, BLACK };
mVertices = {
// Bottom face (Base of the pyramid)
VERTEX_BBL, VERTEX_BBR, VERTEX_FBR,
VERTEX_FBR, VERTEX_FBL, VERTEX_BBL,
// Front face
VERTEX_FBL, VERTEX_FBR, triangleTop,
// Back face
VERTEX_BBR, VERTEX_BBL, triangleTop,
// Left face
VERTEX_BBL, VERTEX_FBL, triangleTop,
// Right face
VERTEX_FBR, VERTEX_BBR, triangleTop,
};
// Find normals for each triangle of the mesh
for (int i = 0; i < mVertices.size(); i += 3) {
QVector3D vertexNormal =
QVector3D::normal(mVertices[i], mVertices[i+1], mVertices[i+2]);
// Three points share this normal
for (int j = 0; j < 3; j++) {
mNormals.push_back(vertexNormal);
}
}
mTexCoords = {
// Bottom face (Base of the pyramid)
UV_ORIGIN, UV_RIGHT, UV_CORNER,
UV_CORNER, UV_TOP, UV_ORIGIN,
// Front face
UV_ORIGIN, UV_RIGHT, UV_CORNER,
// Back face
UV_ORIGIN, UV_RIGHT, UV_CORNER,
// Left face
UV_ORIGIN, UV_RIGHT, UV_CORNER,
// Right face
UV_ORIGIN, UV_RIGHT, UV_CORNER,
};
break;
// Triangle shape data for using glDrawElements
case QTK_DRAW_ELEMENTS:
mColors = { RED, GREEN, BLUE, WHITE, YELLOW, CYAN, MAGENTA, BLACK };
mVertices = {VERTEX_FBL, VERTEX_FBR, VERTEX_BBL, VERTEX_BBR, triangleTop};
mIndices = {
// Bottom face (Base of the pyramid)
2, 3, 1, // Use customVertexes[2], then 3, 1...
1, 0, 2, // Use customVertexes[1], then 0, 2
0, 1, 4, // Front face
3, 2, 4, // Back face
2, 0, 4, // Left face
1, 3, 4, // Right face
};
mNormals =
{/* Use QTK_DRAW_ELEMENTS_NORMALS for normals with glDrawElements */};
mTexCoords = { /* No UVs for triangle with glDrawElements */ };
break;
// Triangle shape data for using normals and UVs with glDrawElements
case QTK_DRAW_ELEMENTS_NORMALS:
mColors = { RED, GREEN, BLUE, WHITE, YELLOW, CYAN, MAGENTA, BLACK };
mVertices = {
// Bottom face
// 0 1 2
VERTEX_FBL, VERTEX_FBR, VERTEX_BBL,
// 3 4 5
VERTEX_BBR, VERTEX_FBR, VERTEX_BBL,
// Front face
// 6 7 8
VERTEX_FBL, VERTEX_FBR, triangleTop,
// Back face
// 9 10 11
VERTEX_BBR, VERTEX_BBL, triangleTop,
// Left face
// 12 13 14
VERTEX_BBL, VERTEX_FBL, triangleTop,
// Right face
// 15 16 17
VERTEX_FBR, VERTEX_BBR, triangleTop,
};
mIndices = {
// Bottom face (Base of the pyramid)
0, 1, 2, // Use customVertexes[2], then 3, 1...
3, 4, 5, // Use customVertexes[1], then 0, 2
6, 7, 8, // Front face
9, 10, 11, // Back face
12, 13, 14, // Left face
15, 16, 17, // Right face
};
// Find normals for each triangle of the mesh
for (int i = 0; i < mVertices.size(); i += 3) {
QVector3D vertexNormal =
QVector3D::normal(mVertices[mIndices[i]],
mVertices[mIndices[i+1]],
mVertices[mIndices[i+2]]);
// Three points share this normal
for (int j = 0; j < 3; j++) {
mNormals.push_back(vertexNormal);
}
}
mTexCoords = {
// Bottom face
UV_ORIGIN, UV_RIGHT, UV_TOP,
UV_CORNER, UV_RIGHT, UV_TOP,
// Front face
UV_ORIGIN, UV_RIGHT, UV_CORNER,
// Back face
UV_ORIGIN, UV_RIGHT, UV_CORNER,
// Left face
UV_ORIGIN, UV_RIGHT, UV_CORNER,
// Right face
UV_ORIGIN, UV_RIGHT, UV_CORNER,
};
break;
}
}

132
src/mesh.h Normal file
View File

@@ -0,0 +1,132 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Collection of static mesh data for quick initialization ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTK_MESH_H
#define QTK_MESH_H
#include <QOpenGLWidget>
#include <QVector2D>
#include <QVector3D>
#include <transform3D.h>
class MeshRenderer;
class Object;
// Define vertices for drawing a cube using two faces (8 vertex points)
// Front Vertices
#define VERTEX_FTR QVector3D( 0.5f, 0.5f, 0.5f) // 1
#define VERTEX_FTL QVector3D(-0.5f, 0.5f, 0.5f) // 2
#define VERTEX_FBL QVector3D(-0.5f, -0.5f, 0.5f) // 3
#define VERTEX_FBR QVector3D( 0.5f, -0.5f, 0.5f) // 4
// Back Vertices
#define VERTEX_BTR QVector3D( 0.5f, 0.5f, -0.5f) // 5
#define VERTEX_BTL QVector3D(-0.5f, 0.5f, -0.5f) // 6
#define VERTEX_BBL QVector3D(-0.5f, -0.5f, -0.5f) // 7
#define VERTEX_BBR QVector3D( 0.5f, -0.5f, -0.5f) // 8
// Direction vectors
#define VECTOR_UP QVector3D(0.0f, 1.0f, 0.0f)
#define VECTOR_DOWN QVector3D(0.0f, -1.0f, 0.0f)
#define VECTOR_LEFT QVector3D(-1.0f, 0.0f, 0.0f)
#define VECTOR_RIGHT QVector3D(1.0f, 0.0f, 0.0f)
#define VECTOR_FORWARD QVector3D(0.0f, 0.0f, 1.0f)
#define VECTOR_BACK QVector3D(0.0f, 0.0f, -1.0f)
// Identity and zero vectors
#define VECTOR_ONE QVector3D(1.0f, 1.0f, 1.0f)
#define VECTOR_ZERO QVector3D(0.0f, 0.0f, 0.0f)
// A series of direction vectors to represent cube face normal
#define FACE_TOP VECTOR_UP, VECTOR_UP, VECTOR_UP, \
VECTOR_UP, VECTOR_UP, VECTOR_UP
#define FACE_BOTTOM VECTOR_DOWN, VECTOR_DOWN, VECTOR_DOWN, \
VECTOR_DOWN, VECTOR_DOWN, VECTOR_DOWN
#define FACE_LEFT VECTOR_LEFT, VECTOR_LEFT, VECTOR_LEFT, \
VECTOR_LEFT, VECTOR_LEFT, VECTOR_LEFT
#define FACE_RIGHT VECTOR_RIGHT, VECTOR_RIGHT, VECTOR_RIGHT, \
VECTOR_RIGHT, VECTOR_RIGHT, VECTOR_RIGHT
#define FACE_FRONT VECTOR_FORWARD, VECTOR_FORWARD, VECTOR_FORWARD, \
VECTOR_FORWARD, VECTOR_FORWARD, VECTOR_FORWARD
#define FACE_BACK VECTOR_BACK, VECTOR_BACK, VECTOR_BACK, \
VECTOR_BACK, VECTOR_BACK, VECTOR_BACK
// Colors using QVector3Ds as RGB values
#define WHITE VECTOR_ONE
#define BLACK VECTOR_ZERO
#define RED QVector3D(1.0f, 0.0f, 0.0f)
#define GREEN QVector3D(0.0f, 1.0f, 0.0f)
#define BLUE QVector3D(0.0f, 0.0f, 1.0f)
#define YELLOW QVector3D(1.0f, 1.0f, 0.0f)
#define CYAN QVector3D(0.0f, 1.0f, 1.0f)
#define MAGENTA QVector3D(1.0f, 0.0f, 1.0f)
#define UV_ORIGIN QVector2D(0.0f, 0.0f)
#define UV_TOP QVector2D(1.0f, 0.0f)
#define UV_RIGHT QVector2D(0.0f, 1.0f)
#define UV_CORNER QVector2D(1.0f, 1.0f)
typedef std::vector<QVector3D> Vertices;
typedef std::vector<QVector3D> Colors;
typedef std::vector<GLuint> Indices;
typedef std::vector<QVector2D> TexCoords;
typedef std::vector<QVector3D> Normals;
enum DrawMode { QTK_DRAW_ARRAYS, QTK_DRAW_ELEMENTS, QTK_DRAW_ELEMENTS_NORMALS };
struct ShapeBase {
ShapeBase(DrawMode mode=QTK_DRAW_ARRAYS, Vertices v={},Indices i={}, Colors c={},
TexCoords t={}, Normals n={})
: mVertices(v), mColors(c), mIndices(i), mTexCoords(t), mNormals(n)
{}
inline const Vertices & vertices() const { return mVertices;}
inline const Indices & indices() const { return mIndices;}
inline const Colors & colors() const { return mColors;}
inline const TexCoords & texCoords() const { return mTexCoords;}
inline const Normals & normals() const { return mNormals;}
protected:
DrawMode mDrawMode;
Vertices mVertices;
Colors mColors;
Indices mIndices;
TexCoords mTexCoords;
Normals mNormals;
};
struct Shape : public ShapeBase {
friend MeshRenderer;
friend Object;
Shape () {}
Shape(const ShapeBase & rhs) : ShapeBase(rhs) {}
virtual inline void setVertices(const Vertices & value) {mVertices = value;}
virtual inline void setIndices(const Indices & value) {mIndices = value;}
virtual inline void setColors(const Colors & value) {mColors = value;}
virtual inline void setTexCoords(const TexCoords & value) {mTexCoords = value;}
virtual inline void setNormals(const Normals & value) {mNormals = value;}
virtual inline void setShape(const Shape & value) { *this = value;}
};
// Primitives inherit from ShapeBase, does not allow setting of shape values
class Mesh {
};
struct Cube : public ShapeBase {
Cube(DrawMode mode=QTK_DRAW_ARRAYS);
};
struct Triangle : public ShapeBase {
Triangle(DrawMode mode=QTK_DRAW_ARRAYS);
};
#endif // QTK_MESH_H

172
src/meshrenderer.cpp Normal file
View File

@@ -0,0 +1,172 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: MeshRenderer class for quick object creation and drawing ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <QImageReader>
#include <scene.h>
#include <texture.h>
#include <meshrenderer.h>
// Static QHash that holds all MeshRenderer instances using their mName as keys
MeshRenderer::MeshManager MeshRenderer::sInstances;
MeshRenderer::MeshRenderer(const char * name, const ShapeBase & shape)
: Object(name, shape), mVertexShader(":/multi-color.vert"),
mFragmentShader(":/multi-color.frag"), mDrawType(GL_TRIANGLES),
mHasTexture(false)
{
mShape = Shape(shape);
init();
sInstances.insert(name, this);
}
MeshRenderer::~MeshRenderer()
{
if (mHasTexture) {
mTexture->destroy();
}
sInstances.remove(mName);
}
// Static member function to retrieve instances of MeshRenderers
MeshRenderer * MeshRenderer::getInstance(const QString & name)
{ return sInstances[name];}
/*******************************************************************************
* Public Member Functions
******************************************************************************/
void MeshRenderer::init()
{
if (mVAO.isCreated()) mVAO.destroy();
if (mProgram.isLinked()) mProgram.removeAllShaders();
if (mVBO.isCreated()) mVBO.destroy();
mVAO.create();
mVAO.bind();
mProgram.create();
mProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,
mVertexShader.c_str());
mProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,
mFragmentShader.c_str());
mProgram.link();
mProgram.bind();
mVBO.create();
mVBO.setUsagePattern(QOpenGLBuffer::StaticDraw);
mVBO.bind();
// Combine position and color data into one vector, allowing us to use one VBO
Vertices combined;
combined.reserve(vertices().size() + colors().size());
combined.insert(combined.end(), vertices().begin(), vertices().end());
combined.insert(combined.end(), colors().begin(), colors().end());
mVBO.allocate(combined.data(),
combined.size() * sizeof(combined[0]));
// Enable position attribute
mProgram.enableAttributeArray(0);
mProgram.setAttributeBuffer(0, GL_FLOAT, 0,
3, sizeof(QVector3D));
// Enable color attribute, setting offset to total size of vertices()
mProgram.enableAttributeArray(1);
mProgram.setAttributeBuffer(1, GL_FLOAT,
vertices().size() * sizeof(vertices()[0]),
3, sizeof(QVector3D));
mVBO.release();
mProgram.release();
mVAO.release();
}
void MeshRenderer::draw()
{
mProgram.bind();
mVAO.bind();
if(mHasTexture) {
mTexture->bind();
}
// TODO: Automate uniforms some other way
setUniformMVP();
if (mShape.mDrawMode == QTK_DRAW_ARRAYS) {
glDrawArrays(mDrawType, 0, vertices().size());
}
else if (mShape.mDrawMode == QTK_DRAW_ELEMENTS
|| mShape.mDrawMode == QTK_DRAW_ELEMENTS_NORMALS) {
glDrawElements(mDrawType, mShape.mIndices.size(),
GL_UNSIGNED_INT, mShape.mIndices.data());
}
if(mHasTexture) {
mTexture->release();
}
mVAO.release();
mProgram.release();
}
void MeshRenderer::setShaders(const std::string & vert, const std::string & frag)
{
mVertexShader = vert;
mFragmentShader = frag;
}
void MeshRenderer::setUniformMVP(const char * model, const char * view,
const char * projection)
{
mProgram.setUniformValue(projection, Scene::Projection());
mProgram.setUniformValue(view, Scene::View());
mProgram.setUniformValue(model, mTransform.toMatrix());
}
void MeshRenderer::setColor(const QVector3D & color)
{
if (mShape.mColors.empty()) {
for (const auto & vertex : mShape.vertices()) {
mShape.mColors.push_back(color);
}
}
else {
for (int i = 0; i < mShape.colors().size(); i++) {
mShape.mColors[i] = color;
}
}
}
void MeshRenderer::setTexture(const char * path)
{
mTexture = new QOpenGLTexture(*Texture::initImage(path));
mHasTexture = true;
}
void MeshRenderer::setTexture(QOpenGLTexture * texture)
{
mTexture = texture;
mHasTexture = true;
}
/*******************************************************************************
* Inherited Virtual Member Functions
******************************************************************************/
void MeshRenderer::setShape(const Shape & value)
{
Object::setShape(value);
init();
}

76
src/meshrenderer.h Normal file
View File

@@ -0,0 +1,76 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: MeshRenderer class for quick object creation and drawing ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTK_MESHRENDERER_H
#define QTK_MESHRENDERER_H
#include <mesh.h>
#include <object.h>
class MeshRenderer : public Object {
public:
// Delegate constructors
MeshRenderer(const char * name, Vertices vertices, Indices indices,
DrawMode mode=QTK_DRAW_ARRAYS)
: MeshRenderer(name, ShapeBase(mode, vertices, indices))
{}
MeshRenderer(const char * name)
: MeshRenderer(name, Cube(QTK_DRAW_ELEMENTS))
{}
// Constructor
MeshRenderer(const char * name, const ShapeBase &shape);
~MeshRenderer();
// Retrieve a mesh by name stored within a static QHash
static MeshRenderer * getInstance(const QString & name);
void init();
void draw();
// Draw types like GL_TRIANGLES, GL_POINTS, GL_LINES, etc
void setDrawType(int drawType) { mDrawType = drawType;}
// Shader settings
inline void setShaderVertex(const std::string & vert) { mVertexShader = vert;}
inline void setShaderFragment(const std::string & frag) { mFragmentShader = frag;}
void setShaders(const std::string & vert, const std::string & frag);
template <typename T>
inline void setUniform(int location, T value)
{ mProgram.setUniformValue(location, value);}
template <typename T>
inline void setUniform(const char * location, T value)
{ mProgram.setUniformValue(location, value);}
// Set MVP matrix using this Object's transform
// + View and projection provided by MainWidget static members
void setUniformMVP(const char * model="uModel", const char * view="uView",
const char * projection="uProjection");
// Sets the texture to the image at the given path
// + Sets mHasTexture to enable texture binding in draw()
void setTexture(const char * path);
void setTexture(QOpenGLTexture * texture);
// These functions modify data stored in a VBO
// + After calling them, the VBO will need to be reallocated
void setShape(const Shape & value) override;
void setColor(const QVector3D & color);
// Static QHash of all mesh objects within the scene
typedef QHash<QString, MeshRenderer *> MeshManager;
private:
static MeshManager sInstances;
int mDrawType;
bool mHasTexture;
std::string mVertexShader, mFragmentShader;
};
#endif // QTK_MESHRENDERER_H

401
src/model.cpp Normal file
View File

@@ -0,0 +1,401 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Model classes for importing with Assimp ##
## From following tutorials on learnopengl.com ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <QFileInfo>
#include <scene.h>
#include <texture.h>
#include <model.h>
Model::ModelManager Model::mManager;
// Static function to access ModelManager for getting Models by name
Model * Model::getInstance(const char * name)
{
return mManager[name];
}
/*******************************************************************************
* ModelMesh Private Member Functions
******************************************************************************/
void ModelMesh::initMesh(const char * vert, const char * frag)
{
// Create VAO, VBO, EBO
mVAO->create();
mVBO->create();
mEBO->create();
mVAO->bind();
// Allocate VBO
mVBO->setUsagePattern(QOpenGLBuffer::StaticDraw);
mVBO->bind();
mVBO->allocate(mVertices.data(),
mVertices.size() * sizeof(mVertices[0]));
// Allocate EBO
mEBO->setUsagePattern(QOpenGLBuffer::StaticDraw);
mEBO->bind();
mEBO->allocate(mIndices.data(),
mIndices.size() * sizeof(mIndices[0]));
mEBO->release();
// Load and link shaders
mProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, vert);
mProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, frag);
mProgram->link();
mProgram->bind();
// Positions
mProgram->enableAttributeArray(0);
mProgram->setAttributeBuffer(0, GL_FLOAT,
offsetof(ModelVertex, mPosition), 3,
sizeof(ModelVertex));
// Normals
mProgram->enableAttributeArray(1);
mProgram->setAttributeBuffer(1, GL_FLOAT,
offsetof(ModelVertex, mNormal), 3,
sizeof(ModelVertex));
// Texture Coordinates
mProgram->enableAttributeArray(2);
mProgram->setAttributeBuffer(2, GL_FLOAT,
offsetof(ModelVertex, mTextureCoord), 2,
sizeof(ModelVertex));
// Vertex tangents
mProgram->enableAttributeArray(3);
mProgram->setAttributeBuffer(3, GL_FLOAT,
offsetof(ModelVertex, mTangent), 3,
sizeof(ModelVertex));
// Vertex bitangents
mProgram->enableAttributeArray(4);
mProgram->setAttributeBuffer(4, GL_FLOAT,
offsetof(ModelVertex, mBitangent), 3,
sizeof(ModelVertex));
mProgram->release();
mVBO->release();
mVAO->release();
}
/*******************************************************************************
* ModelMesh Public Member Functions
******************************************************************************/
void ModelMesh::draw(QOpenGLShaderProgram & shader)
{
mVAO->bind();
// Bind shader
shader.bind();
// Set Model View Projection values
shader.setUniformValue("uModel", mTransform.toMatrix());
shader.setUniformValue("uView", Scene::View());
shader.setUniformValue("uProjection", Scene::Projection());
GLuint diffuseCount = 1;
GLuint specularCount = 1;
GLuint normalCount = 1;
for (GLuint i = 0; i < mTextures.size(); i++) {
// Activate the current texture index by adding offset to GL_TEXTURE0
glActiveTexture(GL_TEXTURE0 + i);
mTextures[i].mTexture->bind();
// Get a name for the texture using a known convention -
// Diffuse: material.texture_diffuse1, material.texture_diffuse2, ...
// Specular: material.texture_specular1, material.texture_specular2, ...
std::string number;
std::string name = mTextures[i].mType;
if (name == "texture_diffuse") number = std::to_string(diffuseCount++);
if (name == "texture_specular") number = std::to_string(specularCount++);
if (name == "texture_normal") number = std::to_string(normalCount++);
// Set the uniform to track this texture ID using our naming convention
shader.setUniformValue((name + number).c_str(), i);
}
// Draw the mesh
glDrawElements(GL_TRIANGLES, mIndices.size(),
GL_UNSIGNED_INT, mIndices.data());
// Release shader, textures
for (const auto & texture : mTextures) {
texture.mTexture->release();
}
shader.release();
mVAO->release();
glActiveTexture(GL_TEXTURE0);
}
/*******************************************************************************
* Model Public Member Functions
******************************************************************************/
void Model::draw()
{
for (GLuint i = 0; i < mMeshes.size(); i++) {
mMeshes[i].mTransform = mTransform;
mMeshes[i].draw();
}
}
void Model::draw(QOpenGLShaderProgram & shader)
{
for (GLuint i = 0; i < mMeshes.size(); i++) {
mMeshes[i].mTransform = mTransform;
mMeshes[i].draw(shader);
}
}
void Model::flipTexture(const std::string & fileName, bool flipX, bool flipY)
{
bool modified = false;
std::string fullPath = mDirectory + '/' + fileName;
for (auto & texture : mTexturesLoaded) {
if (texture.mPath == fileName) {
texture.mTexture->destroy();
texture.mTexture->create();
texture.mTexture->setData(
*Texture::initImage(fullPath.c_str(), flipX, flipY));
modified = true;
}
}
if (!modified) {
qDebug() << "Attempt to flip texture that doesn't exist: "
<< fullPath.c_str() << "\n";
}
}
/*******************************************************************************
* Model Private Member Functions
******************************************************************************/
void Model::loadModel(const std::string & path)
{
Assimp::Importer import;
// JIC a relative path was used, get the absolute file path
QFileInfo info(path.c_str());
info.makeAbsolute();
std::string temp = info.absoluteFilePath().toStdString();
// Import the model, converting non-triangular geometry to triangles
// + And flipping texture UVs, etc..
// Assimp options: http://assimp.sourceforge.net/lib_html/postprocess_8h.html
const aiScene * scene =
import.ReadFile(temp, aiProcess_Triangulate
| aiProcess_FlipUVs
| aiProcess_GenSmoothNormals
| aiProcess_CalcTangentSpace
| aiProcess_OptimizeMeshes
| aiProcess_SplitLargeMeshes
);
// If there were errors, print and return
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
qDebug() << "Error::ASSIMP::" << import.GetErrorString() << "\n";
return;
}
// If there were no errors, find the directory that contains this model
mDirectory = path.substr(0, path.find_last_of('/'));
// Pass the pointers to the root node and the scene to recursive function
// + Base case breaks when no nodes left to process on model
processNode(scene->mRootNode, scene);
// Sort models by their distance from the camera
// Optimizes drawing so that overlapping objects are not overwritten
// + Since the topmost object will be drawn first
sortModels();
// Object finished loading, insert it into ModelManager
mManager.insert(mName, this);
}
void Model::processNode(aiNode * node, const aiScene * scene)
{
// Process each mesh that is available for this node
for (GLuint i = 0; i < node->mNumMeshes; i++) {
aiMesh * mesh = scene->mMeshes[node->mMeshes[i]];
mMeshes.push_back(processMesh(mesh, scene));
}
// Process each child node for this mesh using recursion
for (GLuint i = 0; i < node->mNumChildren; i++) {
processNode(node->mChildren[i], scene);
}
}
ModelMesh Model::processMesh(aiMesh * mesh, const aiScene * scene)
{
ModelMesh::Vertices vertices;
ModelMesh::Indices indices;
ModelMesh::Textures textures;
// For each vertex in the aiMesh
for (GLuint i = 0; i < mesh->mNumVertices; i++) {
// Create a local vertex object for positions, normals, and texture coords
ModelVertex vertex;
// Reuse this vector to initialize positions and normals
QVector3D vector3D;
// Initialize vertex position
vector3D.setX(mesh->mVertices[i].x);
vector3D.setY(mesh->mVertices[i].y);
vector3D.setZ(mesh->mVertices[i].z);
// Set the position of our local vertex to the local vector object
vertex.mPosition = vector3D;
if (mesh->HasNormals()) {
// Initialize vertex normal
vector3D.setX(mesh->mNormals[i].x);
vector3D.setY(mesh->mNormals[i].y);
vector3D.setZ(mesh->mNormals[i].z);
// Set the normals of our local vertex to the local vector object
vertex.mNormal = vector3D;
}
// Initialize texture coordinates, if any are available
if (mesh->mTextureCoords[0]) {
QVector2D vector2D;
// Texture coordinates
vector2D.setX(mesh->mTextureCoords[0][i].x);
vector2D.setY(mesh->mTextureCoords[0][i].y);
vertex.mTextureCoord = vector2D;
// Tangents
vector3D.setX(mesh->mTangents[i].x);
vector3D.setY(mesh->mTangents[i].y);
vector3D.setZ(mesh->mTangents[i].z);
vertex.mTangent = vector3D;
// Bitangents
vector3D.setX(mesh->mBitangents[i].x);
vector3D.setY(mesh->mBitangents[i].y);
vector3D.setZ(mesh->mBitangents[i].z);
vertex.mBitangent = vector3D;
}
else {
vertex.mTextureCoord = {0.0f, 0.0f};
}
// Add the initialized vertex to our container of vertices
vertices.push_back(vertex);
}
// For each face on the mesh, process its indices
for (GLuint i = 0; i < mesh->mNumFaces; i++) {
aiFace face = mesh->mFaces[i];
for (GLuint j = 0; j < face.mNumIndices; j++) {
// Add the index to out container of indices
indices.push_back(face.mIndices[j]);
}
}
// Process material
if (mesh->mMaterialIndex >= 0) {
// Get the material attached to the model using Assimp
aiMaterial * material = scene->mMaterials[mesh->mMaterialIndex];
// Get all diffuse textures from the material
ModelMesh::Textures diffuseMaps =
loadMaterialTextures(material, aiTextureType_DIFFUSE,
"texture_diffuse");
// Insert all diffuse textures found into our textures container
textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
// Get all specular textures from the material
ModelMesh::Textures specularMaps =
loadMaterialTextures(material, aiTextureType_SPECULAR,
"texture_specular");
// Insert all specular textures found into our textures container
textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
// Get all normal textures from the material
ModelMesh::Textures normalMaps =
loadMaterialTextures(material, aiTextureType_HEIGHT,
"texture_normal");
// Insert all normal maps found into our textures container
textures.insert(textures.end(), normalMaps.begin(), normalMaps.end());
}
return ModelMesh(vertices, indices, textures,
mVertexShader, mFragmentShader);
}
ModelMesh::Textures Model::loadMaterialTextures(
aiMaterial * mat, aiTextureType type, const std::string & typeName)
{
ModelMesh::Textures textures;
for (GLuint i = 0; i < mat->GetTextureCount(type); i++) {
// Call GetTexture to get the name of the texture file to load
aiString fileName;
mat->GetTexture(type, i, &fileName);
// Check if we have already loaded this texture
bool skip = false;
for (GLuint j = 0; j < mTexturesLoaded.size(); j++) {
// If the path to the texture already exists in m_texturesLoaded, skip it
if (std::strcmp(mTexturesLoaded[j].mPath.data(), fileName.C_Str()) == 0) {
textures.push_back(mTexturesLoaded[j]);
// If we have loaded the texture, do not load it again
skip = true;
break;
}
}
// If the texture has not yet been loaded
if (!skip) {
ModelTexture texture;
texture.mTexture = Texture::initTexture2D(
std::string(mDirectory + '/' + fileName.C_Str()).c_str(),
false, false);
texture.mID = texture.mTexture->textureId();
texture.mType = typeName;
texture.mPath = fileName.C_Str();
// Add the texture to the textures container
textures.push_back(texture);
// Add the texture to the loaded textures to avoid loading it twice
mTexturesLoaded.push_back(texture);
}
}
// Return the resulting textures
return textures;
}
void Model::sortModels()
{
auto cameraPos = Scene::Camera().transform();
auto cameraDistance = [&cameraPos](const ModelMesh &a, const ModelMesh &b)
{
// Sort by the first vertex position, since all transforms will be the same
return (cameraPos.translation().distanceToPoint(a.mVertices[0].mPosition))
< (cameraPos.translation().distanceToPoint(b.mVertices[0].mPosition));
};
std::sort(mMeshes.begin(), mMeshes.end(), cameraDistance);
}

139
src/model.h Normal file
View File

@@ -0,0 +1,139 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Model classes for importing with Assimp ##
## From following tutorials on learnopengl.com ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTK_MODEL_H
#define QTK_MODEL_H
// QT
#include <QObject>
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
// Assimp
#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h>
// QTK
#include <transform3D.h>
struct ModelVertex {
QVector3D mPosition;
QVector3D mNormal;
QVector3D mTangent;
QVector3D mBitangent;
QVector2D mTextureCoord;
};
struct ModelTexture {
GLuint mID;
QOpenGLTexture * mTexture;
std::string mType;
std::string mPath;
};
class Model;
class ModelMesh {
public:
friend Model;
typedef std::vector<ModelVertex> Vertices;
typedef std::vector<GLuint> Indices;
typedef std::vector<ModelTexture> Textures;
// Constructors, Destructors
ModelMesh(Vertices vertices, Indices indices, Textures textures,
const char * vertexShader=":/model-basic.vert",
const char * fragmentShader=":/model-basic.frag")
: mProgram(new QOpenGLShaderProgram),
mVAO(new QOpenGLVertexArrayObject),
mVBO(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)),
mEBO(new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer)),
mVertices(std::move(vertices)),
mIndices(std::move(indices)),
mTextures(std::move(textures))
{ initMesh(vertexShader, fragmentShader);}
~ModelMesh() {}
private:
void initMesh(const char * vert, const char * frag);
// ModelMesh Private Members
QOpenGLBuffer * mVBO, * mEBO;
QOpenGLVertexArrayObject * mVAO;
QOpenGLShaderProgram * mProgram;
public:
inline void draw() { draw(*mProgram);}
void draw(QOpenGLShaderProgram & shader);
// ModelMesh Public Members
Vertices mVertices;
Indices mIndices;
Textures mTextures;
Transform3D mTransform;
};
class Model : public QObject {
Q_OBJECT
public:
inline Model(const char * name, const char * path,
const char * vertexShader=":/model-basic.vert",
const char * fragmentShader=":/model-basic.frag")
: mName(name), mVertexShader(vertexShader),
mFragmentShader(fragmentShader)
{ loadModel(path);}
inline ~Model() { mManager.remove(mName);}
void draw();
void draw(QOpenGLShaderProgram & shader);
void flipTexture(const std::string & fileName,
bool flipX=false, bool flipY=true);
template <typename T>
void setUniform(const char * location, T value)
{
for (auto & mesh : mMeshes) {
mesh.mProgram->bind();
mesh.mProgram->setUniformValue(location, value);
mesh.mProgram->release();
}
}
Transform3D mTransform;
static Model * getInstance(const char * name);
typedef QHash<QString, Model *> ModelManager;
private:
static ModelManager mManager;
void loadModel(const std::string & path);
void processNode(aiNode * node, const aiScene * scene);
ModelMesh processMesh(aiMesh * mesh, const aiScene * scene);
ModelMesh::Textures loadMaterialTextures(aiMaterial * mat, aiTextureType type,
const std::string & typeName);
void sortModels();
// Model Private Members
ModelMesh::Textures mTexturesLoaded;
std::vector<ModelMesh> mMeshes;
std::string mDirectory;
const char * mVertexShader, * mFragmentShader, * mName;
};
#endif // QTK_MODEL_H

9
src/object.cpp Normal file
View File

@@ -0,0 +1,9 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Object class for storing object data ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <object.h>

61
src/object.h Normal file
View File

@@ -0,0 +1,61 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Object class for storing object data ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTK_OBJECT_H
#define QTK_OBJECT_H
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <mesh.h>
class Object : public QObject {
Q_OBJECT
public:
friend MeshRenderer;
// Initialize an object with no shape data assigned
Object(const char * name)
: mName(name), mVBO(QOpenGLBuffer::VertexBuffer)
{ }
// Initialize an object with shape data assigned
Object(const char * name, const ShapeBase & shape)
: mName(name), mVBO(QOpenGLBuffer::VertexBuffer), mShape(shape)
{ }
~Object() {}
inline const Vertices & vertices() { return mShape.mVertices;}
inline const Indices & indices() { return mShape.mIndices;}
inline const Colors & colors() { return mShape.mColors;}
inline const TexCoords & texCoords() { return mShape.mTexCoords;}
inline const Normals & normals() { return mShape.mNormals;}
inline QOpenGLTexture & texture() const { return *mTexture;}
virtual inline void setVertices(const Vertices & value) { mShape.mVertices = value;}
virtual inline void setIndices(const Indices & value) { mShape.mIndices = value;}
virtual inline void setColors(const Colors & value) { mShape.mColors = value;}
virtual inline void setTexCoords(const TexCoords & value) { mShape.mTexCoords = value;}
virtual inline void setNormals(const Normals & value) { mShape.mNormals = value;}
virtual inline void setShape(const Shape & value) { mShape = value;}
QOpenGLBuffer mVBO, mNBO;
QOpenGLVertexArrayObject mVAO;
QOpenGLShaderProgram mProgram;
Transform3D mTransform;
Shape mShape;
const char * mName;
private:
QOpenGLTexture * mTexture;
};
#endif // QTK_OBJECT_H

759
src/scene.cpp Normal file
View File

@@ -0,0 +1,759 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Classes for managing objects and data within a scene ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <camera3d.h>
#include <texture.h>
#include <meshrenderer.h>
#include <model.h>
#include <scene.h>
Camera3D Scene::mCamera;
QMatrix4x4 Scene::mProjection;
/*******************************************************************************
* Constructors, Destructors
******************************************************************************/
Scene::Scene()
{
init();
}
Scene::~Scene()
{
delete mTestPhong;
delete mTestSpecular;
delete mTestDiffuse;
delete mTestAmbient;
for (auto & mesh : mMeshes) delete mesh;
for (auto & model : mModels) delete model;
}
/*******************************************************************************
* Public Member Functions
******************************************************************************/
void Scene::init()
{
mCamera.transform().setTranslation(0.0f, 0.0f, 5.0f);
mCamera.transform().setRotation(180.0f, 0.0f, 1.0f, 0.0f);
// Initialize Phong example cube
mTestPhong = new MeshRenderer("phong", Cube());
mTestPhong->mTransform.setTranslation(3.0f, 0.0f, -2.0f);
mTestPhong->setShaders(":/solid-phong.vert", ":/solid-phong.frag");
mTestPhong->init();
mTestPhong->mProgram.bind();
mTestPhong->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f));
mTestPhong->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f));
mTestPhong->setUniform("uAmbientStrength", 0.2f);
mTestPhong->setUniform("uSpecularStrength", 0.50f);
mTestPhong->setUniform("uSpecularShine", 256);
mTestPhong->mVAO.bind();
mTestPhong->mNBO.create();
mTestPhong->mNBO.setUsagePattern(QOpenGLBuffer::StaticDraw);
mTestPhong->mNBO.bind();
mTestPhong->mNBO.allocate(mTestPhong->normals().data(),
mTestPhong->normals().size()
* sizeof(mTestPhong->normals()[0]));
mTestPhong->mProgram.enableAttributeArray(1);
mTestPhong->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mTestPhong->mNBO.release();
mTestPhong->mVAO.release();
mTestPhong->mProgram.release();
// Initialize Ambient example cube
mTestAmbient = new MeshRenderer("ambient", Cube());
mTestAmbient->mTransform.setTranslation(7.0f, 0.0f, -2.0f);
mTestAmbient->setShaders(":/solid-ambient.vert", ":/solid-ambient.frag");
mTestAmbient->init();
mTestAmbient->mProgram.bind();
mTestAmbient->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f));
mTestAmbient->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f));
mTestAmbient->setUniform("uAmbientStrength", 0.2f);
mTestAmbient->mVAO.bind();
mTestAmbient->mNBO.create();
mTestAmbient->mNBO.setUsagePattern(QOpenGLBuffer::StaticDraw);
mTestAmbient->mNBO.bind();
mTestAmbient->mNBO.allocate(mTestAmbient->normals().data(),
mTestAmbient->normals().size()
* sizeof(mTestAmbient->normals()[0]));
mTestAmbient->mProgram.enableAttributeArray(1);
mTestAmbient->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mTestAmbient->mNBO.release();
mTestAmbient->mVAO.release();
mTestAmbient->mProgram.release();
// Initialize Diffuse example cube
mTestDiffuse = new MeshRenderer("diffuse", Cube());
mTestDiffuse->mTransform.setTranslation(9.0f, 0.0f, -2.0f);
mTestDiffuse->setShaders(":/solid-diffuse.vert", ":/solid-diffuse.frag");
mTestDiffuse->init();
mTestDiffuse->mProgram.bind();
mTestDiffuse->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f));
mTestDiffuse->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f));
mTestDiffuse->setUniform("uAmbientStrength", 0.2f);
mTestDiffuse->mVAO.bind();
mTestDiffuse->mNBO.create();
mTestDiffuse->mNBO.setUsagePattern(QOpenGLBuffer::StaticDraw);
mTestDiffuse->mNBO.bind();
mTestDiffuse->mNBO.allocate(mTestDiffuse->normals().data(),
mTestDiffuse->normals().size()
* sizeof(mTestDiffuse->normals()[0]));
mTestDiffuse->mProgram.enableAttributeArray(1);
mTestDiffuse->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mTestDiffuse->mNBO.release();
mTestDiffuse->mVAO.release();
mTestDiffuse->mProgram.release();
// Initialize Specular example cube
mTestSpecular = new MeshRenderer("specular", Cube());
mTestSpecular->mTransform.setTranslation(11.0f, 0.0f, -2.0f);
mTestSpecular->setShaders(":/solid-specular.vert", ":/solid-specular.frag");
mTestSpecular->init();
mTestSpecular->mProgram.bind();
mTestSpecular->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f));
mTestSpecular->setUniform("uLightColor", QVector3D(1.0f, 1.0f, 1.0f));
mTestSpecular->setUniform("uAmbientStrength", 0.2f);
mTestSpecular->setUniform("uSpecularStrength", 0.50f);
mTestSpecular->setUniform("uSpecularShine", 256);
mTestSpecular->mVAO.bind();
mTestSpecular->mNBO.create();
mTestSpecular->mNBO.setUsagePattern(QOpenGLBuffer::StaticDraw);
mTestSpecular->mNBO.bind();
mTestSpecular->mNBO.allocate(mTestSpecular->normals().data(),
mTestSpecular->normals().size()
* sizeof(mTestSpecular->normals()[0]));
mTestSpecular->mProgram.enableAttributeArray(1);
mTestSpecular->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mTestSpecular->mNBO.release();
mTestSpecular->mVAO.release();
mTestSpecular->mProgram.release();
//
// Model loading
mModels.push_back(new Model("backpack",
"../resources/models/backpack/backpack.obj"));
// Sometimes model textures need flipped in certain directions
mModels.back()->flipTexture("diffuse.jpg", false, true);
mModels.back()->mTransform.setTranslation(0.0f, 0.0f, -10.0f);
mModels.push_back(
new Model("bird",
"../resources/models/bird/bird.obj"));
mModels.back()->mTransform.setTranslation(2.0f, 2.0f, -10.0f);
// Sometimes the models are very large
mModels.back()->mTransform.scale(0.0025f);
mModels.back()->mTransform.rotate(-110.0f, 0.0f, 1.0f, 0.0f);
mModels.push_back(new Model("lion",
"../resources/models/lion/lion.obj"));
mModels.back()->mTransform.setTranslation(-3.0f, -1.0f, -10.0f);
mModels.back()->mTransform.scale(0.15f);
mModels.push_back(
new Model("alien",
"../resources/models/alien-hominid/alien.obj"));
mModels.back()->mTransform.setTranslation(2.0f, -1.0f, -5.0f);
mModels.back()->mTransform.scale(0.15f);
mModels.push_back(
new Model("scythe",
"../resources/models/scythe/scythe.obj")
);
mModels.back()->mTransform.setTranslation(-6.0f, 0.0f, -10.0f);
mModels.back()->mTransform.rotate(-90.0f, 1.0f, 0.0f, 0.0f);
mModels.back()->mTransform.rotate(90.0f, 0.0f, 1.0f, 0.0f);
mModels.push_back(
new Model("masterChief",
"../resources/models/spartan/spartan.obj"));
mModels.back()->mTransform.setTranslation(-1.5f, 0.5f, -2.0f);
//
// Building example mesh objects
// Render an alien with specular
// Test alien Model with phong lighting and specular mapping
mMeshes.push_back(
new MeshRenderer("alienTestLight", Triangle(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(4.0f, 1.5f, 10.0f);
mMeshes.back()->mTransform.scale(0.25f);
// This function changes values we have allocated in a buffer, so init() after
mMeshes.back()->setColor(GREEN);
mMeshes.back()->init();
mModels.push_back(
new Model("alienTest",
"../resources/models/alien-hominid/alien.obj",
":/model-specular.vert", ":/model-specular.frag"));
mModels.back()->mTransform.setTranslation(3.0f, -1.0f, 10.0f);
mModels.back()->mTransform.scale(0.15f);
mModels.back()->setUniform("uMaterial.ambient", QVector3D(1.0f, 1.0f, 1.0f));
mModels.back()->setUniform("uMaterial.diffuse", QVector3D(1.0f, 1.0f, 1.0f));
mModels.back()->setUniform("uMaterial.specular", QVector3D(1.0f, 1.0f, 1.0f));
mModels.back()->setUniform("uMaterial.ambientStrength", 0.8f);
mModels.back()->setUniform("uMaterial.diffuseStrength", 0.8f);
mModels.back()->setUniform("uMaterial.specularStrength", 1.0f);
mModels.back()->setUniform("uMaterial.shine", 32.0f);
mModels.back()->setUniform("uLight.ambient", QVector3D(1.0f, 1.0f, 1.0f));
mModels.back()->setUniform("uLight.diffuse", QVector3D(1.0f, 1.0f, 1.0f));
mModels.back()->setUniform("uLight.specular", QVector3D(1.0f, 1.0f, 1.0f));
// Test spartan Model with phong lighting, specular and normal mapping
mMeshes.push_back(
new MeshRenderer("spartanTestLight", Triangle(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(1.0f, 1.5f, 10.0f);
mMeshes.back()->mTransform.scale(0.25f);
// This function changes values we have allocated in a buffer, so init() after
mMeshes.back()->setColor(GREEN);
mMeshes.back()->init();
mModels.push_back(
new Model("spartanTest",
"../resources/models/spartan/spartan.obj",
":/model-normals.vert", ":/model-normals.frag"));
mModels.back()->mTransform.setTranslation(0.0f, -1.0f, 10.0f);
mModels.back()->mTransform.scale(2.0f);
mModels.back()->setUniform("uMaterial.ambient", QVector3D(1.0f, 1.0f, 1.0f));
mModels.back()->setUniform("uMaterial.diffuse", QVector3D(1.0f, 1.0f, 1.0f));
mModels.back()->setUniform("uMaterial.specular", QVector3D(1.0f, 1.0f, 1.0f));
mModels.back()->setUniform("uMaterial.ambientStrength", 1.0f);
mModels.back()->setUniform("uMaterial.diffuseStrength", 1.0f);
mModels.back()->setUniform("uMaterial.specularStrength", 1.0f);
mModels.back()->setUniform("uMaterial.shine", 128.0f);
mModels.back()->setUniform("uLight.ambient", QVector3D(1.0f, 1.0f, 1.0f));
mModels.back()->setUniform("uLight.diffuse", QVector3D(1.0f, 1.0f, 1.0f));
mModels.back()->setUniform("uLight.specular", QVector3D(1.0f, 1.0f, 1.0f));
// Test basic cube with phong.vert and phong.frag shaders
mMeshes.push_back(
new MeshRenderer("testLight", Triangle(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(5.0f, 1.25f, 10.0f);
mMeshes.back()->mTransform.scale(0.25f);
mMeshes.back()->setDrawType(GL_LINE_LOOP);
// This function changes values we have allocated in a buffer, so init() after
mMeshes.back()->setColor(GREEN);
mMeshes.back()->init();
mMeshes.push_back(
new MeshRenderer("testPhong", Cube(QTK_DRAW_ARRAYS)));
mMeshes.back()->mTransform.setTranslation(5.0f, 0.0f, 10.0f);
mMeshes.back()->setShaders(":/phong.vert", ":/phong.frag");
mMeshes.back()->setColor(QVector3D(0.0f, 0.25f, 0.0f));
mMeshes.back()->init();
mMeshes.back()->mProgram.bind();
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->normals().data(),
mMeshes.back()->normals().size()
* sizeof(mMeshes.back()->normals()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
mMeshes.back()->setUniform("uMaterial.ambient", QVector3D(0.0f, 0.3f, 0.0f));
mMeshes.back()->setUniform("uMaterial.diffuse", QVector3D(0.0f, 0.2f, 0.0f));
mMeshes.back()->setUniform("uMaterial.specular", QVector3D(1.0f, 1.0f, 1.0f));
mMeshes.back()->setUniform("uMaterial.ambientStrength", 1.0f);
mMeshes.back()->setUniform("uMaterial.diffuseStrength", 1.0f);
mMeshes.back()->setUniform("uMaterial.specularStrength", 1.0f);
mMeshes.back()->setUniform("uMaterial.shine", 64.0f);
mMeshes.back()->setUniform("uLight.ambient", QVector3D(0.25f, 0.2f, 0.075f));
mMeshes.back()->setUniform("uLight.diffuse", QVector3D(0.75f, 0.6f, 0.22f));
mMeshes.back()->setUniform("uLight.specular", QVector3D(0.62f, 0.55f, 0.37f));
mMeshes.back()->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f));
mMeshes.back()->mProgram.release();
//
// Create simple shapes using MeshRenderer class and data in mesh.h
mMeshes.push_back(
new MeshRenderer("rightTriangle", Triangle(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(-5.0f, 0.0f, -2.0f);
mMeshes.push_back(
new MeshRenderer("centerCube", Cube(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(-7.0f, 0.0f, -2.0f);
mMeshes.push_back(
new MeshRenderer("leftTriangle", Triangle(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(-9.0f, 0.0f, -2.0f);
mMeshes.back()->setDrawType(GL_LINE_LOOP);
mMeshes.push_back(
new MeshRenderer("topTriangle", Triangle(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(-7.0f, 2.0f, -2.0f);
mMeshes.back()->mTransform.scale(0.25f);
mMeshes.push_back(
new MeshRenderer("bottomTriangle", Triangle(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(-7.0f, -2.0f, -2.0f);
mMeshes.back()->mTransform.scale(0.25f);
mMeshes.back()->setDrawType(GL_LINE_LOOP);
// This function changes values we have allocated in a buffer, so init() after
mMeshes.back()->setColor(GREEN);
mMeshes.back()->init();
//
// Testing for normals, texture coordinates
// RGB Normals cube to show normals are correct with QTK_DRAW_ARRAYS
mMeshes.push_back(
new MeshRenderer("rgbNormalsCubeArraysTest", Cube(QTK_DRAW_ARRAYS)));
mMeshes.back()->mTransform.setTranslation(5.0f, 0.0f, 4.0f);
mMeshes.back()->setShaders(":/rgb-normals.vert", ":/rgb-normals.frag");
mMeshes.back()->init();
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mProgram.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->normals().data(),
mMeshes.back()->normals().size()
* sizeof(mMeshes.back()->normals()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mMeshes.back()->mProgram.release();
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
// RGB Normals cube to show normals are correct with QTK_DRAW_ELEMENTS_NORMALS
mMeshes.push_back(
new MeshRenderer("rgbNormalsCubeElementsTest", Cube(QTK_DRAW_ELEMENTS_NORMALS)));
mMeshes.back()->mTransform.setTranslation(5.0f, 0.0f, 2.0f);
mMeshes.back()->setShaders(":/rgb-normals.vert", ":/rgb-normals.frag");
mMeshes.back()->init();
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mProgram.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->normals().data(),
mMeshes.back()->normals().size()
* sizeof(mMeshes.back()->normals()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mMeshes.back()->mProgram.release();
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
// Texturing a cube using texture coordinates and glDrawArrays
// + Texturing with UVs using glDrawElements requires QTK_DRAW_ELEMENTS_NORMALS
// + UVs required duplicating element position data from QTK_DRAW_ELEMENTS
// + This is because the same position must use different UV coordinates
mMeshes.push_back(
new MeshRenderer("uvCubeArraysTest", Cube(QTK_DRAW_ARRAYS)));
mMeshes.back()->mTransform.setTranslation(-3.0f, 0.0f, -2.0f);
mMeshes.back()->setShaders(":/texture2d.vert", ":/texture2d.frag");
mMeshes.back()->init();
mMeshes.back()->mProgram.bind();
mMeshes.back()->setTexture(Texture::initTexture2D(":/crate.png"));
mMeshes.back()->setUniform("uTexture", 0);
mMeshes.back()->texture().bind();
mMeshes.back()->texture().release();
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.destroy();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->mShape.texCoords().data(),
mMeshes.back()->mShape.texCoords().size()
* sizeof(mMeshes.back()->mShape.texCoords()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
2, sizeof(QVector2D));
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
mMeshes.back()->mProgram.release();
// Test drawing a cube with texture coordinates using glDrawElements
mMeshes.push_back(
new MeshRenderer("uvCubeElementsTest", Cube(QTK_DRAW_ELEMENTS_NORMALS)));
mMeshes.back()->mTransform.setTranslation(-1.7f, 0.0f, -2.0f);
mMeshes.back()->setShaders(":/texture2d.vert", ":/texture2d.frag");
mMeshes.back()->init();
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mProgram.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->texCoords().data(),
mMeshes.back()->texCoords().size()
* sizeof(mMeshes.back()->texCoords()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mMeshes.back()->setTexture(Texture::initTexture2D(":/crate.png"));
mMeshes.back()->mProgram.setUniformValue("uTexture", 0);
mMeshes.back()->mProgram.release();
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
mMeshes.back()->mTransform.rotate(45.0f, 0.0f, 1.0f, 0.0f);
// Texturing a cube using a cube map
// + Cube map texturing works with both QTK_DRAW_ARRAYS and QTK_DRAW_ELEMENTS
mMeshes.push_back(
new MeshRenderer("testCubeMap", Cube(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(-3.0f, 1.0f, -2.0f);
mMeshes.back()->mTransform.setRotation(45.0f, 0.0f, 1.0f, 0.0f);
mMeshes.back()->setShaders(":/texture-cubemap.vert", ":/texture-cubemap.frag");
mMeshes.back()->init();
mMeshes.back()->mProgram.bind();
mMeshes.back()->setTexture(Texture::initCubeMap(":/crate.png"));
mMeshes.back()->setUniform("uTexture", 0);
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.destroy();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->mShape.texCoords().data(),
mMeshes.back()->mShape.texCoords().size()
* sizeof(mMeshes.back()->mShape.texCoords()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
2, sizeof(QVector2D));
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
mMeshes.back()->mProgram.release();
// Create a cube with custom shaders
// + Apply RGB normals shader and spin the cube for a neat effect
mMeshes.push_back(
new MeshRenderer("rgbNormalsCube", Cube(QTK_DRAW_ARRAYS)));
mMeshes.back()->mTransform.setTranslation(5.0f, 2.0f, -2.0f);
mMeshes.back()->setShaders(":/rgb-normals.vert", ":/rgb-normals.frag");
mMeshes.back()->init();
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mProgram.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->normals().data(),
mMeshes.back()->normals().size()
* sizeof(mMeshes.back()->normals()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mMeshes.back()->mProgram.release();
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
// RGB Normals triangle to show normals are correct with QTK_DRAW_ARRAYS
mMeshes.push_back(
new MeshRenderer("rgbTriangleArraysTest", Triangle(QTK_DRAW_ARRAYS)));
mMeshes.back()->mTransform.setTranslation(7.0f, 0.0f, 2.0f);
mMeshes.back()->setShaders(":/rgb-normals.vert", ":/rgb-normals.frag");
mMeshes.back()->init();
mMeshes.back()->mProgram.bind();
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->normals().data(),
mMeshes.back()->normals().size()
* sizeof(mMeshes.back()->normals()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
mMeshes.back()->mProgram.release();
// RGB Normals triangle to show normals are correct with QTK_DRAW_ELEMENTS
mMeshes.push_back(
new MeshRenderer("rgbTriangleElementsTest",
Triangle(QTK_DRAW_ELEMENTS_NORMALS)));
mMeshes.back()->mTransform.setTranslation(7.0f, 0.0f, 4.0f);
mMeshes.back()->setShaders(":/rgb-normals.vert", ":/rgb-normals.frag");
mMeshes.back()->init();
mMeshes.back()->mProgram.bind();
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->normals().data(),
mMeshes.back()->normals().size()
* sizeof(mMeshes.back()->normals()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
3, sizeof(QVector3D));
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
mMeshes.back()->mProgram.release();
// Test drawing triangle with glDrawArrays with texture coordinates
mMeshes.push_back(
new MeshRenderer("testTriangleArraysUV", Triangle(QTK_DRAW_ARRAYS)));
mMeshes.back()->mTransform.setTranslation(-3.0f, 2.0f, -2.0f);
mMeshes.back()->setShaders(":/texture2d.vert", ":/texture2d.frag");
mMeshes.back()->init();
mMeshes.back()->mProgram.bind();
mMeshes.back()->setTexture(Texture::initTexture2D(":/crate.png"));
mMeshes.back()->setUniform("uTexture", 0);
mMeshes.back()->texture().bind();
mMeshes.back()->texture().release();
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.destroy();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->mShape.texCoords().data(),
mMeshes.back()->mShape.texCoords().size()
* sizeof(mMeshes.back()->mShape.texCoords()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
2, sizeof(QVector2D));
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
mMeshes.back()->mProgram.release();
// Test drawing triangle with glDrawElements with texture coordinates
mMeshes.push_back(
new MeshRenderer("testTriangleElementsUV",
Triangle(QTK_DRAW_ELEMENTS_NORMALS)));
mMeshes.back()->mTransform.setTranslation(-2.5f, 0.0f, -1.0f);
mMeshes.back()->setShaders(":/texture2d.vert", ":/texture2d.frag");
mMeshes.back()->init();
mMeshes.back()->mProgram.bind();
mMeshes.back()->setTexture(Texture::initTexture2D(":/crate.png"));
mMeshes.back()->setUniform("uTexture", 0);
mMeshes.back()->texture().bind();
mMeshes.back()->texture().release();
mMeshes.back()->mVAO.bind();
mMeshes.back()->mNBO.destroy();
mMeshes.back()->mNBO.create();
mMeshes.back()->mNBO.bind();
mMeshes.back()->mNBO.allocate(mMeshes.back()->mShape.texCoords().data(),
mMeshes.back()->mShape.texCoords().size()
* sizeof(mMeshes.back()->mShape.texCoords()[0]));
mMeshes.back()->mProgram.enableAttributeArray(1);
mMeshes.back()->mProgram.setAttributeBuffer(1, GL_FLOAT, 0,
2, sizeof(QVector2D));
mMeshes.back()->mNBO.release();
mMeshes.back()->mVAO.release();
mMeshes.back()->mProgram.release();
//
// Lighting cube examples
// Example of a cube with no lighting applied
mMeshes.push_back(
new MeshRenderer("noLight", Cube(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(5.0f, 0.0f, -2.0f);
mMeshes.back()->setShaders(":/solid-perspective.vert",
":/solid-perspective.frag");
mMeshes.back()->init();
mMeshes.back()->mProgram.bind();
mMeshes.back()->setUniform("uColor", QVector3D(0.0f, 0.25f, 0.0f));
mMeshes.back()->mProgram.release();
// Create objects that represent light sources for lighting examples
mMeshes.push_back(
new MeshRenderer("phongLight", Triangle(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(3.0f, 2.0f, -2.0f);
mMeshes.back()->mTransform.scale(0.25f);
mMeshes.push_back(
new MeshRenderer("diffuseLight", Triangle(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(9.0f, 2.0f, -2.0f);
mMeshes.back()->mTransform.scale(0.25f);
mMeshes.push_back(
new MeshRenderer("specularLight", Triangle(QTK_DRAW_ELEMENTS)));
mMeshes.back()->mTransform.setTranslation(11.0f, 2.0f, -2.0f);
mMeshes.back()->mTransform.scale(0.25f);
}
void Scene::draw()
{
mSkybox.draw();
for (auto & model : mModels) model->draw();
for (const auto &mesh : mMeshes) mesh->draw();
mTestPhong->mProgram.bind();
mTestPhong->setUniform("uModelInverseTransposed",
mTestPhong->mTransform.toMatrix().normalMatrix());
mTestPhong->setUniform(
"uLightPosition",
MeshRenderer::getInstance("phongLight")->mTransform.translation());
mTestPhong->setUniform("uCameraPosition",
Scene::Camera().transform().translation());
mTestPhong->mProgram.release();
mTestPhong->draw();
mTestAmbient->mProgram.bind();
mTestAmbient->setUniform("uCameraPosition",
Scene::Camera().transform().translation());
mTestAmbient->mProgram.release();
mTestAmbient->draw();
mTestDiffuse->mProgram.bind();
mTestDiffuse->setUniform("uModelInverseTransposed",
mTestDiffuse->mTransform.toMatrix().normalMatrix());
mTestDiffuse->setUniform(
"uLightPosition",
MeshRenderer::getInstance("diffuseLight")->mTransform.translation());
mTestDiffuse->setUniform("uCameraPosition", Scene::Camera().transform().translation());
mTestDiffuse->mProgram.release();
mTestDiffuse->draw();
mTestSpecular->mProgram.bind();
mTestSpecular->setUniform(
"uModelInverseTransposed",
mTestSpecular->mTransform.toMatrix().normalMatrix());
mTestSpecular->setUniform(
"uLightPosition",
MeshRenderer::getInstance("specularLight")->mTransform.translation());
mTestSpecular->setUniform("uCameraPosition", Scene::Camera().transform().translation());
mTestSpecular->mProgram.release();
mTestSpecular->draw();
}
void Scene::update()
{
auto position = MeshRenderer::getInstance("alienTestLight")->mTransform.translation();
Model::getInstance("alienTest")->setUniform(
"uLight.position", position);
Model::getInstance("alienTest")->setUniform(
"uCameraPosition", Scene::Camera().transform().translation());
auto posMatrix = Model::getInstance("alienTest")->mTransform.toMatrix();
Model::getInstance("alienTest")->setUniform(
"uMVP.normalMatrix", posMatrix.normalMatrix());
Model::getInstance("alienTest")->setUniform(
"uMVP.model", posMatrix);
Model::getInstance("alienTest")->setUniform(
"uMVP.view", Scene::Camera().toMatrix());
Model::getInstance("alienTest")->setUniform(
"uMVP.projection", Scene::Projection());
Model::getInstance("alienTest")->mTransform.rotate(0.75f, 0.0f, 1.0f, 0.0f);
position = MeshRenderer::getInstance("spartanTestLight")->mTransform.translation();
Model::getInstance("spartanTest")->setUniform(
"uLight.position", position);
Model::getInstance("spartanTest")->setUniform(
"uCameraPosition", Scene::Camera().transform().translation());
posMatrix = Model::getInstance("spartanTest")->mTransform.toMatrix();
Model::getInstance("spartanTest")->setUniform(
"uMVP.normalMatrix", posMatrix.normalMatrix());
Model::getInstance("spartanTest")->setUniform(
"uMVP.model", posMatrix);
Model::getInstance("spartanTest")->setUniform(
"uMVP.view", Scene::Camera().toMatrix());
Model::getInstance("spartanTest")->setUniform(
"uMVP.projection", Scene::Projection());
Model::getInstance("spartanTest")->mTransform.rotate(0.75f, 0.0f, 1.0f, 0.0f);
MeshRenderer::getInstance("testPhong")->mTransform.rotate(
0.75f, 1.0f, 0.5f, 0.0f);
MeshRenderer::getInstance("testPhong")->mProgram.bind();
position = MeshRenderer::getInstance("testLight")->mTransform.translation();
MeshRenderer::getInstance("testPhong")->setUniform(
"uLight.position", position);
MeshRenderer::getInstance("testPhong")->setUniform(
"uCameraPosition", Scene::Camera().transform().translation());
posMatrix = MeshRenderer::getInstance("testPhong")->mTransform.toMatrix();
MeshRenderer::getInstance("testPhong")->setUniform(
"uMVP.normalMatrix", posMatrix.normalMatrix());
MeshRenderer::getInstance("testPhong")->setUniform(
"uMVP.model", posMatrix);
MeshRenderer::getInstance("testPhong")->setUniform(
"uMVP.view", Scene::Camera().toMatrix());
MeshRenderer::getInstance("testPhong")->setUniform(
"uMVP.projection", Scene::Projection());
MeshRenderer::getInstance("testPhong")->mProgram.release();
// Rotate lighting example cubes
mTestPhong->mTransform.rotate(0.75f, 0.5f, 0.3f, 0.2f);
MeshRenderer::getInstance("noLight")->mTransform.rotate(
0.75f, 0.5f, 0.3f, 0.2f);
mTestAmbient->mTransform.rotate(0.75f, 0.5f, 0.3f, 0.2f);
mTestDiffuse->mTransform.rotate(0.75f, 0.5f, 0.3f, 0.2f);
mTestSpecular->mTransform.rotate(0.75f, 0.5f, 0.3f, 0.2f);
// Examples of various translations and rotations
// Rotate in multiple directions simultaneously
MeshRenderer::getInstance("rgbNormalsCube")->mTransform.rotate(
0.75f, 0.2f, 0.4f, 0.6f);
// Pitch forward and roll sideways
MeshRenderer::getInstance("leftTriangle")->mTransform.rotate(
0.75f, 1.0f, 0.0f, 0.0f);
MeshRenderer::getInstance("rightTriangle")->mTransform.rotate(
0.75f, 0.0f, 0.0f, 1.0f);
// Move between two positions over time
static float translateX = 0.025f;
float limit = -9.0f; // Origin position.x - 2.0f
float posX =
MeshRenderer::getInstance("topTriangle")->mTransform.translation().x();
if (posX < limit || posX > limit + 4.0f) {
translateX = -translateX;
}
MeshRenderer::getInstance("topTriangle")->mTransform.translate(
translateX, 0.0f, 0.0f);
MeshRenderer::getInstance("bottomTriangle")->mTransform.translate(
-translateX, 0.0f, 0.0f);
// And lets rotate the triangles in two directions at once
MeshRenderer::getInstance("topTriangle")->mTransform.rotate(
0.75f, 0.2f, 0.0f, 0.4f);
MeshRenderer::getInstance("bottomTriangle")->mTransform.rotate(
0.75f, 0.0f, 0.2f, 0.4f);
// And make the bottom triangle green, instead of RGB
// Rotate center cube in several directions simultaneously
// + Not subject to gimbal lock since we are using quaternions :)
MeshRenderer::getInstance("centerCube")->mTransform.rotate(
0.75f, 0.2f, 0.4f, 0.6f);
}

45
src/scene.h Normal file
View File

@@ -0,0 +1,45 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Classes for managing objects and data within a scene ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTK_SCENE_H
#define QTK_SCENE_H
#include <camera3d.h>
#include <skybox.h>
#include <QMatrix4x4>
class MeshRenderer;
class Model;
class Scene {
public:
Scene();
~Scene();
void init();
void draw();
void update();
static Camera3D & Camera() { return mCamera;}
static QMatrix4x4 View() { return mCamera.toMatrix();}
static QMatrix4x4 & Projection() { return mProjection;}
private:
static Camera3D mCamera;
static QMatrix4x4 mProjection;
Skybox mSkybox;
MeshRenderer * mTestPhong;
MeshRenderer * mTestSpecular;
MeshRenderer * mTestDiffuse;
MeshRenderer * mTestAmbient;
std::vector<MeshRenderer *> mMeshes;
std::vector<Model *> mModels;
};
#endif // QTK_SCENE_H

101
src/skybox.cpp Normal file
View File

@@ -0,0 +1,101 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Skybox class using QtOpenGL ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <scene.h>
#include <texture.h>
#include <skybox.h>
Skybox::Skybox(std::string right, std::string top, std::string front,
std::string left, std::string bottom, std::string back,
const std::string & name)
: mCubeMap(Texture::initCubeMap(
QImage(right.c_str()).mirrored(), QImage(top.c_str()),
QImage(front.c_str()), QImage(left.c_str()),
QImage(bottom.c_str()), QImage(back.c_str()))),
mVBO(QOpenGLBuffer::VertexBuffer),
mVertices(Cube(QTK_DRAW_ELEMENTS).vertices()),
mIndices(Cube(QTK_DRAW_ELEMENTS).indices())
{ init();}
Skybox::Skybox(std::string name)
: Skybox(":/right.png", ":/top.png", ":/front.png",
":/left.png", ":/bottom.png", ":/back.png",
name)
{}
Skybox::Skybox(QOpenGLTexture * cubeMap, const std::string & name)
: mCubeMap(cubeMap) { init();}
/*******************************************************************************
* Public Member Functions
******************************************************************************/
void Skybox::draw()
{
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_FALSE);
mVAO.bind();
mProgram.bind();
mCubeMap->bind();
mProgram.setUniformValue("uProjectionMatrix", Scene::Projection());
mProgram.setUniformValue("uViewMatrix", Scene::Camera().toMatrix());
mProgram.setUniformValue("uTexture", 0);
glDrawElements(GL_TRIANGLES, mIndices.size(),
GL_UNSIGNED_INT, mIndices.data());
mCubeMap->release();
mProgram.release();
mVAO.release();
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
glActiveTexture(GL_TEXTURE0);
}
/*******************************************************************************
* Private Member Functions
******************************************************************************/
void Skybox::init()
{
// Set up shader program
mProgram.create();
mProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/skybox.vert");
mProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/skybox.frag");
mProgram.link();
mProgram.bind();
// Setup VAO
mVAO.create();
mVAO.bind();
// Setup VBO for vertex position data
mVBO.create();
mVBO.setUsagePattern(QOpenGLBuffer::StaticDraw);
mVBO.bind();
// Allocate vertex positions into VBO
mVBO.allocate(mVertices.data(),
mVertices.size() * sizeof(mVertices[0]));
// Enable attribute array for vertex positions
mProgram.enableAttributeArray(0);
mProgram.setAttributeBuffer(0, GL_FLOAT, 0,
3, sizeof(QVector3D));
// Set shader texture unit to 0
mProgram.setUniformValue("uTexture", 0);
mVAO.release();
mVBO.release();
mProgram.release();
}

47
src/skybox.h Normal file
View File

@@ -0,0 +1,47 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Skybox class using QtOpenGL ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTK_SKYBOX_H
#define QTK_SKYBOX_H
#include <QImage>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <camera3d.h>
#include <mesh.h>
class Skybox {
public:
// Delegate this constructor to use default skybox images
// + This allows creating a skybox with no arguments ( auto s = new Skybox; )
explicit Skybox(std::string name="Skybox");
explicit Skybox(QOpenGLTexture * cubeMap, const std::string & name="Skybox");
// Constructor, Destructor
Skybox(std::string right, std::string top, std::string front,
std::string left, std::string bottom, std::string back,
const std::string & name="Skybox");
~Skybox() {}
void draw();
private:
void init();
Vertices mVertices;
Indices mIndices;
QOpenGLShaderProgram mProgram;
QOpenGLVertexArrayObject mVAO;
QOpenGLBuffer mVBO;
QOpenGLTexture * mCubeMap;
};
#endif // QTK_SKYBOX_H

106
src/texture.cpp Normal file
View File

@@ -0,0 +1,106 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Texture class to help with texture and image initializations ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <QDebug>
#include <QImageReader>
#include <texture.h>
QImage * Texture::initImage(const char * image, bool flipX, bool flipY)
{
// Qt6 limits loaded images to 256MB by default
QImageReader::setAllocationLimit(512);
auto loadedImage = new QImage(QImage(image).mirrored(flipX, flipY));
if (loadedImage->isNull()) {
qDebug() << "Error loading image: " << image << "\n";
qDebug() << QImageReader::supportedImageFormats();
return Q_NULLPTR;
}
return loadedImage;
}
QOpenGLTexture * Texture::initTexture2D(const char * texture,
bool flipX, bool flipY)
{
QImage * image = initImage(texture, flipX, flipY);
auto newTexture = new QOpenGLTexture(QOpenGLTexture::Target2D);
newTexture->setData(*image);
newTexture->setWrapMode(QOpenGLTexture::Repeat);
newTexture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear,
QOpenGLTexture::Linear);
delete image;
return newTexture;
}
QOpenGLTexture * Texture::initCubeMap(const char * tile)
{
return initCubeMap(QImage(tile), QImage(tile),
QImage(tile), QImage(tile),
QImage(tile), QImage(tile));
}
QOpenGLTexture * Texture::initCubeMap(
const char * right, const char * top,
const char * front, const char * left,
const char * bottom, const char * back)
{
return initCubeMap(QImage(right), QImage(top),
QImage(front), QImage(left),
QImage(bottom), QImage(back));
}
QOpenGLTexture * Texture::initCubeMap(
QImage right, QImage top,
QImage front, QImage left,
QImage bottom, QImage back)
{
auto texture = new QOpenGLTexture(QOpenGLTexture::TargetCubeMap);
std::vector<QImage> faceTextures = {
right, top, front,
left, bottom, back
};
// Initialize skybox cubemap texture
texture->create();
texture->bind();
// For each cube map face
std::vector<QOpenGLTexture::CubeMapFace> faces = {
QOpenGLTexture::CubeMapPositiveX, QOpenGLTexture::CubeMapPositiveY,
QOpenGLTexture::CubeMapPositiveZ, QOpenGLTexture::CubeMapNegativeX,
QOpenGLTexture::CubeMapNegativeY, QOpenGLTexture::CubeMapNegativeZ
};
int i = 0;
for (const auto & face : faces) {
QImage faceImage(faceTextures[i]);
if (faceImage.isNull()) {
qDebug() << "Error loading cube map image\n";
}
faceImage = faceImage.convertToFormat(QImage::Format_RGBA8888);
// On the first iteration, set format and allocate texture storage
if (face == QOpenGLTexture::CubeMapPositiveX) {
// This also needs to happen on the first iteration, anyways
texture->setSize(faceImage.width(), faceImage.height(), faceImage.depth());
texture->setFormat(QOpenGLTexture::RGBA8_UNorm);
texture->allocateStorage();
}
texture->setData(0, 0, face,
QOpenGLTexture::RGBA, QOpenGLTexture::UInt8,
faceImage.constBits());
i++;
}
texture->setWrapMode(QOpenGLTexture::ClampToEdge);
texture->generateMipMaps();
texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
texture->setMagnificationFilter(QOpenGLTexture::Linear);
texture->release();
return texture;
}

42
src/texture.h Normal file
View File

@@ -0,0 +1,42 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Texture class to help with texture and image initializations ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTOPENGL_TEXTURE_H
#define QTOPENGL_TEXTURE_H
#include <QOpenGLTexture>
class Texture {
public:
~Texture() {}
// QImage
static QImage * initImage(const char * image,
bool flipX=false, bool flipY=false);
// 2D Texture
static QOpenGLTexture * initTexture2D(const char * texture,
bool flipX=false, bool flipY=false);
// Cube maps
static QOpenGLTexture * initCubeMap(QImage right, QImage top,
QImage front, QImage left,
QImage bottom, QImage back);
// Overloads for cube map initialization
static QOpenGLTexture * initCubeMap(const char * tile);
static QOpenGLTexture * initCubeMap(const char * right, const char * top,
const char * front, const char * left,
const char * bottom, const char * back);
private:
// Private ctor to prevent creating instances of this class
Texture() {}
};
#endif // QTOPENGL_TEXTURE_H

144
src/transform3D.cpp Normal file
View File

@@ -0,0 +1,144 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Transform3D class to represent object position in 3D space ##
## From following tutorials at trentreed.net ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <transform3D.h>
const QVector3D Transform3D::LocalForward(0.0f, 0.0f, 1.0f);
const QVector3D Transform3D::LocalUp(0.0f, 1.0f, 0.0f);
const QVector3D Transform3D::LocalRight(1.0f, 0.0f, 0.0f);
/*******************************************************************************
* Transformations
******************************************************************************/
void Transform3D::translate(const QVector3D & dt)
{
m_dirty = true;
mTranslation += dt;
}
void Transform3D::scale(const QVector3D & ds)
{
m_dirty = true;
mScale *= ds;
}
void Transform3D::rotate(const QQuaternion & dr)
{
m_dirty = true;
mRotation = dr * mRotation;
}
void Transform3D::grow(const QVector3D & ds)
{
m_dirty = true;
mScale += ds;
}
/*******************************************************************************
* Setters
******************************************************************************/
void Transform3D::setTranslation(const QVector3D & t)
{
m_dirty = true;
mTranslation = t;
}
void Transform3D::setScale(const QVector3D & s)
{
m_dirty = true;
mScale = s;
}
void Transform3D::setRotation(const QQuaternion & r)
{
m_dirty = true;
mRotation = r;
}
/*******************************************************************************
* Accessors
******************************************************************************/
// Produces modelToWorld matrix using current set of transformations
// Transformation * rotation * scale = modelToWorld
const QMatrix4x4 & Transform3D::toMatrix()
{
if (m_dirty)
{
m_dirty = false;
mWorld.setToIdentity();
mWorld.translate(mTranslation);
mWorld.rotate(mRotation);
mWorld.scale(mScale);
}
return mWorld;
}
/*******************************************************************************
* Queries
******************************************************************************/
QVector3D Transform3D::forward() const
{
return mRotation.rotatedVector(LocalForward);
}
QVector3D Transform3D::up() const
{
return mRotation.rotatedVector(LocalUp);
}
QVector3D Transform3D::right() const
{
return mRotation.rotatedVector(LocalRight);
}
/*******************************************************************************
* QT Streams
******************************************************************************/
QDebug operator<<(QDebug dbg, const Transform3D & transform)
{
dbg << "Transform3D\n{\n";
dbg << "Position: <" << transform.translation().x() << ", "
<< transform.translation().y() << ", "
<< transform.translation().z() << ">\n";
dbg << "Scale: <" << transform.scale().x() << ", "
<< transform.scale().y() << ", "
<< transform.scale().z() << ">\n";
dbg << "Rotation: <" << transform.rotation().x() << ", "
<< transform.rotation().y() << ", "
<< transform.rotation().z() << " | " <<
transform.rotation().scalar() << ">\n}";
return dbg;
}
QDataStream & operator<<(QDataStream & out, const Transform3D & transform)
{
out << transform.mTranslation;
out << transform.mScale;
out << transform.mRotation;
return out;
}
QDataStream & operator>>(QDataStream & in, Transform3D & transform)
{
in >> transform.mTranslation;
in >> transform.mScale;
in >> transform.mRotation;
transform.m_dirty = true;
return in;
}

117
src/transform3D.h Normal file
View File

@@ -0,0 +1,117 @@
/*##############################################################################
## Author: Shaun Reed ##
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
## About: Transform3D class to represent object position in 3D space ##
## From following tutorials at trentreed.net ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#ifndef QTK_TRANSFORM3D_H
#define QTK_TRANSFORM3D_H
#include <QDebug>
#include <QMatrix4x4>
#include <QQuaternion>
#include <QVector3D>
class Transform3D
{
public:
// Constructors
inline Transform3D() : m_dirty(true),
mScale(1.0f, 1.0f, 1.0f),
mTranslation(0.0f, 0.0f, 0.0f) { }
//
// Transformations
void translate(const QVector3D & dt);
inline void translate(float dx, float dy, float dz)
{ translate(QVector3D(dx, dy, dz));}
// Scale object with multiplication
void scale(const QVector3D & ds);
inline void scale(float dx, float dy, float dz)
{ scale(QVector3D(dx, dy, dz));}
inline void scale(float factor)
{ scale(QVector3D(factor, factor, factor));}
// Multiplying by a rotation
void rotate(const QQuaternion & dr);
inline void rotate(float angle, const QVector3D & axis)
{ rotate(QQuaternion::fromAxisAndAngle(axis, angle));}
inline void rotate(float angle, float ax, float ay, float az)
{ rotate(QQuaternion::fromAxisAndAngle(ax, ay, az, angle));}
// Scale object by addition
void grow(const QVector3D & ds);
inline void grow(float dx, float dy, float dz)
{ grow(QVector3D(dx, dy, dz));}
inline void grow(float factor)
{ grow(QVector3D(factor, factor, factor));}
//
// Setters
// Set object position
void setTranslation(const QVector3D & t);
inline void setTranslation(float x, float y, float z)
{ setTranslation(QVector3D(x, y, z));}
// Set object scale
void setScale(const QVector3D & s);
inline void setScale(float x, float y, float z)
{ setScale(QVector3D(x, y, z));}
inline void setScale(float k)
{ setScale(QVector3D(k, k, k));}
// Set object rotation
void setRotation(const QQuaternion & r);
inline void setRotation(float angle, const QVector3D & axis)
{ setRotation(QQuaternion::fromAxisAndAngle(axis, angle));}
inline void setRotation(float angle, float ax, float ay, float az)
{ setRotation(QQuaternion::fromAxisAndAngle(ax, ay, az, angle));}
//
// Accessors
inline const QVector3D & translation() const { return mTranslation;}
inline const QVector3D & scale() const { return mScale; }
inline const QQuaternion & rotation() const { return mRotation; }
const QMatrix4x4 & toMatrix();
QVector3D forward() const;
QVector3D up() const;
QVector3D right() const;
static const QVector3D LocalForward, LocalUp, LocalRight;
private:
QVector3D mTranslation;
QQuaternion mRotation;
QVector3D mScale;
QMatrix4x4 mWorld;
bool m_dirty;
#ifndef QT_NO_DATASTREAM
friend QDataStream &operator<<(QDataStream & out, const Transform3D & transform);
friend QDataStream &operator>>(QDataStream & in, Transform3D & transform);
#endif
};
Q_DECLARE_TYPEINFO(Transform3D, Q_MOVABLE_TYPE);
// Qt Streams
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const Transform3D & transform);
#endif
#ifndef QT_NO_DATASTREAM
QDataStream &operator<<(QDataStream & out, const Transform3D & transform);
QDataStream &operator>>(QDataStream & in, Transform3D & transform);
#endif
#endif // QTK_TRANSFORM3D_H