# HG changeset patch # User John Tsiombikas # Date 1400470960 -10800 # Node ID da100bf13f7f1dd8cc5fe3b5da591fb84f1ac5fc # Parent 21319e71117fe3772c162b5482386f48dfd1839d [goat3d] implemented animation loading [goatview] working on the animation controls diff -r 21319e71117f -r da100bf13f7f goatview/src/goatview.cc --- a/goatview/src/goatview.cc Sun May 18 19:58:47 2014 +0300 +++ b/goatview/src/goatview.cc Mon May 19 06:42:40 2014 +0300 @@ -1,4 +1,5 @@ #include +#include #include #include "opengl.h" #include @@ -78,6 +79,7 @@ goat3d_free(scene); } if(!(scene = goat3d_create()) || goat3d_load(scene, fname) == -1) { + QMessageBox::critical(this, "Error", "Failed to load scene file: " + QString(fname)); return false; } @@ -97,6 +99,52 @@ return true; } +bool GoatView::load_anim(const char *fname) +{ + if(!scene) { + QMessageBox::critical(this, "Error", "You must load a scene before loading any animations!"); + return false; + } + + if(goat3d_load_anim(scene, fname) == -1) { + QMessageBox::critical(this, "Error", QString("Failed to load animation: ") + QString(fname)); + return false; + } + + + long tstart = LONG_MAX, tend = LONG_MIN; + int num_nodes = goat3d_get_node_count(scene); + for(int i=0; i tend) tend = t1; + } + } + + if(tstart != LONG_MAX) { + act_play->setDisabled(false); + act_rewind->setDisabled(false); + chk_loop->setDisabled(false); + + slider_time->setDisabled(false); + slider_time->setMinimum(tstart); + slider_time->setMaximum(tend); + + spin_time->setDisabled(false); + spin_time->setMinimum(tstart); + spin_time->setMaximum(tend); + } + + post_redisplay(); + return true; +} + bool GoatView::make_menu() { // file menu @@ -177,34 +225,56 @@ dock_cont->setLayout(dock_hbox); // animation control box - QGridLayout *anim_ctl_box = new QGridLayout; - dock_hbox->addLayout(anim_ctl_box); + QGroupBox *grp_anim_ctl = new QGroupBox("Animation controls"); + // TODO figure out how these fucking stretching policies work... + //grp_anim_ctl->sizePolicy().setHorizontalPolicy(QSizePolicy::Maximum); + grp_anim_ctl->sizePolicy().setHorizontalStretch(1); + dock_hbox->addWidget(grp_anim_ctl); - anim_ctl_box->addWidget(new QLabel("Animation"), 0, 0); - cbox_anims = new QComboBox; - cbox_anims->setDisabled(true); - anim_ctl_box->addWidget(cbox_anims, 0, 1); + QVBoxLayout *anim_ctl_box = new QVBoxLayout; + grp_anim_ctl->setLayout(anim_ctl_box); chk_loop = new QCheckBox("loop"); + chk_loop->setDisabled(true); chk_loop->setChecked(false); - anim_ctl_box->addWidget(chk_loop, 1, 0); + anim_ctl_box->addWidget(chk_loop); QToolBar *toolbar_ctl = new QToolBar; - anim_ctl_box->addWidget(toolbar_ctl, 1, 1); + anim_ctl_box->addWidget(toolbar_ctl); act_rewind = new QAction(style()->standardIcon(QStyle::SP_MediaSkipBackward), "Rewind", this); act_rewind->setDisabled(true); + connect(act_rewind, &QAction::triggered, [this](){ slider_time->setValue(slider_time->minimum()); }); toolbar_ctl->addAction(act_rewind); + act_play = new QAction(style()->standardIcon(QStyle::SP_MediaPlay), "Play", this); act_play->setDisabled(true); toolbar_ctl->addAction(act_play); - // timeline slider + // slider and spinbox + QWidget *ssgroup = new QWidget; + ssgroup->sizePolicy().setHorizontalStretch(4); + dock_hbox->addWidget(ssgroup); + + QGridLayout *ssgrid = new QGridLayout; + //dock_hbox->addLayout(ssgrid); + ssgroup->setLayout(ssgrid); + + ssgrid->addWidget(new QLabel("msec"), 0, 0); + spin_time = new QSpinBox; + spin_time->setDisabled(true); + ssgrid->addWidget(spin_time, 0, 1); + slider_time = new QSlider(Qt::Orientation::Horizontal); slider_time->setDisabled(true); + ssgrid->addWidget(slider_time, 1, 0, 1, 3); + connect(slider_time, &QSlider::valueChanged, - [&](){ anim_time = slider_time->value(); post_redisplay(); }); - dock_hbox->addWidget(slider_time); + [&](){ anim_time = slider_time->value(); spin_time->setValue(anim_time); post_redisplay(); }); + + typedef void (QSpinBox::*ValueChangedIntFunc)(int); + connect(spin_time, (ValueChangedIntFunc)&QSpinBox::valueChanged, + [&](){ anim_time = spin_time->value(); slider_time->setValue(anim_time); post_redisplay(); }); dock = new QDockWidget(this); dock->setAllowedAreas(Qt::BottomDockWidgetArea); @@ -235,7 +305,7 @@ statusBar()->showMessage("failed to load scene file"); return; } - statusBar()->showMessage("Successfully loaded scene: " + QString(fname)); + statusBar()->showMessage("Successfully loaded scene: " + QString(fname.c_str())); } void GoatView::open_anim() @@ -252,7 +322,7 @@ statusBar()->showMessage("failed to load animation file"); return; } - statusBar()->showMessage("Successfully loaded animation: " + QString(fname)); + statusBar()->showMessage("Successfully loaded animation: " + QString(fname.c_str())); } diff -r 21319e71117f -r da100bf13f7f goatview/src/goatview.h --- a/goatview/src/goatview.h Sun May 18 19:58:47 2014 +0300 +++ b/goatview/src/goatview.h Mon May 19 06:42:40 2014 +0300 @@ -23,7 +23,7 @@ // animation controls QSlider *slider_time; - QComboBox *cbox_anims; + QSpinBox *spin_time; QCheckBox *chk_loop; QAction *act_play, *act_rewind; @@ -41,6 +41,7 @@ ~GoatView(); bool load_scene(const char *fname); + bool load_anim(const char *fname); void show_about(); }; diff -r 21319e71117f -r da100bf13f7f src/goat3d.cc --- a/src/goat3d.cc Sun May 18 19:58:47 2014 +0300 +++ b/src/goat3d.cc Mon May 19 06:42:40 2014 +0300 @@ -885,6 +885,63 @@ return node->get_animation_name(); } +GOAT3DAPI long goat3d_get_anim_timeline(struct goat3d_node *root, long *tstart, long *tend) +{ + if(root->get_timeline_bounds(tstart, tend)) { + return *tend - *tstart; + } + return -1; +} + +GOAT3DAPI int goat3d_get_node_position_key_count(struct goat3d_node *node) +{ + return node->get_position_key_count(); +} + +GOAT3DAPI int goat3d_get_node_rotation_key_count(struct goat3d_node *node) +{ + return node->get_rotation_key_count(); +} + +GOAT3DAPI int goat3d_get_node_scaling_key_count(struct goat3d_node *node) +{ + return node->get_scaling_key_count(); +} + +GOAT3DAPI long goat3d_get_node_position_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr) +{ + Vector3 pos = node->get_position_key_value(idx); + long tm = node->get_position_key_time(idx); + + if(xptr) *xptr = pos.x; + if(yptr) *yptr = pos.y; + if(zptr) *zptr = pos.z; + return tm; +} + +GOAT3DAPI long goat3d_get_node_rotation_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr, float *wptr) +{ + Quaternion rot = node->get_rotation_key_value(idx); + long tm = node->get_rotation_key_time(idx); + + if(xptr) *xptr = rot.v.x; + if(yptr) *yptr = rot.v.y; + if(zptr) *zptr = rot.v.z; + if(wptr) *wptr = rot.s; + return tm; +} + +GOAT3DAPI long goat3d_get_node_scaling_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr) +{ + Vector3 scale = node->get_scaling_key_value(idx); + long tm = node->get_scaling_key_time(idx); + + if(xptr) *xptr = scale.x; + if(yptr) *yptr = scale.y; + if(zptr) *zptr = scale.z; + return tm; +} + GOAT3DAPI void goat3d_set_node_position(struct goat3d_node *node, float x, float y, float z, long tmsec) { node->set_position(Vector3(x, y, z), tmsec); diff -r 21319e71117f -r da100bf13f7f src/goat3d.h --- a/src/goat3d.h Sun May 18 19:58:47 2014 +0300 +++ b/src/goat3d.h Mon May 19 06:42:40 2014 +0300 @@ -268,6 +268,15 @@ GOAT3DAPI void goat3d_set_anim_name(struct goat3d_node *root, const char *name); GOAT3DAPI const char *goat3d_get_anim_name(struct goat3d_node *node); +GOAT3DAPI long goat3d_get_anim_timeline(struct goat3d_node *root, long *tstart, long *tend); + +GOAT3DAPI int goat3d_get_node_position_key_count(struct goat3d_node *node); +GOAT3DAPI int goat3d_get_node_rotation_key_count(struct goat3d_node *node); +GOAT3DAPI int goat3d_get_node_scaling_key_count(struct goat3d_node *node); +GOAT3DAPI long goat3d_get_node_position_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr); +GOAT3DAPI long goat3d_get_node_rotation_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr, float *wptr); +GOAT3DAPI long goat3d_get_node_scaling_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr); + GOAT3DAPI void goat3d_set_node_position(struct goat3d_node *node, float x, float y, float z, long tmsec); GOAT3DAPI void goat3d_set_node_rotation(struct goat3d_node *node, float qx, float qy, float qz, float qw, long tmsec); GOAT3DAPI void goat3d_set_node_scaling(struct goat3d_node *node, float sx, float sy, float sz, long tmsec); diff -r 21319e71117f -r da100bf13f7f src/goat3d_readxml.cc --- a/src/goat3d_readxml.cc Sun May 18 19:58:47 2014 +0300 +++ b/src/goat3d_readxml.cc Mon May 19 06:42:40 2014 +0300 @@ -18,6 +18,7 @@ #include #include #include +#include #include "goat3d.h" #include "goat3d_impl.h" #include "tinyxml2.h" @@ -26,6 +27,14 @@ using namespace g3dimpl; using namespace tinyxml2; +struct Key { + long tm; + Vector4 val; +}; + +static bool read_track(Scene *scn, XMLElement *xml_track, int *anim_idx); +static Key read_key(XMLElement *xml_key); + static Material *read_material(Scene *scn, XMLElement *xml_mtl); static const char *read_material_attrib(MaterialAttrib *attr, XMLElement *xml_attr); static Mesh *read_mesh(Scene *scn, XMLElement *xml_mesh); @@ -132,27 +141,164 @@ XMLElement *root = xml.RootElement(); if(strcmp(root->Name(), "anim") != 0) { - logmsg(LOG_ERROR, "invalid XML file, root node is not \n"); + logmsg(LOG_ERROR, "%s: root node is not \n", __FUNCTION__); delete [] buf; return false; } - XMLElement *elem; - - elem = root->FirstChildElement(); + int anim_idx = -1; + XMLElement *elem = root->FirstChildElement(); while(elem) { const char *elem_name = elem->Name(); - if(strcmp(elem_name, "name") == 0) { - } else if(strcmp(elem_name, "attr") == 0) { + if(strcmp(elem_name, "track") != 0) { + logmsg(LOG_ERROR, "%s: only s allowed in \n", __FUNCTION__); + delete [] buf; + return false; + } + + if(!read_track(this, elem, &anim_idx)) { + delete [] buf; + return false; } elem = elem->NextSiblingElement(); } + if(anim_idx == -1) { + logmsg(LOG_INFO, "%s: WARNING animation affected 0 nodes\n", __FUNCTION__); + } + delete [] buf; return true; } +static bool read_track(Scene *scn, XMLElement *xml_track, int *anim_idx) +{ + Node *node = 0; + int type = -1; + std::vector keys; + + XMLElement *elem = xml_track->FirstChildElement(); + while(elem) { + const char *elem_name = elem->Name(); + + if(strcmp(elem_name, "node") == 0) { + const char *name = elem->Attribute("string"); + if(!name || !(node = scn->get_node(name))) { + logmsg(LOG_ERROR, "%s: invalid track node: %s\n", __FUNCTION__, name); + return false; + } + + } else if(strcmp(elem_name, "attr") == 0) { + const char *str = elem->Attribute("string"); + if(str && strcmp(str, "position") == 0) { + type = XFormNode::POSITION_TRACK; + } else if(str && strcmp(str, "rotation") == 0) { + type = XFormNode::ROTATION_TRACK; + } else if(str && strcmp(str, "scaling") == 0) { + type = XFormNode::SCALING_TRACK; + } else { + logmsg(LOG_ERROR, "%s: invalid track attribute specifier: %s\n", __FUNCTION__, str); + return false; + } + + } else if(strcmp(elem_name, "key") == 0) { + Key key = read_key(elem); + if(key.tm == LONG_MIN) { + return false; // logging in read_key + } + keys.push_back(key); + + } else { + logmsg(LOG_ERROR, "%s: unexpected element <%s> in \n", __FUNCTION__, elem_name); + return false; + } + elem = elem->NextSiblingElement(); + } + + if(!node) { + logmsg(LOG_ERROR, "%s: invalid track, missing node reference\n", __FUNCTION__); + return false; + } + if(type == -1) { + logmsg(LOG_ERROR, "%s: invalid track, missing attribute specifier\n", __FUNCTION__); + return false; + } + + if(*anim_idx == -1) { + // this is the first node we encounter. add a new animation and activate it + XFormNode *root = node->get_root(); + *anim_idx = root->get_animation_count() + 1; + + char name[64]; + sprintf(name, "anim%03d\n", *anim_idx); + root->add_animation(name); + root->use_animation(*anim_idx); + } else { + // make sure this node hierarchy already has this animation, otherwise add it + XFormNode *root = node->get_root(); + if(root->get_active_animation_index() != *anim_idx) { + char name[64]; + sprintf(name, "anim%03d\n", *anim_idx); + root->add_animation(name); + root->use_animation(*anim_idx); + } + } + + for(auto key : keys) { + switch(type) { + case XFormNode::POSITION_TRACK: + node->set_position(Vector3(key.val.x, key.val.y, key.val.z), key.tm); + break; + case XFormNode::ROTATION_TRACK: + node->set_rotation(Quaternion(key.val.w, key.val.x, key.val.y, key.val.z), key.tm); + break; + case XFormNode::SCALING_TRACK: + node->set_scaling(Vector3(key.val.x, key.val.y, key.val.z), key.tm); + } + } + return true; +} + +static Key read_key(XMLElement *xml_key) +{ + Key key; + key.tm = LONG_MIN; // initialize to invalid time + + XMLElement *xml_time = xml_key->FirstChildElement("time"); + XMLElement *xml_value = xml_key->FirstChildElement("value"); + + if(!xml_time || !xml_value) { + logmsg(LOG_ERROR, "%s: invalid key, missing either