goat3d
changeset 95:da100bf13f7f
[goat3d] implemented animation loading
[goatview] working on the animation controls
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Mon, 19 May 2014 06:42:40 +0300 |
parents | 21319e71117f |
children | 20b04b4edad4 |
files | goatview/src/goatview.cc goatview/src/goatview.h src/goat3d.cc src/goat3d.h src/goat3d_readxml.cc src/xform_node.cc src/xform_node.h |
diffstat | 7 files changed, 351 insertions(+), 20 deletions(-) [+] |
line diff
1.1 --- a/goatview/src/goatview.cc Sun May 18 19:58:47 2014 +0300 1.2 +++ b/goatview/src/goatview.cc Mon May 19 06:42:40 2014 +0300 1.3 @@ -1,4 +1,5 @@ 1.4 #include <stdio.h> 1.5 +#include <limits.h> 1.6 #include <map> 1.7 #include "opengl.h" 1.8 #include <QtOpenGL/QtOpenGL> 1.9 @@ -78,6 +79,7 @@ 1.10 goat3d_free(scene); 1.11 } 1.12 if(!(scene = goat3d_create()) || goat3d_load(scene, fname) == -1) { 1.13 + QMessageBox::critical(this, "Error", "Failed to load scene file: " + QString(fname)); 1.14 return false; 1.15 } 1.16 1.17 @@ -97,6 +99,52 @@ 1.18 return true; 1.19 } 1.20 1.21 +bool GoatView::load_anim(const char *fname) 1.22 +{ 1.23 + if(!scene) { 1.24 + QMessageBox::critical(this, "Error", "You must load a scene before loading any animations!"); 1.25 + return false; 1.26 + } 1.27 + 1.28 + if(goat3d_load_anim(scene, fname) == -1) { 1.29 + QMessageBox::critical(this, "Error", QString("Failed to load animation: ") + QString(fname)); 1.30 + return false; 1.31 + } 1.32 + 1.33 + 1.34 + long tstart = LONG_MAX, tend = LONG_MIN; 1.35 + int num_nodes = goat3d_get_node_count(scene); 1.36 + for(int i=0; i<num_nodes; i++) { 1.37 + goat3d_node *node = goat3d_get_node(scene, i); 1.38 + if(goat3d_get_node_parent(node)) { 1.39 + continue; 1.40 + } 1.41 + 1.42 + long t0, t1; 1.43 + if(goat3d_get_anim_timeline(node, &t0, &t1) != -1) { 1.44 + if(t0 < tstart) tstart = t0; 1.45 + if(t1 > tend) tend = t1; 1.46 + } 1.47 + } 1.48 + 1.49 + if(tstart != LONG_MAX) { 1.50 + act_play->setDisabled(false); 1.51 + act_rewind->setDisabled(false); 1.52 + chk_loop->setDisabled(false); 1.53 + 1.54 + slider_time->setDisabled(false); 1.55 + slider_time->setMinimum(tstart); 1.56 + slider_time->setMaximum(tend); 1.57 + 1.58 + spin_time->setDisabled(false); 1.59 + spin_time->setMinimum(tstart); 1.60 + spin_time->setMaximum(tend); 1.61 + } 1.62 + 1.63 + post_redisplay(); 1.64 + return true; 1.65 +} 1.66 + 1.67 bool GoatView::make_menu() 1.68 { 1.69 // file menu 1.70 @@ -177,34 +225,56 @@ 1.71 dock_cont->setLayout(dock_hbox); 1.72 1.73 // animation control box 1.74 - QGridLayout *anim_ctl_box = new QGridLayout; 1.75 - dock_hbox->addLayout(anim_ctl_box); 1.76 + QGroupBox *grp_anim_ctl = new QGroupBox("Animation controls"); 1.77 + // TODO figure out how these fucking stretching policies work... 1.78 + //grp_anim_ctl->sizePolicy().setHorizontalPolicy(QSizePolicy::Maximum); 1.79 + grp_anim_ctl->sizePolicy().setHorizontalStretch(1); 1.80 + dock_hbox->addWidget(grp_anim_ctl); 1.81 1.82 - anim_ctl_box->addWidget(new QLabel("Animation"), 0, 0); 1.83 - cbox_anims = new QComboBox; 1.84 - cbox_anims->setDisabled(true); 1.85 - anim_ctl_box->addWidget(cbox_anims, 0, 1); 1.86 + QVBoxLayout *anim_ctl_box = new QVBoxLayout; 1.87 + grp_anim_ctl->setLayout(anim_ctl_box); 1.88 1.89 chk_loop = new QCheckBox("loop"); 1.90 + chk_loop->setDisabled(true); 1.91 chk_loop->setChecked(false); 1.92 - anim_ctl_box->addWidget(chk_loop, 1, 0); 1.93 + anim_ctl_box->addWidget(chk_loop); 1.94 1.95 QToolBar *toolbar_ctl = new QToolBar; 1.96 - anim_ctl_box->addWidget(toolbar_ctl, 1, 1); 1.97 + anim_ctl_box->addWidget(toolbar_ctl); 1.98 1.99 act_rewind = new QAction(style()->standardIcon(QStyle::SP_MediaSkipBackward), "Rewind", this); 1.100 act_rewind->setDisabled(true); 1.101 + connect(act_rewind, &QAction::triggered, [this](){ slider_time->setValue(slider_time->minimum()); }); 1.102 toolbar_ctl->addAction(act_rewind); 1.103 + 1.104 act_play = new QAction(style()->standardIcon(QStyle::SP_MediaPlay), "Play", this); 1.105 act_play->setDisabled(true); 1.106 toolbar_ctl->addAction(act_play); 1.107 1.108 - // timeline slider 1.109 + // slider and spinbox 1.110 + QWidget *ssgroup = new QWidget; 1.111 + ssgroup->sizePolicy().setHorizontalStretch(4); 1.112 + dock_hbox->addWidget(ssgroup); 1.113 + 1.114 + QGridLayout *ssgrid = new QGridLayout; 1.115 + //dock_hbox->addLayout(ssgrid); 1.116 + ssgroup->setLayout(ssgrid); 1.117 + 1.118 + ssgrid->addWidget(new QLabel("msec"), 0, 0); 1.119 + spin_time = new QSpinBox; 1.120 + spin_time->setDisabled(true); 1.121 + ssgrid->addWidget(spin_time, 0, 1); 1.122 + 1.123 slider_time = new QSlider(Qt::Orientation::Horizontal); 1.124 slider_time->setDisabled(true); 1.125 + ssgrid->addWidget(slider_time, 1, 0, 1, 3); 1.126 + 1.127 connect(slider_time, &QSlider::valueChanged, 1.128 - [&](){ anim_time = slider_time->value(); post_redisplay(); }); 1.129 - dock_hbox->addWidget(slider_time); 1.130 + [&](){ anim_time = slider_time->value(); spin_time->setValue(anim_time); post_redisplay(); }); 1.131 + 1.132 + typedef void (QSpinBox::*ValueChangedIntFunc)(int); 1.133 + connect(spin_time, (ValueChangedIntFunc)&QSpinBox::valueChanged, 1.134 + [&](){ anim_time = spin_time->value(); slider_time->setValue(anim_time); post_redisplay(); }); 1.135 1.136 dock = new QDockWidget(this); 1.137 dock->setAllowedAreas(Qt::BottomDockWidgetArea); 1.138 @@ -235,7 +305,7 @@ 1.139 statusBar()->showMessage("failed to load scene file"); 1.140 return; 1.141 } 1.142 - statusBar()->showMessage("Successfully loaded scene: " + QString(fname)); 1.143 + statusBar()->showMessage("Successfully loaded scene: " + QString(fname.c_str())); 1.144 } 1.145 1.146 void GoatView::open_anim() 1.147 @@ -252,7 +322,7 @@ 1.148 statusBar()->showMessage("failed to load animation file"); 1.149 return; 1.150 } 1.151 - statusBar()->showMessage("Successfully loaded animation: " + QString(fname)); 1.152 + statusBar()->showMessage("Successfully loaded animation: " + QString(fname.c_str())); 1.153 } 1.154 1.155
2.1 --- a/goatview/src/goatview.h Sun May 18 19:58:47 2014 +0300 2.2 +++ b/goatview/src/goatview.h Mon May 19 06:42:40 2014 +0300 2.3 @@ -23,7 +23,7 @@ 2.4 2.5 // animation controls 2.6 QSlider *slider_time; 2.7 - QComboBox *cbox_anims; 2.8 + QSpinBox *spin_time; 2.9 QCheckBox *chk_loop; 2.10 QAction *act_play, *act_rewind; 2.11 2.12 @@ -41,6 +41,7 @@ 2.13 ~GoatView(); 2.14 2.15 bool load_scene(const char *fname); 2.16 + bool load_anim(const char *fname); 2.17 2.18 void show_about(); 2.19 };
3.1 --- a/src/goat3d.cc Sun May 18 19:58:47 2014 +0300 3.2 +++ b/src/goat3d.cc Mon May 19 06:42:40 2014 +0300 3.3 @@ -885,6 +885,63 @@ 3.4 return node->get_animation_name(); 3.5 } 3.6 3.7 +GOAT3DAPI long goat3d_get_anim_timeline(struct goat3d_node *root, long *tstart, long *tend) 3.8 +{ 3.9 + if(root->get_timeline_bounds(tstart, tend)) { 3.10 + return *tend - *tstart; 3.11 + } 3.12 + return -1; 3.13 +} 3.14 + 3.15 +GOAT3DAPI int goat3d_get_node_position_key_count(struct goat3d_node *node) 3.16 +{ 3.17 + return node->get_position_key_count(); 3.18 +} 3.19 + 3.20 +GOAT3DAPI int goat3d_get_node_rotation_key_count(struct goat3d_node *node) 3.21 +{ 3.22 + return node->get_rotation_key_count(); 3.23 +} 3.24 + 3.25 +GOAT3DAPI int goat3d_get_node_scaling_key_count(struct goat3d_node *node) 3.26 +{ 3.27 + return node->get_scaling_key_count(); 3.28 +} 3.29 + 3.30 +GOAT3DAPI long goat3d_get_node_position_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr) 3.31 +{ 3.32 + Vector3 pos = node->get_position_key_value(idx); 3.33 + long tm = node->get_position_key_time(idx); 3.34 + 3.35 + if(xptr) *xptr = pos.x; 3.36 + if(yptr) *yptr = pos.y; 3.37 + if(zptr) *zptr = pos.z; 3.38 + return tm; 3.39 +} 3.40 + 3.41 +GOAT3DAPI long goat3d_get_node_rotation_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr, float *wptr) 3.42 +{ 3.43 + Quaternion rot = node->get_rotation_key_value(idx); 3.44 + long tm = node->get_rotation_key_time(idx); 3.45 + 3.46 + if(xptr) *xptr = rot.v.x; 3.47 + if(yptr) *yptr = rot.v.y; 3.48 + if(zptr) *zptr = rot.v.z; 3.49 + if(wptr) *wptr = rot.s; 3.50 + return tm; 3.51 +} 3.52 + 3.53 +GOAT3DAPI long goat3d_get_node_scaling_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr) 3.54 +{ 3.55 + Vector3 scale = node->get_scaling_key_value(idx); 3.56 + long tm = node->get_scaling_key_time(idx); 3.57 + 3.58 + if(xptr) *xptr = scale.x; 3.59 + if(yptr) *yptr = scale.y; 3.60 + if(zptr) *zptr = scale.z; 3.61 + return tm; 3.62 +} 3.63 + 3.64 GOAT3DAPI void goat3d_set_node_position(struct goat3d_node *node, float x, float y, float z, long tmsec) 3.65 { 3.66 node->set_position(Vector3(x, y, z), tmsec);
4.1 --- a/src/goat3d.h Sun May 18 19:58:47 2014 +0300 4.2 +++ b/src/goat3d.h Mon May 19 06:42:40 2014 +0300 4.3 @@ -268,6 +268,15 @@ 4.4 GOAT3DAPI void goat3d_set_anim_name(struct goat3d_node *root, const char *name); 4.5 GOAT3DAPI const char *goat3d_get_anim_name(struct goat3d_node *node); 4.6 4.7 +GOAT3DAPI long goat3d_get_anim_timeline(struct goat3d_node *root, long *tstart, long *tend); 4.8 + 4.9 +GOAT3DAPI int goat3d_get_node_position_key_count(struct goat3d_node *node); 4.10 +GOAT3DAPI int goat3d_get_node_rotation_key_count(struct goat3d_node *node); 4.11 +GOAT3DAPI int goat3d_get_node_scaling_key_count(struct goat3d_node *node); 4.12 +GOAT3DAPI long goat3d_get_node_position_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr); 4.13 +GOAT3DAPI long goat3d_get_node_rotation_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr, float *wptr); 4.14 +GOAT3DAPI long goat3d_get_node_scaling_key(struct goat3d_node *node, int idx, float *xptr, float *yptr, float *zptr); 4.15 + 4.16 GOAT3DAPI void goat3d_set_node_position(struct goat3d_node *node, float x, float y, float z, long tmsec); 4.17 GOAT3DAPI void goat3d_set_node_rotation(struct goat3d_node *node, float qx, float qy, float qz, float qw, long tmsec); 4.18 GOAT3DAPI void goat3d_set_node_scaling(struct goat3d_node *node, float sx, float sy, float sz, long tmsec);
5.1 --- a/src/goat3d_readxml.cc Sun May 18 19:58:47 2014 +0300 5.2 +++ b/src/goat3d_readxml.cc Mon May 19 06:42:40 2014 +0300 5.3 @@ -18,6 +18,7 @@ 5.4 #include <stdio.h> 5.5 #include <map> 5.6 #include <string> 5.7 +#include <algorithm> 5.8 #include "goat3d.h" 5.9 #include "goat3d_impl.h" 5.10 #include "tinyxml2.h" 5.11 @@ -26,6 +27,14 @@ 5.12 using namespace g3dimpl; 5.13 using namespace tinyxml2; 5.14 5.15 +struct Key { 5.16 + long tm; 5.17 + Vector4 val; 5.18 +}; 5.19 + 5.20 +static bool read_track(Scene *scn, XMLElement *xml_track, int *anim_idx); 5.21 +static Key read_key(XMLElement *xml_key); 5.22 + 5.23 static Material *read_material(Scene *scn, XMLElement *xml_mtl); 5.24 static const char *read_material_attrib(MaterialAttrib *attr, XMLElement *xml_attr); 5.25 static Mesh *read_mesh(Scene *scn, XMLElement *xml_mesh); 5.26 @@ -132,27 +141,164 @@ 5.27 5.28 XMLElement *root = xml.RootElement(); 5.29 if(strcmp(root->Name(), "anim") != 0) { 5.30 - logmsg(LOG_ERROR, "invalid XML file, root node is not <anim>\n"); 5.31 + logmsg(LOG_ERROR, "%s: root node is not <anim>\n", __FUNCTION__); 5.32 delete [] buf; 5.33 return false; 5.34 } 5.35 5.36 - XMLElement *elem; 5.37 - 5.38 - elem = root->FirstChildElement(); 5.39 + int anim_idx = -1; 5.40 + XMLElement *elem = root->FirstChildElement(); 5.41 while(elem) { 5.42 const char *elem_name = elem->Name(); 5.43 5.44 - if(strcmp(elem_name, "name") == 0) { 5.45 - } else if(strcmp(elem_name, "attr") == 0) { 5.46 + if(strcmp(elem_name, "track") != 0) { 5.47 + logmsg(LOG_ERROR, "%s: only <track>s allowed in <anim>\n", __FUNCTION__); 5.48 + delete [] buf; 5.49 + return false; 5.50 + } 5.51 + 5.52 + if(!read_track(this, elem, &anim_idx)) { 5.53 + delete [] buf; 5.54 + return false; 5.55 } 5.56 elem = elem->NextSiblingElement(); 5.57 } 5.58 5.59 + if(anim_idx == -1) { 5.60 + logmsg(LOG_INFO, "%s: WARNING animation affected 0 nodes\n", __FUNCTION__); 5.61 + } 5.62 + 5.63 delete [] buf; 5.64 return true; 5.65 } 5.66 5.67 +static bool read_track(Scene *scn, XMLElement *xml_track, int *anim_idx) 5.68 +{ 5.69 + Node *node = 0; 5.70 + int type = -1; 5.71 + std::vector<Key> keys; 5.72 + 5.73 + XMLElement *elem = xml_track->FirstChildElement(); 5.74 + while(elem) { 5.75 + const char *elem_name = elem->Name(); 5.76 + 5.77 + if(strcmp(elem_name, "node") == 0) { 5.78 + const char *name = elem->Attribute("string"); 5.79 + if(!name || !(node = scn->get_node(name))) { 5.80 + logmsg(LOG_ERROR, "%s: invalid track node: %s\n", __FUNCTION__, name); 5.81 + return false; 5.82 + } 5.83 + 5.84 + } else if(strcmp(elem_name, "attr") == 0) { 5.85 + const char *str = elem->Attribute("string"); 5.86 + if(str && strcmp(str, "position") == 0) { 5.87 + type = XFormNode::POSITION_TRACK; 5.88 + } else if(str && strcmp(str, "rotation") == 0) { 5.89 + type = XFormNode::ROTATION_TRACK; 5.90 + } else if(str && strcmp(str, "scaling") == 0) { 5.91 + type = XFormNode::SCALING_TRACK; 5.92 + } else { 5.93 + logmsg(LOG_ERROR, "%s: invalid track attribute specifier: %s\n", __FUNCTION__, str); 5.94 + return false; 5.95 + } 5.96 + 5.97 + } else if(strcmp(elem_name, "key") == 0) { 5.98 + Key key = read_key(elem); 5.99 + if(key.tm == LONG_MIN) { 5.100 + return false; // logging in read_key 5.101 + } 5.102 + keys.push_back(key); 5.103 + 5.104 + } else { 5.105 + logmsg(LOG_ERROR, "%s: unexpected element <%s> in <track>\n", __FUNCTION__, elem_name); 5.106 + return false; 5.107 + } 5.108 + elem = elem->NextSiblingElement(); 5.109 + } 5.110 + 5.111 + if(!node) { 5.112 + logmsg(LOG_ERROR, "%s: invalid track, missing node reference\n", __FUNCTION__); 5.113 + return false; 5.114 + } 5.115 + if(type == -1) { 5.116 + logmsg(LOG_ERROR, "%s: invalid track, missing attribute specifier\n", __FUNCTION__); 5.117 + return false; 5.118 + } 5.119 + 5.120 + if(*anim_idx == -1) { 5.121 + // this is the first node we encounter. add a new animation and activate it 5.122 + XFormNode *root = node->get_root(); 5.123 + *anim_idx = root->get_animation_count() + 1; 5.124 + 5.125 + char name[64]; 5.126 + sprintf(name, "anim%03d\n", *anim_idx); 5.127 + root->add_animation(name); 5.128 + root->use_animation(*anim_idx); 5.129 + } else { 5.130 + // make sure this node hierarchy already has this animation, otherwise add it 5.131 + XFormNode *root = node->get_root(); 5.132 + if(root->get_active_animation_index() != *anim_idx) { 5.133 + char name[64]; 5.134 + sprintf(name, "anim%03d\n", *anim_idx); 5.135 + root->add_animation(name); 5.136 + root->use_animation(*anim_idx); 5.137 + } 5.138 + } 5.139 + 5.140 + for(auto key : keys) { 5.141 + switch(type) { 5.142 + case XFormNode::POSITION_TRACK: 5.143 + node->set_position(Vector3(key.val.x, key.val.y, key.val.z), key.tm); 5.144 + break; 5.145 + case XFormNode::ROTATION_TRACK: 5.146 + node->set_rotation(Quaternion(key.val.w, key.val.x, key.val.y, key.val.z), key.tm); 5.147 + break; 5.148 + case XFormNode::SCALING_TRACK: 5.149 + node->set_scaling(Vector3(key.val.x, key.val.y, key.val.z), key.tm); 5.150 + } 5.151 + } 5.152 + return true; 5.153 +} 5.154 + 5.155 +static Key read_key(XMLElement *xml_key) 5.156 +{ 5.157 + Key key; 5.158 + key.tm = LONG_MIN; // initialize to invalid time 5.159 + 5.160 + XMLElement *xml_time = xml_key->FirstChildElement("time"); 5.161 + XMLElement *xml_value = xml_key->FirstChildElement("value"); 5.162 + 5.163 + if(!xml_time || !xml_value) { 5.164 + logmsg(LOG_ERROR, "%s: invalid key, missing either <time> or <value> elements\n", __FUNCTION__); 5.165 + return key; 5.166 + } 5.167 + 5.168 + int ival; 5.169 + if(xml_time->QueryIntAttribute("int", &ival) != XML_NO_ERROR) { 5.170 + logmsg(LOG_ERROR, "%s: invalid time element in <key>\n", __FUNCTION__); 5.171 + return key; 5.172 + } 5.173 + 5.174 + const char *vstr; 5.175 + if((vstr = xml_value->Attribute("float3"))) { 5.176 + if(sscanf(vstr, "%f %f %f", &key.val.x, &key.val.y, &key.val.z) != 3) { 5.177 + logmsg(LOG_ERROR, "%s: invalid float3 value element in <key>: %s\n", __FUNCTION__, vstr); 5.178 + return key; 5.179 + } 5.180 + } else if((vstr = xml_value->Attribute("float4"))) { 5.181 + if(sscanf(vstr, "%f %f %f %f", &key.val.x, &key.val.y, &key.val.z, &key.val.w) != 4) { 5.182 + logmsg(LOG_ERROR, "%s: invalid float4 value element in <key>: %s\n", __FUNCTION__, vstr); 5.183 + return key; 5.184 + } 5.185 + } else { 5.186 + logmsg(LOG_ERROR, "%s: invalid value element in <key>: missing float3 or float4 attributes\n", __FUNCTION__); 5.187 + return key; 5.188 + } 5.189 + 5.190 + key.tm = ival; 5.191 + return key; 5.192 +} 5.193 + 5.194 static Material *read_material(Scene *scn, XMLElement *xml_mtl) 5.195 { 5.196 Material *mtl = new Material;
6.1 --- a/src/xform_node.cc Sun May 18 19:58:47 2014 +0300 6.2 +++ b/src/xform_node.cc Mon May 19 06:42:40 2014 +0300 6.3 @@ -90,6 +90,18 @@ 6.4 return parent; 6.5 } 6.6 6.7 +XFormNode *XFormNode::get_root() 6.8 +{ 6.9 + if(!parent) return this; 6.10 + return parent->get_root(); 6.11 +} 6.12 + 6.13 +const XFormNode *XFormNode::get_root() const 6.14 +{ 6.15 + if(!parent) return this; 6.16 + return parent->get_root(); 6.17 +} 6.18 + 6.19 void XFormNode::add_child(XFormNode *child) 6.20 { 6.21 if(!child || child == this) return; 6.22 @@ -203,6 +215,37 @@ 6.23 return anm_get_active_animation_name(anm); 6.24 } 6.25 6.26 +bool XFormNode::get_timeline_bounds(long *start, long *end) 6.27 +{ 6.28 + long node_start = LONG_MAX; 6.29 + long node_end = LONG_MIN; 6.30 + 6.31 + for(int i=0; i<3; i++) { 6.32 + int nkeys = get_key_count(i); 6.33 + if(nkeys > 0) { 6.34 + long tmp = get_key_time(i, 0); 6.35 + if(tmp < node_start) node_start = tmp; 6.36 + tmp = get_key_time(i, nkeys - 1); 6.37 + if(tmp > node_end) node_end = tmp; 6.38 + } 6.39 + } 6.40 + 6.41 + for(size_t i=0; i<children.size(); i++) { 6.42 + long cstart, cend; 6.43 + if(children[i]->get_timeline_bounds(&cstart, &cend)) { 6.44 + if(cstart < node_start) node_start = cstart; 6.45 + if(cend > node_end) node_end = cend; 6.46 + } 6.47 + } 6.48 + 6.49 + if(node_start != LONG_MAX) { 6.50 + *start = node_start; 6.51 + *end = node_end; 6.52 + return true; 6.53 + } 6.54 + return false; 6.55 +} 6.56 + 6.57 static const int track_type_base[] = {ANM_TRACK_POS_X, ANM_TRACK_ROT_X, ANM_TRACK_SCL_X}; 6.58 static const int track_type_nelem[] = {3, 4, 3}; 6.59
7.1 --- a/src/xform_node.h Sun May 18 19:58:47 2014 +0300 7.2 +++ b/src/xform_node.h Mon May 19 06:42:40 2014 +0300 7.3 @@ -69,6 +69,9 @@ 7.4 virtual XFormNode *get_parent(); 7.5 virtual const XFormNode *get_parent() const; 7.6 7.7 + virtual XFormNode *get_root(); 7.8 + virtual const XFormNode *get_root() const; 7.9 + 7.10 // children management 7.11 virtual void add_child(XFormNode *child); 7.12 virtual void remove_child(XFormNode *child); 7.13 @@ -95,6 +98,8 @@ 7.14 virtual void set_animation_name(const char *name); 7.15 virtual const char *get_animation_name() const; 7.16 7.17 + virtual bool get_timeline_bounds(long *start, long *end); 7.18 + 7.19 // raw keyframe retrieval without interpolation 7.20 // NOTE: trackid parameters correspond to the values of the unnamed enumeration at the top 7.21