nuclear@78: #include nuclear@85: #include nuclear@82: #include "opengl.h" nuclear@76: #include nuclear@76: #include nuclear@73: #include "goatview.h" nuclear@74: #include "goat3d.h" nuclear@74: nuclear@78: static void draw_node(goat3d_node *node); nuclear@82: static void draw_mesh(goat3d_mesh *mesh); nuclear@78: nuclear@74: goat3d *scene; nuclear@85: static SceneModel *sdata; nuclear@88: static GoatViewport *glview; nuclear@75: nuclear@75: static long anim_time; nuclear@75: static float cam_theta, cam_phi, cam_dist = 8; nuclear@76: static float fov = 60.0; nuclear@82: static bool use_nodes = true; nuclear@83: static bool use_lighting = true; nuclear@75: nuclear@88: void post_redisplay() nuclear@88: { nuclear@88: glview->updateGL(); nuclear@88: } nuclear@88: nuclear@82: nuclear@82: GoatView::GoatView() nuclear@82: { nuclear@82: glview = 0; nuclear@85: scene_model = 0; nuclear@82: nuclear@84: QSettings settings; nuclear@84: resize(settings.value("main/size", QSize(1024, 768)).toSize()); nuclear@84: move(settings.value("main/pos", QPoint(100, 100)).toPoint()); nuclear@84: use_nodes = settings.value("use_nodes", true).toBool(); nuclear@84: use_lighting = settings.value("use_lighting", true).toBool(); nuclear@83: nuclear@83: make_center(); // must be first nuclear@82: make_menu(); nuclear@82: make_dock(); nuclear@82: nuclear@82: statusBar(); nuclear@82: nuclear@82: setWindowTitle("GoatView"); nuclear@82: } nuclear@82: nuclear@82: GoatView::~GoatView() nuclear@82: { nuclear@85: delete scene_model; nuclear@85: sdata = 0; nuclear@82: } nuclear@82: nuclear@83: void GoatView::closeEvent(QCloseEvent *ev) nuclear@83: { nuclear@84: QSettings settings; nuclear@84: settings.setValue("main/size", size()); nuclear@84: settings.setValue("main/pos", pos()); nuclear@84: settings.setValue("use_nodes", use_nodes); nuclear@84: settings.setValue("use_lighting", use_lighting); nuclear@83: } nuclear@83: nuclear@83: nuclear@82: bool GoatView::load_scene(const char *fname) nuclear@76: { nuclear@76: if(scene) { nuclear@76: goat3d_free(scene); nuclear@76: } nuclear@76: if(!(scene = goat3d_create()) || goat3d_load(scene, fname) == -1) { nuclear@76: return false; nuclear@76: } nuclear@76: nuclear@76: float bmin[3], bmax[3]; nuclear@82: if(goat3d_get_bounds(scene, bmin, bmax) != -1) { nuclear@82: float bsize = (Vector3(bmax[0], bmax[1], bmax[2]) - Vector3(bmin[0], bmin[1], bmin[2])).length(); nuclear@83: cam_dist = bsize / tan(DEG_TO_RAD(fov) / 2.0); nuclear@82: printf("bounds size: %f, cam_dist: %f\n", bsize, cam_dist); nuclear@82: } nuclear@76: nuclear@85: scene_model->set_scene(scene); nuclear@85: treeview->expandAll(); nuclear@85: treeview->resizeColumnToContents(0); nuclear@85: nuclear@85: sdata = scene_model; // set the global sdata ptr nuclear@76: return true; nuclear@76: } nuclear@73: nuclear@73: bool GoatView::make_menu() nuclear@73: { nuclear@82: // file menu nuclear@73: QMenu *menu_file = menuBar()->addMenu("&File"); nuclear@73: nuclear@73: QAction *act_open_sce = new QAction("&Open Scene", this); nuclear@73: act_open_sce->setShortcuts(QKeySequence::Open); nuclear@73: connect(act_open_sce, &QAction::triggered, this, &GoatView::open_scene); nuclear@73: menu_file->addAction(act_open_sce); nuclear@73: nuclear@73: QAction *act_open_anm = new QAction("Open &Animation", this); nuclear@73: connect(act_open_anm, &QAction::triggered, this, &GoatView::open_anim); nuclear@73: menu_file->addAction(act_open_anm); nuclear@73: nuclear@73: QAction *act_quit = new QAction("&Quit", this); nuclear@73: act_quit->setShortcuts(QKeySequence::Quit); nuclear@73: connect(act_quit, &QAction::triggered, [&](){qApp->quit();}); nuclear@73: menu_file->addAction(act_quit); nuclear@82: nuclear@82: // view menu nuclear@82: QMenu *menu_view = menuBar()->addMenu("&View"); nuclear@82: nuclear@82: QAction *act_use_nodes = new QAction("use nodes", this); nuclear@82: act_use_nodes->setCheckable(true); nuclear@82: act_use_nodes->setChecked(use_nodes); nuclear@83: connect(act_use_nodes, &QAction::triggered, this, nuclear@88: [&](){ use_nodes = !use_nodes; post_redisplay(); }); nuclear@82: menu_view->addAction(act_use_nodes); nuclear@83: nuclear@83: QAction *act_use_lighting = new QAction("lighting", this); nuclear@83: act_use_lighting->setCheckable(true); nuclear@83: act_use_lighting->setChecked(use_lighting); nuclear@83: connect(act_use_lighting, &QAction::triggered, glview, &GoatViewport::toggle_lighting); nuclear@83: menu_view->addAction(act_use_lighting); nuclear@83: nuclear@83: // help menu nuclear@83: QMenu *menu_help = menuBar()->addMenu("&Help"); nuclear@83: nuclear@83: QAction *act_about = new QAction("&About", this); nuclear@83: connect(act_about, &QAction::triggered, this, &GoatView::show_about); nuclear@83: menu_help->addAction(act_about); nuclear@73: return true; nuclear@73: } nuclear@73: nuclear@73: bool GoatView::make_dock() nuclear@73: { nuclear@73: // ---- side-dock ---- nuclear@73: QWidget *dock_cont = new QWidget; nuclear@73: QVBoxLayout *dock_vbox = new QVBoxLayout; nuclear@73: dock_cont->setLayout(dock_vbox); nuclear@73: nuclear@73: QDockWidget *dock = new QDockWidget("Scene graph", this); nuclear@73: dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); nuclear@73: dock->setWidget(dock_cont); nuclear@73: addDockWidget(Qt::LeftDockWidgetArea, dock); nuclear@73: nuclear@82: // make the tree view widget nuclear@85: treeview = new QTreeView; nuclear@85: treeview->setAlternatingRowColors(true); nuclear@88: treeview->setSelectionMode(QAbstractItemView::SingleSelection); nuclear@85: dock_vbox->addWidget(treeview); nuclear@82: nuclear@85: scene_model = new SceneModel; nuclear@88: connect(scene_model, &SceneModel::dataChanged, [&](){ post_redisplay(); }); nuclear@85: treeview->setModel(scene_model); nuclear@82: nuclear@88: connect(treeview->selectionModel(), &QItemSelectionModel::selectionChanged, nuclear@88: [&](){ scene_model->selchange(treeview->selectionModel()->selectedIndexes()); }); nuclear@88: nuclear@82: // misc nuclear@82: QPushButton *bn_quit = new QPushButton("quit"); nuclear@82: dock_vbox->addWidget(bn_quit); nuclear@85: connect(bn_quit, &QPushButton::clicked, [&](){ qApp->quit(); }); nuclear@82: nuclear@73: // ---- bottom dock ---- nuclear@73: dock_cont = new QWidget; nuclear@73: QHBoxLayout *dock_hbox = new QHBoxLayout; nuclear@73: dock_cont->setLayout(dock_hbox); nuclear@73: nuclear@73: QSlider *slider_time = new QSlider(Qt::Orientation::Horizontal); nuclear@73: slider_time->setDisabled(true); nuclear@73: dock_hbox->addWidget(slider_time); nuclear@73: nuclear@73: dock = new QDockWidget("Animation", this); nuclear@73: dock->setAllowedAreas(Qt::BottomDockWidgetArea); nuclear@73: dock->setWidget(dock_cont); nuclear@73: addDockWidget(Qt::BottomDockWidgetArea, dock); nuclear@73: nuclear@73: return true; nuclear@73: } nuclear@73: nuclear@73: bool GoatView::make_center() nuclear@73: { nuclear@88: glview = ::glview = new GoatViewport(this); nuclear@82: setCentralWidget(glview); nuclear@73: return true; nuclear@73: } nuclear@73: nuclear@73: void GoatView::open_scene() nuclear@73: { nuclear@74: std::string fname = QFileDialog::getOpenFileName(this, "Open scene file", "", nuclear@74: "Goat3D Scene (*.goatsce);;All Files (*)").toStdString(); nuclear@74: if(fname.empty()) { nuclear@74: statusBar()->showMessage("Abort: No file selected!"); nuclear@74: return; nuclear@74: } nuclear@74: nuclear@74: statusBar()->showMessage("opening scene file"); nuclear@76: if(!load_scene(fname.c_str())) { nuclear@74: statusBar()->showMessage("failed to load scene file"); nuclear@74: } nuclear@73: } nuclear@73: nuclear@73: void GoatView::open_anim() nuclear@73: { nuclear@73: statusBar()->showMessage("opening animation..."); nuclear@73: } nuclear@73: nuclear@73: nuclear@73: // ---- OpenGL viewport ---- nuclear@82: GoatViewport::GoatViewport(QWidget *main_win) nuclear@73: : QGLWidget(QGLFormat(QGL::DepthBuffer)) nuclear@73: { nuclear@82: this->main_win = main_win; nuclear@82: initialized = false; nuclear@73: } nuclear@73: nuclear@73: GoatViewport::~GoatViewport() nuclear@73: { nuclear@73: } nuclear@73: nuclear@73: QSize GoatViewport::sizeHint() const nuclear@73: { nuclear@73: return QSize(800, 600); nuclear@73: } nuclear@73: nuclear@82: #define CRITICAL(error, detail) \ nuclear@82: do { \ nuclear@82: fprintf(stderr, "%s: %s\n", error, detail); \ nuclear@82: QMessageBox::critical(main_win, error, detail); \ nuclear@82: abort(); \ nuclear@82: } while(0) nuclear@82: nuclear@73: void GoatViewport::initializeGL() nuclear@73: { nuclear@82: if(initialized) return; nuclear@82: initialized = true; nuclear@82: nuclear@79: init_opengl(); nuclear@79: nuclear@82: if(!GLEW_ARB_transpose_matrix) { nuclear@82: CRITICAL("OpenGL initialization failed", "ARB_transpose_matrix extension not found!"); nuclear@82: } nuclear@82: nuclear@79: glClearColor(0.1f, 0.1f, 0.1f, 1.0f); nuclear@78: nuclear@78: glEnable(GL_DEPTH_TEST); nuclear@78: glEnable(GL_CULL_FACE); nuclear@83: if(use_lighting) { nuclear@83: glEnable(GL_LIGHTING); nuclear@83: } nuclear@83: glEnable(GL_LIGHT0); nuclear@83: nuclear@83: float ldir[] = {-1, 1, 2, 0}; nuclear@83: glLightfv(GL_LIGHT0, GL_POSITION, ldir); nuclear@73: } nuclear@73: nuclear@73: void GoatViewport::resizeGL(int xsz, int ysz) nuclear@73: { nuclear@73: glViewport(0, 0, xsz, ysz); nuclear@75: glMatrixMode(GL_PROJECTION); nuclear@75: glLoadIdentity(); nuclear@75: gluPerspective(60.0, (float)xsz / (float)ysz, 0.5, 5000.0); nuclear@73: } nuclear@73: nuclear@73: void GoatViewport::paintGL() nuclear@73: { nuclear@75: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); nuclear@75: nuclear@75: glMatrixMode(GL_MODELVIEW); nuclear@75: glLoadIdentity(); nuclear@75: glTranslatef(0, 0, -cam_dist); nuclear@75: glRotatef(cam_phi, 1, 0, 0); nuclear@75: glRotatef(cam_theta, 0, 1, 0); nuclear@75: nuclear@82: if(!scene) return; nuclear@82: nuclear@82: if(use_nodes) { nuclear@75: int node_count = goat3d_get_node_count(scene); nuclear@75: for(int i=0; iget_node_data(node) : 0; nuclear@85: if(!data) return; nuclear@85: nuclear@75: float xform[16]; nuclear@75: goat3d_get_node_matrix(node, xform, anim_time); nuclear@78: nuclear@78: glPushMatrix(); nuclear@82: glMultTransposeMatrixf(xform); nuclear@75: nuclear@85: if(data->visible) { nuclear@85: if(goat3d_get_node_type(node) == GOAT3D_NODE_MESH) { nuclear@85: goat3d_mesh *mesh = (goat3d_mesh*)goat3d_get_node_object(node); nuclear@75: nuclear@85: draw_mesh(mesh); nuclear@88: nuclear@88: if(data->selected) { nuclear@88: float bmin[3], bmax[3]; nuclear@88: goat3d_get_mesh_bounds(mesh, bmin, bmax); nuclear@88: nuclear@88: glPushAttrib(GL_ENABLE_BIT); nuclear@88: glDisable(GL_LIGHTING); nuclear@88: nuclear@88: glColor3f(0.3, 1, 0.2); nuclear@88: nuclear@88: glBegin(GL_LINE_LOOP); nuclear@88: glVertex3f(bmin[0], bmin[1], bmin[2]); nuclear@88: glVertex3f(bmax[0], bmin[1], bmin[2]); nuclear@88: glVertex3f(bmax[0], bmin[1], bmax[2]); nuclear@88: glVertex3f(bmin[0], bmin[1], bmax[2]); nuclear@88: glEnd(); nuclear@88: nuclear@88: glBegin(GL_LINE_LOOP); nuclear@88: glVertex3f(bmin[0], bmax[1], bmin[2]); nuclear@88: glVertex3f(bmax[0], bmax[1], bmin[2]); nuclear@88: glVertex3f(bmax[0], bmax[1], bmax[2]); nuclear@88: glVertex3f(bmin[0], bmax[1], bmax[2]); nuclear@88: glEnd(); nuclear@88: nuclear@88: glBegin(GL_LINES); nuclear@88: glVertex3f(bmin[0], bmin[1], bmin[2]); nuclear@88: glVertex3f(bmin[0], bmax[1], bmin[2]); nuclear@88: glVertex3f(bmin[0], bmin[1], bmax[2]); nuclear@88: glVertex3f(bmin[0], bmax[1], bmax[2]); nuclear@88: glVertex3f(bmax[0], bmin[1], bmin[2]); nuclear@88: glVertex3f(bmax[0], bmax[1], bmin[2]); nuclear@88: glVertex3f(bmax[0], bmin[1], bmax[2]); nuclear@88: glVertex3f(bmax[0], bmax[1], bmax[2]); nuclear@88: glEnd(); nuclear@88: nuclear@88: glPopAttrib(); nuclear@88: } nuclear@85: } nuclear@75: } nuclear@75: nuclear@83: int num_child = goat3d_get_node_child_count(node); nuclear@75: for(int i=0; ix(); nuclear@78: prev_y = ev->y(); nuclear@78: } nuclear@78: nuclear@78: void GoatViewport::mouseMoveEvent(QMouseEvent *ev) nuclear@78: { nuclear@78: int dx = ev->x() - prev_x; nuclear@78: int dy = ev->y() - prev_y; nuclear@80: prev_x = ev->x(); nuclear@80: prev_y = ev->y(); nuclear@78: nuclear@78: if(!dx && !dy) return; nuclear@78: nuclear@78: if(ev->buttons() & Qt::LeftButton) { nuclear@78: cam_theta += dx * 0.5; nuclear@78: cam_phi += dy * 0.5; nuclear@78: nuclear@78: if(cam_phi < -90) cam_phi = -90; nuclear@78: if(cam_phi > 90) cam_phi = 90; nuclear@78: } nuclear@78: if(ev->buttons() & Qt::RightButton) { nuclear@78: cam_dist += dy * 0.1; nuclear@78: nuclear@78: if(cam_dist < 0.0) cam_dist = 0.0; nuclear@78: } nuclear@78: updateGL(); nuclear@78: } nuclear@83: nuclear@83: static const char *about_str = nuclear@83: "GoatView - Goat3D scene file viewer
" nuclear@83: "Copyright (C) 2014 John Tsiombikas <nuclear@mutantstargoat.com>
" nuclear@83: "
" nuclear@83: "This program is free software: you can redistribute it and/or modify
" nuclear@83: "it under the terms of the GNU General Public License as published by
" nuclear@83: "the Free Software Foundation, either version 3 of the License, or
" nuclear@83: "(at your option) any later version.
" nuclear@83: "
" nuclear@83: "This program is distributed in the hope that it will be useful,
" nuclear@83: "but WITHOUT ANY WARRANTY; without even the implied warranty of
" nuclear@83: "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
" nuclear@83: "GNU General Public License for more details.
" nuclear@83: "
" nuclear@83: "You should have received a copy of the GNU General Public License
" nuclear@83: "along with this program. If not, see http://www.gnu.org/licenses/gpl."; nuclear@83: nuclear@83: void GoatView::show_about() nuclear@83: { nuclear@83: QMessageBox::information(this, "About GoatView", about_str); nuclear@83: }