diff --git a/include/obj.h b/include/obj.h index 2a8e0c9..65929f1 100644 --- a/include/obj.h +++ b/include/obj.h @@ -1,14 +1,20 @@ +// not good at using namespaces, bear with me + #include #include using namespace std; + +// struct to hold vertex position struct Vertex { float pos[3]; }; +// struct to hold normal vectors struct Normal { float dir[3]; }; +// struct to hold material information struct Material { int idx; string name; @@ -17,12 +23,15 @@ struct Material { float diffuse[4]; }; +// meta struct for each individual face, pointers to its set of +// vertices, normals and material structs struct Face { vector vertices; vector normals; Material* mtl; }; +// class to load and handle .obj and .mtl parsing and face data construction class ObjectLoader { public: diff --git a/include/opglwidget.h b/include/opglwidget.h index b4179ca..9a69efc 100644 --- a/include/opglwidget.h +++ b/include/opglwidget.h @@ -10,26 +10,28 @@ public: OPGLWidget(QWidget *parent) : QOpenGLWidget(parent) {} ObjectLoader obj; public slots: - void paintGL(); - void update_camera(); + void paintGL(); // for repaint loop + void update_camera(); // for mouse movement protected: void initializeGL(); void resizeGL(int w, int h); void reorient(); + // camera controls void keyPressEvent(QKeyEvent *event); void keyReleaseEvent(QKeyEvent *event); void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); - int mtl_idx = -1; - float loc[3] = {0, 0, 10}; - float f[3] = {0, 0, -1}; - float up[3] = {0, 1, 0}; - float right[3] = {1, 0, 0}; - float vel[3] = {0, 0, 0}; - float speed = 0; - bool key_states[6] = {false, false, false, false, false, false}; - float mouse_loc_old[2]; + // init values + int mtl_idx = -1; // index to store current material to use + float loc[3] = {0, 0, 10}; // initial camera position + float f[3] = {0, 0, -1}; // initial camera direction + float up[3] = {0, 1, 0}; // initial camera up vector + float right[3] = {1, 0, 0}; // initial camera right vector + float vel[3] = {0, 0, 0}; // initial camera velocity + float speed = 0; // placeholder init for abs speed (for limit and slowing mostly) + bool key_states[6] = {false, false, false, false, false, false}; // camera control states + float mouse_loc_old[2]; // for calculating camera direction changes with mouse movement }; diff --git a/src/main.cpp b/src/main.cpp index 319368e..a022344 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,9 +5,11 @@ #include #include +// main loop int main(int argc, char **argv) { QApplication app(argc, argv); QCommandLineParser parser; + // pass in files as -m and -o QCommandLineOption mtl_filepath_opt({"m", "material"}, "Material file path, required", "path"); QCommandLineOption obj_filepath_opt({"o", "object"}, "Object file path, required", "path"); parser.addHelpOption(); @@ -19,16 +21,21 @@ int main(int argc, char **argv) { if (mtl_filepath.isEmpty() || obj_filepath.isEmpty()) { return 1; } + QMainWindow w; Ui::MainWindow ui; ui.setupUi(&w); + + // parse obj and mtl files ui.openGLWidget->obj.load_mtl(mtl_filepath.toStdString()); ui.openGLWidget->obj.load_obj(obj_filepath.toStdString()); + // timers for drawing and movement loops QTimer timer_draw; QTimer timer_cam; w.connect(&timer_draw, SIGNAL(timeout()), ui.openGLWidget, SLOT(paintGL())); w.connect(&timer_cam, SIGNAL(timeout()), ui.openGLWidget, SLOT(update_camera())); + // update every 16ms timer_draw.start(16); timer_cam.start(16); w.show(); diff --git a/src/obj.cpp b/src/obj.cpp index 70abe15..5e95c82 100644 --- a/src/obj.cpp +++ b/src/obj.cpp @@ -5,11 +5,16 @@ #include #include +// .mtl file load and parse void ObjectLoader::load_mtl(string filename) { string tmp; - ifstream f(filename); - int mtl_idx = -1; + ifstream f(filename); // open file + int mtl_idx = -1; // init index outside of valid range while (getline(f, tmp)) { + // if line defines a new material + // create new and set its name + // following lines will define ambient, spec, diffuse info for + // the newly defined material if (tmp.rfind("newmtl", 0) == 0) { mtl_idx += 1; mtls.push_back(*(new Material)); @@ -29,23 +34,30 @@ void ObjectLoader::load_mtl(string filename) { default: continue; }; + // parse syntax + // eg; Kd 0.2 0.2 0.2 + // delim by space, split and parse value char delim = ' '; char *token = strtok(const_cast(tmp.c_str()), &delim); for (int i = 0; i < 3; i++) { token = strtok(NULL, &delim); target_list[i] = atof(token); }; + // set alpha val since blender doesn't output it target_list[3] = 1.0f; }; }; }; +// .obj file load and parsing +// only supports single object files void ObjectLoader::load_obj(string filename) { string tmp; ifstream f(filename); while (getline(f, tmp)) { Material* mtl_ptr; if (tmp[0] == 'v') { + // if vertex (v) or vertex normal (vn), create new vertex struct float* target_list; switch (tmp[1]) { case ' ': @@ -59,6 +71,11 @@ void ObjectLoader::load_obj(string filename) { default: continue; }; + // same parsing as material values above + // something breaks here somehow on first run i think + // no clue if its pointer wierdness or something with + // the files but vector floats get borked + // works perfectly fine on next runs so... eh? char delim = ' '; char *token = strtok(const_cast(tmp.c_str()), &delim); for (int i = 0; i < 3; i++) { @@ -67,8 +84,14 @@ void ObjectLoader::load_obj(string filename) { }; } else if (tmp.rfind("usemtl", 0) == 0) { + // if line declares material scope, point the pointer to said material mtl_ptr = get_mtl_ptr(tmp.substr(tmp.find(' ') + 1)); } else if (tmp[0] == 'f') { + // face declared as set of v/vt/vn indexes + // currently ignoring vt and only using files without any vt exported + // (like generic meshes with materials) + // also only handles triangle mesh objects + // assign it a metal and its corresponding vertexes and normals faces.push_back(*(new Face)); faces.back().mtl = mtl_ptr; regex re("f (\\d*)//(\\d*) (\\d*)//(\\d*) (\\d*)//(\\d*)"); @@ -85,6 +108,7 @@ void ObjectLoader::load_obj(string filename) { }; }; +// just finds the pointer to the given material from its string Material* ObjectLoader::get_mtl_ptr(string mtl_name) { for (size_t i = 0; i < mtls.size(); i++) { if (mtls[i].name == mtl_name) { diff --git a/src/opglwidget.cpp b/src/opglwidget.cpp index a3a2ab4..15be607 100644 --- a/src/opglwidget.cpp +++ b/src/opglwidget.cpp @@ -4,6 +4,9 @@ #include #include +// probably should have learned OpenGL and rendering +// first, not even going to pretend to know what the first half +// does void OPGLWidget::initializeGL() { glClearDepth(1.0f); glDepthFunc(GL_LESS); @@ -14,6 +17,7 @@ void OPGLWidget::initializeGL() { glMatrixMode(GL_MODELVIEW); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); + // values here largely just copied from default light object in blender GLfloat light_ambient[] = {0.0, 0.0, 0.0, 1.0}; GLfloat light_diffuse[] = {1.0, 1.0, 1.0, 1.0}; GLfloat light_specular[] = {0.0, 0.5, 0.5, 0.2}; @@ -25,13 +29,18 @@ void OPGLWidget::initializeGL() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); } +// paint loop void OPGLWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); - reorient(); + reorient(); // implementation of gluPerspective to shift camera perspective + // mostly just because I could not figure out how to get GLU included properly glShadeModel(GL_SMOOTH); glPolygonMode(GL_FRONT, GL_FILL); glBegin(GL_TRIANGLES); + // for each face, check if its material is different from the one + // currently selected in mtl_idx. if different, populate new values and move idx + // then draw each vertex and normal for (size_t i = 0; i < obj.faces.size(); i++) { Material* mtl_ptr = obj.faces[i].mtl; if (mtl_idx != mtl_ptr->idx) { @@ -51,6 +60,7 @@ void OPGLWidget::paintGL() { update(); } +// cursed implementation of gluPerscpective here: https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml void OPGLWidget::reorient() { GLfloat s[] = {-f[2], 0, f[0]}; float s_mag = pow(pow(s[0], 2) + pow(s[1], 2) + pow(s[2], 2), 0.5); @@ -62,6 +72,7 @@ void OPGLWidget::reorient() { glTranslatef(-loc[0], -loc[1], -loc[2]); } +// window resize void OPGLWidget::resizeGL(int w, int h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); @@ -73,10 +84,13 @@ void OPGLWidget::resizeGL(int w, int h) { glMatrixMode(GL_MODELVIEW); } +// camera update, movement simulation void OPGLWidget::update_camera() { + // update location in direction of velocity by 1/100 of speed each tick for (int i = 0; i < 3; i++) { loc[i] += vel[i] * speed/100.0f; }; + // cursed, but its here, update velocity by corresponding key if (key_states[0]) { for (int i = 0; i < 3; i++) { vel[i] += f[i]; @@ -108,11 +122,14 @@ void OPGLWidget::update_camera() { } }; speed = pow(pow(vel[0], 2) + pow(vel[1], 2) + pow(vel[2], 2), 0.5); + // speed limit, update vel if over if (speed > 2) { for (int i = 0; i < 3; i++) { vel[i] = vel[i] / speed; } } + + // if no key pressed, decay speed until threshold then stop bool allFalse = true; for (int i = 0; i < 6; i++) { if (key_states[i]) { @@ -131,6 +148,7 @@ void OPGLWidget::update_camera() { } } +// key mappings void OPGLWidget::keyPressEvent(QKeyEvent *event) { switch (event -> key()) { case Qt::Key_W: @@ -177,29 +195,37 @@ void OPGLWidget::keyReleaseEvent(QKeyEvent *event) { }; } +// camera panning only happens on drag, also makes it so I don't +// have to bother with mouse capture and other wierdness from i3 tiling void OPGLWidget::mousePressEvent(QMouseEvent *event) { setMouseTracking(true); mouse_loc_old[0] = event -> x(); mouse_loc_old[1] = event -> y(); } +// stop tracking on release void OPGLWidget::mouseReleaseEvent(QMouseEvent *event) { setMouseTracking(false); mouse_loc_old[0] = event -> x(); mouse_loc_old[1] = event -> y(); } +// actual camera panning implementation void OPGLWidget::mouseMoveEvent(QMouseEvent *event) { + // calc a delta movement from old, then new -> old float delta_x = event->x() - mouse_loc_old[0]; float delta_y = event->y() - mouse_loc_old[1]; mouse_loc_old[0] = event->x(); mouse_loc_old[1] = event->y(); + // shift forward vector appropriately with movement for (int i = 0; i < 3; i++) { f[i] += right[i] * delta_x / 1000.0f; f[i] -= up[i] * delta_y / 1000.0f; } + // since up is static, cross product to find right, which is simply: right[0] = -f[2]; right[2] = f[0]; + // normalize forward and right float F_mag = pow(pow(f[0], 2) + pow(f[1], 2) + pow(f[2], 2), 0.5); float right_mag = pow(pow(right[0], 2) + pow(right[1], 2) + pow(right[2], 2), 0.5); for (int i = 0; i < 3; i++) {