Skip to content

Instantly share code, notes, and snippets.

@kunugibaru
Created February 22, 2017 12:01
Show Gist options
  • Save kunugibaru/a205c34dfcf16f9ae6fa4fee0d16a594 to your computer and use it in GitHub Desktop.
Save kunugibaru/a205c34dfcf16f9ae6fa4fee0d16a594 to your computer and use it in GitHub Desktop.
#pragma once
/** ======================
Done: テクスチャー付き立方体, カメラの移動、キーボード操作の受付、
Todo:
形状と、シェーダーの情報を分離する。
ジオメトリ読み込み, キューブマップ, 法線マップ, ソート
アルファ, TriBuffer, mikkt,
カメラを UserControllable として一般化?
=========================
*/
#include <QOpenGLWidget>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QtWidgets/QApplication>
#include <QMatrix4x4>
#include <QVector>
#include <QOpenGLShader>
#include <QOpenGLVertexArrayObject>
#include <QMouseEvent>
#include <QOpenGLTexture>
#include <QDebug>
#include <QWidget>
#include <QTimer>
#include <QOpenGLFunctions>
// use stl containers instead of qt's.
#include <fstream>
#include <string>
#include <iostream>
#include <array>
#include <vector>
#include <functional>
#include <map>
#include <memory>
/** ======================
DEBUG
=========================
*/
namespace dbg
{
template<typename T>
inline QString dump_array(const T* ts, int length)
{
QString out;
for (size_t i = 0; i < length; i++) {
out += QString::fromStdString(std::to_string(static_cast<float>(ts[i]))) + ", ";
}
return out;
}
inline void ifdef_debug(const std::function<void()>& func)
{
#ifdef _DEBUG
func();
#endif // _DEBUG
}
}
/** ======================
VECTOR, DATA TYPES
=========================
*/
#if 0
namespace vecs
{
template<typename N, int num>
class Vec
{
public:
std::array<N, num> vec_;
N x() const { vec_.at(0); }
N y() const { vec_.at(1); }
N z() const { vec_.at(2); }
};
template<typename N>
using Vec3 = Vec<N, 3>;
template<typename N>
class Vec4 : public Vec<N, 4>
{
N a() const { vec_.at(3); }
};
}
//using Vec3 = vecs::Vec3<float>;
//using Vec4 = vecs::Vec4<float>;
#endif // 0
using Vec3 = QVector3D;
using Vec4 = QVector4D;
using Vec2 = QVector2D;
using Quaternion = QQuaternion;
enum struct UserCommand
{
kNone,
// Camera Movement Rotations
kCamTiltUp, kCamTiltDown, kCamPanLeft, kCamPanRight,
// Camera Movement Translations
kCamDollyIn, kCamDollyOut,
kCamTruckRight, kCamTruckLeft, kCamCraneUp, kCamCraneDown,
kCamTruckRightAroundOrigin, kCamTruckLeftAroundOrigin,
kCamCraneUpAroundOrigin, kCamCraneDownAroundOrigin,
// Camera Misc
kCamFocusOrigin,
};
/** ======================
CAMERA
=========================
*/
class Camera
{
public:
using Matrix = QMatrix4x4;
Matrix model_;
Matrix perspective_;
Matrix view_;
float vertical_angle_ = 60.f;
Vec3 pos_ = { 2, 3, 5 };
Vec3 target_ = { 0,0,0 };
Vec3 up_ = { 0,1,0 };
float dolly_speed_ = 1.0f;
float pan_speed_ = 1.0f;
QSize aspect_ratio_;
Vec3 forward(bool normalize = true) const
{
Vec3 dir = target_ - pos_;;
if (normalize) {
dir.normalize();
}
return dir;
}
Vec3 back(bool normalize = true) const
{
return -1 * forward(normalize);
}
Vec3 left(bool normalize = true) const
{
Vec3 left = QVector3D::crossProduct(this->up_, this->forward());
if (normalize) {
left.normalize();
}
return left;
}
Vec3 right(bool normalize = true) const
{
return -1 * this->left(normalize);
}
void translate(const Vec3& angle)
{
//qInfo() << dir;
this->pos_ += angle * pan_speed_;
this->target_ += angle * pan_speed_;
}
void rotate(const float& angle)
{
const Vec3& old_dir = this->pos_ - this->target_;
const Vec3& new_dir = Quaternion::fromAxisAndAngle(this->up_, angle).rotatedVector(old_dir);
this->target_ = target_ + (old_dir - new_dir);
}
void rotate_around_targer(const float& angle)
{
const Vec3& old_dir = this->target_ - this->pos_;
const Vec3& new_dir = Quaternion::fromAxisAndAngle(this->up_, angle).rotatedVector(old_dir);
this->pos_ = pos_ + (old_dir - new_dir);
}
void update()
{
perspective_.setToIdentity();
view_.setToIdentity();
model_.setToIdentity();
perspective_.perspective(this->vertical_angle_, aspect_ratio_.width() / aspect_ratio_.height(), 0.001f, 120.0f);
this->view_.lookAt(this->pos_, target_, up_);
}
Matrix mvp()
{
return (perspective_ * view_) * model_;
}
};
/** ======================
MESH
=========================
*/
/*
メッシュ作業中。
面に特殊な情報は一切持たせない方向で。
*/
namespace geo
{
struct Placement // 分けないと常に原点からしか回転できなくなる。
{
Vec3 pos_;
QMatrix4x4 transform_;
};
struct BasicVert
{
protected:
Vec3 pos_;
Vec2 uv_;
public:
BasicVert(const Vec3& pos) : pos_(pos) {}
float x() const { return pos_.x(); }
float y() const { return pos_.y(); }
float z() const { return pos_.z(); }
};
template<typename VertT>
struct Shader
{
using vert_t = VertT;
std::string name_;
std::string vs_code_;
std::string fs_colde_;
std::string basecolor_;
};
template<typename ShaderT>
struct Surf
{
protected:
using vert_t = typename ShaderT::vert_t;
using shader_t = ShaderT;
std::tuple<int, int, int> indices_;
std::shared_ptr<ShaderT> shader_;
};
template<typename ShaderT>
struct Mesh
{
using surf_t = Surf<ShaderT>;
using vert_t = typename Surf<ShaderT>::vert_t;
Placement placement_;
std::vector<vert_t> verts_;
std::vector<surf_t> surfs_;
};
using BasicMesh = Mesh<Shader<BasicVert>>;
struct PointLight
{
Placement placement_;
};
struct BasicScene
{
std::vector<BasicMesh> meshes_;
std::vector<PointLight> lights_;
};
}
/** ======================
ATTRIBUTES, DATA
=========================
*/
template<typename T>
struct GLTypeMap {};
template<> struct GLTypeMap<float> { const static short glt = GL_FLOAT; };
template<> struct GLTypeMap<int> { const static short glt = GL_INT; };
using VattrCallback = std::function<void(
const int& offset, const void* data, const int size,
const QString& name, const short& gltype, const int& dim, const int& stride)>;
template<typename T, int dimension>
struct VertAttr
{
protected:
int dim_ = dimension;
std::vector<T> data_;
QString name_;
int offset_;
short stride_ = 0;
public:
// Setters
void set_verts(const int& offset, const QString& name, const std::vector<T>& verts)
{
this->offset_ = offset;
this->name_ = name;
this->data_ = verts;
}
// Getters
const T* data() const { return this->data_.data(); }
int dim() const { return this->dim_; }
short gltype() const { return GLTypeMap<T>::glt; }
int count() const { return this->data_.size(); }
int bytesize() const { return this->count() * sizeof(T); }
int b_offset() const { return this->offset_; }
int b_end() const { return this->b_offset() + bytesize(); }
std::string name() const { return this->name_.toStdString(); }
void dump_self(const VattrCallback& func) const
{
func(b_offset(), (void*)data(), bytesize(), name_, gltype(), dim(), stride_);
}
};
template<int size>
struct Attribute {
std::string name_;
int size_ = size;
};
struct Uniform {
std::string name_;
};
// INPUTDATA ====
/**
OpenGL のインターフェイスに依存しない、形状のみを格納する構造体。
*/
struct InputData
{
InputData() {}
QString vs_;
QString fs_;
QImage texture_;
VertAttr<float, 3> p_;
VertAttr<float, 3> cd_;
VertAttr<float, 2> uv_;
Attribute<1> frame_;
Uniform mvp_;
int vertnum() const
{
return p_.count() / 3;
}
int bytesize() const
{
return this->p_.bytesize() + this->cd_.bytesize() + this->uv_.bytesize();
}
void iter_vattr(const VattrCallback& func) const
{
p_.dump_self(func);
cd_.dump_self(func);
uv_.dump_self(func);
}
};
/**
Create a basic cube
*/
inline std::shared_ptr<InputData> basic_obj()
{
InputData id;
// Shaders
{
id.vs_ = R"(
attribute vec3 vP;
attribute vec3 vCd;
attribute vec2 vUv;
attribute float frame;
uniform mat4 mvpMat;
out float varFrame;
out vec3 fCd;
out vec2 fUv;
void main(void) {
fCd = vCd;
fUv = vUv;
varFrame = frame;
gl_Position = mvpMat * vec4(vP, 1.0);
}
)";
id.fs_ = R"(#version 330
layout(location = 0, index = 0) out vec4 fragColor;
in float varFrame;
in vec3 fCd;
in vec2 fUv;
uniform sampler2D sampler;
void main(void) {
float t = varFrame * 5;
fragColor = vec4(
abs(cos(t/100.0)),
abs(cos(t/200.0)),
abs(cos(t/300.0)),
1.0) * vec4(fCd, 1.0) + 0.3;
fragColor = vec4(texture( sampler, fUv).rg, 0.5, 1);
}
)";
}
// Verts
{
id.p_.set_verts(0,
"vP", {
-1.0f,-1.0f,-1.0f, -1.0f,-1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
1.0f, 1.0f,-1.0f, -1.0f,-1.0f,-1.0f, -1.0f, 1.0f,-1.0f,
1.0f,-1.0f, 1.0f, -1.0f,-1.0f,-1.0f, 1.0f,-1.0f,-1.0f,
1.0f, 1.0f,-1.0f, 1.0f,-1.0f,-1.0f, -1.0f,-1.0f,-1.0f,
-1.0f,-1.0f,-1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f,-1.0f,
1.0f,-1.0f, 1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f,-1.0f,
-1.0f, 1.0f, 1.0f, -1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,-1.0f,-1.0f, 1.0f, 1.0f,-1.0f,
1.0f,-1.0f,-1.0f, 1.0f, 1.0f, 1.0f, 1.0f,-1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f, 1.0f,-1.0f, -1.0f, 1.0f,-1.0f,
1.0f, 1.0f, 1.0f, -1.0f, 1.0f,-1.0f, -1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f,-1.0f, 1.0f
});
id.cd_.set_verts(id.p_.b_end(),
"vCd", {
0.583f, 0.771f, 0.014f, 0.609f, 0.115f, 0.436f, 0.327f, 0.483f, 0.844f,
0.822f, 0.569f, 0.201f, 0.435f, 0.602f, 0.223f, 0.310f, 0.747f, 0.185f,
0.597f, 0.770f, 0.761f, 0.559f, 0.436f, 0.730f, 0.359f, 0.583f, 0.152f,
0.483f, 0.596f, 0.789f, 0.559f, 0.861f, 0.639f, 0.195f, 0.548f, 0.859f,
0.014f, 0.184f, 0.576f, 0.771f, 0.328f, 0.970f, 0.406f, 0.615f, 0.116f,
0.676f, 0.977f, 0.133f, 0.971f, 0.572f, 0.833f, 0.140f, 0.616f, 0.489f,
0.997f, 0.513f, 0.064f, 0.945f, 0.719f, 0.592f, 0.543f, 0.021f, 0.978f,
0.279f, 0.317f, 0.505f, 0.167f, 0.620f, 0.077f, 0.347f, 0.857f, 0.137f,
0.055f, 0.953f, 0.042f, 0.714f, 0.505f, 0.345f, 0.783f, 0.290f, 0.734f,
0.722f, 0.645f, 0.174f, 0.302f, 0.455f, 0.848f, 0.225f, 0.587f, 0.040f,
0.517f, 0.713f, 0.338f, 0.053f, 0.959f, 0.120f, 0.393f, 0.621f, 0.362f,
0.673f, 0.211f, 0.457f, 0.820f, 0.883f, 0.371f, 0.982f, 0.099f, 0.879f
});
id.uv_.set_verts(id.cd_.b_end(),
"vUv", {
1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
});
}
// Attribs
{
id.frame_.name_ = "frame";
id.frame_.size_ = 1;
id.mvp_.name_ = "mvpMat";
}
// Textures
{
id.texture_ = QImage(QString(":/qtglt/textures/side1.png"));
}
return std::make_shared<InputData>(id);
}
template<typename UData>
struct OglData
{
using AttriibLocation = int;
using UniformLocation = int;
QOpenGLShaderProgram program_;
QOpenGLBuffer vbo_{ QOpenGLBuffer::VertexBuffer };
QOpenGLBuffer ibo_;
QOpenGLVertexArrayObject vao_;
QOpenGLTexture* texture_;
AttriibLocation frame_;
UniformLocation mvp_;
std::shared_ptr<UData> udata_;
OglData()
{
vao_.create();
vbo_.create();
}
void bind()
{
vao_.bind();
vbo_.bind();
program_.bind();
texture_->bind();
}
void release()
{
vao_.release();
vbo_.release();
program_.release();
texture_->release();
}
int offset() const
{
return 0;
}
inline void make(std::shared_ptr<UData> udata)
{
udata_ = udata;
const auto& ud = *(udata_.get());
// Shader
program_.addShaderFromSourceCode(QOpenGLShader::Vertex, ud.vs_);
program_.addShaderFromSourceCode(QOpenGLShader::Fragment, ud.fs_);
program_.link();
// Vertex
vbo_.create();
vbo_.bind();
vbo_.setUsagePattern(QOpenGLBuffer::StaticDraw);
vbo_.allocate(ud.bytesize());
ud.iter_vattr([&](
const int& offset, const void* data, const int size,
const QString& name, const short& gltype, const int& dim, const int& stride)
{
vbo_.write(offset, data, size);
program_.setAttributeBuffer(name.toStdString().c_str(), gltype, offset, dim, stride);
program_.enableAttributeArray(name.toStdString().c_str());
});
// Attributes
frame_ = program_.attributeLocation(ud.frame_.name_.c_str());
mvp_ = program_.uniformLocation(ud.mvp_.name_.c_str());
texture_ = new QOpenGLTexture(ud.texture_.mirrored());
texture_->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
texture_->setMagnificationFilter(QOpenGLTexture::Linear);
}
};
/** ======================
Mouse, User Input, OS, things are dependent on user environment.
=========================
*/
struct Environ
{
private:
float frame_ = 0.0f;
public:
bool is_mouse_pressed_ = false;
Vec2 mouse_dir_;
QPoint last_mouse_pos_;
UserCommand last_user_command_ = UserCommand::kNone;
void tick()
{
frame_++;
}
float frame() const { return frame_; }
};
/** ======================
APP
=========================
*/
class qtglt : public QOpenGLWidget
{
Q_OBJECT
private:
QTimer timer_;
QOpenGLContext context_;
Environ environ_;
Camera cam_;
QString msg_ = "";
OglData<InputData> main_buffers;
//OglData normal_line_buffers;
public:
qtglt(QWidget* parent = nullptr) : QOpenGLWidget(parent), context_(0)
{
connect(&timer_, SIGNAL(timeout()), this, SLOT(update()));
timer_.start(16.666666);
}
virtual void initializeGL()
{
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// Vertex
{
main_buffers.make(basic_obj());
}
}
virtual void paintGL()
{
// do user control things.
{
environ_.tick();
handle_user_command();
cam_.update();
}
{
main_buffers.bind();
main_buffers.program_.setAttributeValue(main_buffers.frame_, environ_.frame());
main_buffers.program_.setUniformValue(main_buffers.mvp_, cam_.mvp());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, main_buffers.offset(), main_buffers.udata_->vertnum());
main_buffers.release();
}
{
}
}
virtual void resizeGL(int w, int h)
{
cam_.aspect_ratio_ = size();
glViewport(0, 0, w, h);
}
// GL DELEGATE END ====
void handle_error_msg() const
{
if (msg_.size() > 0) {
qWarning() << this->msg_;
std::ofstream ofst(R"(d:/temps/te.log)");
ofst << this->msg_.toStdString();
ofst.close();
throw 3;
}
}
void handle_user_command()
{
// Mouse Control Handlers
const QPoint& mouse_pos = this->mapFromGlobal(QCursor::pos());
QPoint mov = (environ_.last_mouse_pos_ - mouse_pos);
environ_.mouse_dir_.setX(float(mov.x()) / size().width());
environ_.mouse_dir_.setY(float(mov.y()) / size().height());
environ_.last_mouse_pos_ = mouse_pos;
if (environ_.is_mouse_pressed_ && (environ_.mouse_dir_.length() != 0)) {
qInfo() << environ_.mouse_dir_.x() << environ_.mouse_dir_.y();
cam_.rotate_around_targer(environ_.mouse_dir_.x() * 50);
}
// User Command Handlers
switch (environ_.last_user_command_)
{
case UserCommand::kCamDollyIn:
cam_.translate(cam_.forward());
break;
case UserCommand::kCamTruckLeft:
cam_.translate(cam_.left());
break;
case UserCommand::kCamTruckRight:
cam_.translate(cam_.right());
break;
case UserCommand::kCamDollyOut:
cam_.translate(cam_.back());
break;
case UserCommand::kCamPanLeft:
//cam_.rotate(10);
cam_.rotate_around_targer(10);
break;
case UserCommand::kCamPanRight:
//cam_.rotate(-10);
cam_.rotate_around_targer(-10);
break;
case UserCommand::kCamFocusOrigin:
cam_.target_ = Vec3(0, 0, 0);
default:
break;
}
environ_.last_user_command_ = UserCommand::kNone;
}
void keyPressEvent(QKeyEvent* e) override
{
switch (e->key())
{
case Qt::Key_W:
case Qt::Key_Up:
environ_.last_user_command_ = UserCommand::kCamDollyIn;
return;
case Qt::Key_A:
case Qt::Key_Left:
environ_.last_user_command_ = UserCommand::kCamTruckLeft;
return;
case Qt::Key_D:
case Qt::Key_Right:
environ_.last_user_command_ = UserCommand::kCamTruckRight;
return;
case Qt::Key_S:
case Qt::Key_Down:
environ_.last_user_command_ = UserCommand::kCamDollyOut;
return;
case Qt::Key_Q:
environ_.last_user_command_ = UserCommand::kCamPanLeft;
return;
case Qt::Key_E:
environ_.last_user_command_ = UserCommand::kCamPanRight;
return;
case Qt::Key_R:
environ_.last_user_command_ = UserCommand::kCamFocusOrigin;
return;
default:
break;
}
}
void mousePressEvent(QMouseEvent* mev)
{
environ_.is_mouse_pressed_ = true;
}
void mouseReleaseEvent(QMouseEvent* mev)
{
environ_.is_mouse_pressed_ = false;
}
void mouse_dragged(const Vec2& mov)
{
qInfo() << mov.x() << mov.y();
}
void check_ok(bool result, const QString& msg)
{
if (!result) {
this->msg_ += msg;
}
}
};
inline int call_me_in_main(int argc, char *argv[])
{
QApplication a(argc, argv);
qtglt mygl;
mygl.show();
return a.exec();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment