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