goat3d

changeset 75:76dea247f75c

in progress
author John Tsiombikas <nuclear@member.fsf.org>
date Thu, 08 May 2014 00:50:16 +0300
parents ab66cdabf6f2
children 9785847d52d4
files goatview/src/goatview.cc goatview/src/goatview.h goatview/src/main.cc src/aabox.cc src/aabox.h src/goat3d_impl.h src/goat3d_readxml.cc src/mesh.h src/node.cc src/node.h src/object.h src/scene.cc
diffstat 12 files changed, 327 insertions(+), 8 deletions(-) [+]
line diff
     1.1 --- a/goatview/src/goatview.cc	Tue May 06 13:26:52 2014 +0300
     1.2 +++ b/goatview/src/goatview.cc	Thu May 08 00:50:16 2014 +0300
     1.3 @@ -1,7 +1,13 @@
     1.4 +#include <GL/glu.h>
     1.5  #include "goatview.h"
     1.6  #include "goat3d.h"
     1.7  
     1.8  goat3d *scene;
     1.9 +QSettings *settings;
    1.10 +
    1.11 +static long anim_time;
    1.12 +static float cam_theta, cam_phi, cam_dist = 8;
    1.13 +
    1.14  
    1.15  GoatView::GoatView()
    1.16  {
    1.17 @@ -12,12 +18,20 @@
    1.18  	statusBar();
    1.19  
    1.20  	setWindowTitle("GoatView");
    1.21 +	resize(settings->value("main/size", QSize(1024, 768)).toSize());
    1.22 +	move(settings->value("main/pos", QPoint(100, 100)).toPoint());
    1.23  }
    1.24  
    1.25  GoatView::~GoatView()
    1.26  {
    1.27  }
    1.28  
    1.29 +void GoatView::closeEvent(QCloseEvent *ev)
    1.30 +{
    1.31 +	settings->setValue("main/size", size());
    1.32 +	settings->setValue("main/pos", pos());
    1.33 +}
    1.34 +
    1.35  bool GoatView::make_menu()
    1.36  {
    1.37  	QMenu *menu_file = menuBar()->addMenu("&File");
    1.38 @@ -120,15 +134,79 @@
    1.39  
    1.40  void GoatViewport::initializeGL()
    1.41  {
    1.42 +	glClearColor(0.1, 0.1, 0.1, 1);
    1.43  }
    1.44  
    1.45  void GoatViewport::resizeGL(int xsz, int ysz)
    1.46  {
    1.47  	glViewport(0, 0, xsz, ysz);
    1.48 +	glMatrixMode(GL_PROJECTION);
    1.49 +	glLoadIdentity();
    1.50 +	gluPerspective(60.0, (float)xsz / (float)ysz, 0.5, 5000.0);
    1.51  }
    1.52  
    1.53 +static void draw_node(goat3d_node *node);
    1.54 +
    1.55  void GoatViewport::paintGL()
    1.56  {
    1.57 -	glClearColor(1, 0, 0, 1);
    1.58 -	glClear(GL_COLOR_BUFFER_BIT);
    1.59 +	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    1.60 +
    1.61 +	glMatrixMode(GL_MODELVIEW);
    1.62 +	glLoadIdentity();
    1.63 +	glTranslatef(0, 0, -cam_dist);
    1.64 +	glRotatef(cam_phi, 1, 0, 0);
    1.65 +	glRotatef(cam_theta, 0, 1, 0);
    1.66 +
    1.67 +	if(scene) {
    1.68 +		int node_count = goat3d_get_node_count(scene);
    1.69 +		for(int i=0; i<node_count; i++) {
    1.70 +			goat3d_node *node = goat3d_get_node(scene, i);
    1.71 +			draw_node(node);
    1.72 +		}
    1.73 +	}
    1.74  }
    1.75 +
    1.76 +static void draw_node(goat3d_node *node)
    1.77 +{
    1.78 +	float xform[16];
    1.79 +	goat3d_get_node_matrix(node, xform, anim_time);
    1.80 +	glMultMatrixf(xform);
    1.81 +
    1.82 +	if(goat3d_get_node_type(node) == GOAT3D_NODE_MESH) {
    1.83 +		goat3d_mesh *mesh = (goat3d_mesh*)goat3d_get_node_object(node);
    1.84 +
    1.85 +		int num_faces = goat3d_get_mesh_face_count(mesh);
    1.86 +		int num_verts = goat3d_get_mesh_attrib_count(mesh, GOAT3D_MESH_ATTR_VERTEX);
    1.87 +
    1.88 +		glEnableClientState(GL_VERTEX_ARRAY);
    1.89 +		glVertexPointer(3, GL_FLOAT, 0, goat3d_get_mesh_attribs(mesh, GOAT3D_MESH_ATTR_VERTEX));
    1.90 +
    1.91 +		float *data;
    1.92 +		if((data = (float*)goat3d_get_mesh_attribs(mesh, GOAT3D_MESH_ATTR_NORMAL))) {
    1.93 +			glEnableClientState(GL_NORMAL_ARRAY);
    1.94 +			glNormalPointer(GL_FLOAT, 0, data);
    1.95 +		}
    1.96 +		if((data = (float*)goat3d_get_mesh_attribs(mesh, GOAT3D_MESH_ATTR_TEXCOORD))) {
    1.97 +			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    1.98 +			glTexCoordPointer(2, GL_FLOAT, 0, data);
    1.99 +		}
   1.100 +
   1.101 +		int *indices;
   1.102 +		if((indices = goat3d_get_mesh_faces(mesh))) {
   1.103 +			glDrawElements(GL_TRIANGLES, num_faces * 3, GL_UNSIGNED_INT, indices);
   1.104 +		} else {
   1.105 +			glDrawArrays(GL_TRIANGLES, 0, num_verts * 3);
   1.106 +		}
   1.107 +
   1.108 +		glDisableClientState(GL_VERTEX_ARRAY);
   1.109 +		glDisableClientState(GL_NORMAL_ARRAY);
   1.110 +		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
   1.111 +	}
   1.112 +
   1.113 +	int num_child = goat3d_get_node_child_count(node);
   1.114 +	for(int i=0; i<num_child; i++) {
   1.115 +		draw_node(goat3d_get_node_child(node, i));
   1.116 +	}
   1.117 +}
   1.118 +
   1.119 +
     2.1 --- a/goatview/src/goatview.h	Tue May 06 13:26:52 2014 +0300
     2.2 +++ b/goatview/src/goatview.h	Thu May 08 00:50:16 2014 +0300
     2.3 @@ -1,15 +1,18 @@
     2.4  #ifndef GOATVIEW_H_
     2.5  #define GOATVIEW_H_
     2.6  
     2.7 +#include <QtCore/QtCore>
     2.8  #include <QtWidgets/QtWidgets>
     2.9  #include <QtOpenGL/QGLWidget>
    2.10  #include "goat3d.h"
    2.11  
    2.12  extern goat3d *scene;
    2.13 +extern QSettings *settings;
    2.14  
    2.15  class GoatView : public QMainWindow {
    2.16  	Q_OBJECT
    2.17  private:
    2.18 +	void closeEvent(QCloseEvent *ev);
    2.19  	bool make_menu();
    2.20  	bool make_dock();
    2.21  	bool make_center();
     3.1 --- a/goatview/src/main.cc	Tue May 06 13:26:52 2014 +0300
     3.2 +++ b/goatview/src/main.cc	Thu May 08 00:50:16 2014 +0300
     3.3 @@ -1,9 +1,47 @@
     3.4  #include <QtWidgets/QtWidgets>
     3.5 +#include <QtCore/QtCore>
     3.6  #include "goatview.h"
     3.7  
     3.8  int main(int argc, char **argv)
     3.9  {
    3.10  	QApplication app(argc, argv);
    3.11 +	app.setOrganizationName("Mutant Stargoat");
    3.12 +	app.setOrganizationDomain("mutantstargoat.com");
    3.13 +	app.setApplicationName("GoatView");
    3.14 +	settings = new QSettings;
    3.15 +
    3.16 +	QCommandLineParser argparse;
    3.17 +	argparse.addHelpOption();
    3.18 +
    3.19 +	argparse.addPositionalArgument("scene", "scene file to open");
    3.20 +	argparse.addOption(QCommandLineOption("a", "add animation file"));
    3.21 +	argparse.process(app);
    3.22 +
    3.23 +	const QStringList &args = argparse.positionalArguments();
    3.24 +	if(!args.isEmpty()) {
    3.25 +		if(args.count() > 1) {
    3.26 +			fprintf(stderr, "please specify at most one scene file to open\n");
    3.27 +			return 1;
    3.28 +		}
    3.29 +		std::string fname = args.at(0).toStdString();
    3.30 +		printf("loading scene file: %s ...\n", fname.c_str());
    3.31 +		if(!(scene = goat3d_create()) || goat3d_load(scene, fname.c_str())) {
    3.32 +			fprintf(stderr, "failed to load scene: %s\n", fname.c_str());
    3.33 +			return 1;
    3.34 +		}
    3.35 +	}
    3.36 +
    3.37 +	const QStringList &anims = argparse.values("a");
    3.38 +	QStringList::const_iterator it = anims.begin();
    3.39 +	while(it != anims.end()) {
    3.40 +		std::string fname = it++->toStdString();
    3.41 +		printf("loading animation file: %s ...\n", fname.c_str());
    3.42 +
    3.43 +		if(goat3d_load_anim(scene, fname.c_str()) == -1) {
    3.44 +			fprintf(stderr, "failed to load animation: %s\n", fname.c_str());
    3.45 +			return 1;
    3.46 +		}
    3.47 +	}
    3.48  
    3.49  	GoatView gview;
    3.50  	gview.show();
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/src/aabox.cc	Thu May 08 00:50:16 2014 +0300
     4.3 @@ -0,0 +1,27 @@
     4.4 +#include <float.h>
     4.5 +#include <algorithm>
     4.6 +#include "aabox.h"
     4.7 +
     4.8 +using namespace g3dimpl;
     4.9 +
    4.10 +AABox::AABox()
    4.11 +	: bmin(FLT_MAX, FLT_MAX, FLT_MAX), bmax(-FLT_MAX, -FLT_MAX, -FLT_MAX)
    4.12 +{
    4.13 +}
    4.14 +
    4.15 +AABox::AABox(const Vector3 &b0, const Vector3 &b1)
    4.16 +	: bmin(b0), bmax(b1)
    4.17 +{
    4.18 +}
    4.19 +
    4.20 +AABox g3dimpl::aabox_union(const AABox &a, const AABox &b)
    4.21 +{
    4.22 +	Vector3 bmin, bmax;
    4.23 +
    4.24 +	for(int i=0; i<3; i++) {
    4.25 +		bmin[i] = std::min(a.bmin[i], b.bmin[i]);
    4.26 +		bmax[i] = std::max(a.bmax[i], b.bmax[i]);
    4.27 +	}
    4.28 +
    4.29 +	return AABox(bmin, bmax);
    4.30 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/src/aabox.h	Thu May 08 00:50:16 2014 +0300
     5.3 @@ -0,0 +1,20 @@
     5.4 +#ifndef AABOX_H_
     5.5 +#define AABOX_H_
     5.6 +
     5.7 +#include <vmath/vmath.h>
     5.8 +
     5.9 +namespace g3dimpl {
    5.10 +
    5.11 +class AABox {
    5.12 +public:
    5.13 +	Vector3 bmin, bmax;
    5.14 +
    5.15 +	AABox();
    5.16 +	AABox(const Vector3 &b0, const Vector3 &b1);
    5.17 +};
    5.18 +
    5.19 +AABox aabox_union(const AABox &a, const AABox &b);
    5.20 +
    5.21 +}
    5.22 +
    5.23 +#endif	// AABOX_H_
     6.1 --- a/src/goat3d_impl.h	Tue May 06 13:26:52 2014 +0300
     6.2 +++ b/src/goat3d_impl.h	Thu May 08 00:50:16 2014 +0300
     6.3 @@ -26,6 +26,7 @@
     6.4  #include "camera.h"
     6.5  #include "material.h"
     6.6  #include "node.h"
     6.7 +#include "aabox.h"
     6.8  
     6.9  namespace g3dimpl {
    6.10  class Scene;
    6.11 @@ -65,6 +66,9 @@
    6.12  	std::vector<Camera*> cameras;
    6.13  	std::vector<Node*> nodes;
    6.14  
    6.15 +	mutable AABox bbox;
    6.16 +	mutable bool bbox_valid;
    6.17 +
    6.18  public:
    6.19  	goat3d *goat;
    6.20  
    6.21 @@ -104,6 +108,8 @@
    6.22  	Node *get_node(const char *name) const;
    6.23  	int get_node_count() const;
    6.24  
    6.25 +	const AABox &get_bounds() const;
    6.26 +
    6.27  	bool load(goat3d_io *io);
    6.28  	bool save(goat3d_io *io) const;
    6.29  
     7.1 --- a/src/goat3d_readxml.cc	Tue May 06 13:26:52 2014 +0300
     7.2 +++ b/src/goat3d_readxml.cc	Thu May 08 00:50:16 2014 +0300
     7.3 @@ -16,6 +16,8 @@
     7.4  along with this program.  If not, see <http://www.gnu.org/licenses/>.
     7.5  */
     7.6  #include <stdio.h>
     7.7 +#include <map>
     7.8 +#include <string>
     7.9  #include "goat3d.h"
    7.10  #include "goat3d_impl.h"
    7.11  #include "tinyxml2.h"
    7.12 @@ -27,7 +29,8 @@
    7.13  static Material *read_material(Scene *scn, XMLElement *xml_mtl);
    7.14  static const char *read_material_attrib(MaterialAttrib *attr, XMLElement *xml_attr);
    7.15  static Mesh *read_mesh(Scene *scn, XMLElement *xml_mesh);
    7.16 -static std::string get_name(XMLElement *node, int idx);
    7.17 +static Node *read_node(Scene *scn, XMLElement *xml_node, std::map<Node*, std::string> &linkmap);
    7.18 +static std::string get_name(XMLElement *node, int idx, const char *def_prefix);
    7.19  
    7.20  bool Scene::loadxml(goat3d_io *io)
    7.21  {
    7.22 @@ -79,6 +82,29 @@
    7.23  		elem = elem->NextSiblingElement("mesh");
    7.24  	}
    7.25  
    7.26 +	// get all nodes
    7.27 +	std::map<Node*, std::string> linkmap;
    7.28 +
    7.29 +	elem = root->FirstChildElement("node");
    7.30 +	while(elem) {
    7.31 +		Node *node = read_node(this, elem, linkmap);
    7.32 +		if(node) {
    7.33 +			add_node(node);
    7.34 +		}
    7.35 +		elem = elem->NextSiblingElement("node");
    7.36 +	}
    7.37 +
    7.38 +	// link up all the nodes in the hierarchy
    7.39 +	for(size_t i=0; i<nodes.size(); i++) {
    7.40 +		std::string parent_name = linkmap[nodes[i]];
    7.41 +		if(!parent_name.empty()) {
    7.42 +			Node *parent = get_node(parent_name.c_str());
    7.43 +			if(parent) {
    7.44 +				parent->add_child(nodes[i]);
    7.45 +			}
    7.46 +		}
    7.47 +	}
    7.48 +
    7.49  	delete [] buf;
    7.50  	return true;
    7.51  }
    7.52 @@ -130,7 +156,7 @@
    7.53  static Material *read_material(Scene *scn, XMLElement *xml_mtl)
    7.54  {
    7.55  	Material *mtl = new Material;
    7.56 -	mtl->name = get_name(xml_mtl, scn->get_material_count());
    7.57 +	mtl->name = get_name(xml_mtl, scn->get_material_count(), "material");
    7.58  
    7.59  	// get all the material attributes in turn
    7.60  	XMLElement *elem = xml_mtl->FirstChildElement("attr");
    7.61 @@ -188,7 +214,7 @@
    7.62  static Mesh *read_mesh(Scene *scn, XMLElement *xml_mesh)
    7.63  {
    7.64  	Mesh *mesh = new Mesh;
    7.65 -	mesh->name = get_name(xml_mesh, scn->get_mesh_count());
    7.66 +	mesh->name = get_name(xml_mesh, scn->get_mesh_count(), "mesh");
    7.67  
    7.68  	XMLElement *elem;
    7.69  	if((elem = xml_mesh->FirstChildElement("material"))) {
    7.70 @@ -226,7 +252,74 @@
    7.71  	return mesh;
    7.72  }
    7.73  
    7.74 -static std::string get_name(XMLElement *node, int idx)
    7.75 +static Node *read_node(Scene *scn, XMLElement *xml_node, std::map<Node*, std::string> &linkmap)
    7.76 +{
    7.77 +	Node *node = new Node;
    7.78 +	node->set_name(get_name(xml_node, scn->get_node_count(), "node").c_str());
    7.79 +
    7.80 +	XMLElement *elem;
    7.81 +	if((elem = xml_node->FirstChildElement("parent"))) {
    7.82 +		const char *pname = elem->Attribute("string");
    7.83 +		if(pname) {
    7.84 +			linkmap[node] = pname;
    7.85 +		}
    7.86 +	}
    7.87 +
    7.88 +	if((elem = xml_node->FirstChildElement("mesh"))) {
    7.89 +		Mesh *mesh = scn->get_mesh(elem->Attribute("string"));
    7.90 +		if(mesh) {
    7.91 +			node->set_object(mesh);
    7.92 +		}
    7.93 +	} else if((elem = xml_node->FirstChildElement("light"))) {
    7.94 +		Light *lt = scn->get_light(elem->Attribute("string"));
    7.95 +		if(lt) {
    7.96 +			node->set_object(lt);
    7.97 +		}
    7.98 +	} else if((elem = xml_node->FirstChildElement("camera"))) {
    7.99 +		Camera *cam = scn->get_camera(elem->Attribute("string"));
   7.100 +		if(cam) {
   7.101 +			node->set_object(cam);
   7.102 +		}
   7.103 +	}
   7.104 +
   7.105 +	float vec[4];
   7.106 +	if((elem = xml_node->FirstChildElement("pos"))) {
   7.107 +		const char *val = elem->Attribute("float3");
   7.108 +		if(val && sscanf(val, "%f %f %f", vec, vec + 1, vec + 2) == 3) {
   7.109 +			node->set_position(Vector3(val[0], val[1], val[2]));
   7.110 +		} else {
   7.111 +			logmsg(LOG_ERROR, "node %s: invalid position tag\n", node->get_name());
   7.112 +		}
   7.113 +	}
   7.114 +	if((elem = xml_node->FirstChildElement("rot"))) {
   7.115 +		const char *val = elem->Attribute("float4");
   7.116 +		if(val && sscanf(val, "%f %f %f %f", vec, vec + 1, vec + 2, vec + 3) == 4) {
   7.117 +			node->set_rotation(Quaternion(vec[3], Vector3(vec[0], vec[1], vec[2])));
   7.118 +		} else {
   7.119 +			logmsg(LOG_ERROR, "node %s: invalid rotation tag\n", node->get_name());
   7.120 +		}
   7.121 +	}
   7.122 +	if((elem = xml_node->FirstChildElement("scale"))) {
   7.123 +		const char *val = elem->Attribute("float3");
   7.124 +		if(val && sscanf(val, "%f %f %f", vec, vec + 1, vec + 2) == 3) {
   7.125 +			node->set_scaling(Vector3(vec[0], vec[1], vec[2]));
   7.126 +		} else {
   7.127 +			logmsg(LOG_ERROR, "node %s: invalid scaling tag\n", node->get_name());
   7.128 +		}
   7.129 +	}
   7.130 +	if((elem = xml_node->FirstChildElement("pivot"))) {
   7.131 +		const char *val = elem->Attribute("float3");
   7.132 +		if(val && sscanf(val, "%f %f %f", vec, vec + 1, vec + 2) == 3) {
   7.133 +			node->set_pivot(Vector3(vec[0], vec[1], vec[2]));
   7.134 +		} else {
   7.135 +			logmsg(LOG_ERROR, "node %s: invalid pivot tag\n", node->get_name());
   7.136 +		}
   7.137 +	}
   7.138 +
   7.139 +	return node;
   7.140 +}
   7.141 +
   7.142 +static std::string get_name(XMLElement *node, int idx, const char *def_prefix)
   7.143  {
   7.144  	char buf[64];
   7.145  	const char *name = 0;
   7.146 @@ -237,7 +330,7 @@
   7.147  	}
   7.148  
   7.149  	if(!name) {
   7.150 -		sprintf(buf, "mesh%04d", idx);
   7.151 +		sprintf(buf, "%s%04d", def_prefix, idx);
   7.152  		name = buf;
   7.153  	}
   7.154  
     8.1 --- a/src/mesh.h	Tue May 06 13:26:52 2014 +0300
     8.2 +++ b/src/mesh.h	Thu May 08 00:50:16 2014 +0300
     8.3 @@ -60,6 +60,8 @@
     8.4  	void set_material(Material *mat);
     8.5  	Material *get_material();
     8.6  	const Material *get_material() const;
     8.7 +
     8.8 +	AABox get_bounds() const;
     8.9  };
    8.10  
    8.11  }	// namespace g3dimpl
     9.1 --- a/src/node.cc	Tue May 06 13:26:52 2014 +0300
     9.2 +++ b/src/node.cc	Thu May 08 00:50:16 2014 +0300
     9.3 @@ -24,15 +24,18 @@
     9.4  Node::Node()
     9.5  {
     9.6  	obj = 0;
     9.7 +	bbox_valid = false;
     9.8  }
     9.9  
    9.10  void Node::set_object(Object *obj)
    9.11  {
    9.12  	this->obj = obj;
    9.13 +	bbox_valid = false;
    9.14  }
    9.15  
    9.16  Object *Node::get_object()
    9.17  {
    9.18 +	bbox_valid = false;
    9.19  	return obj;
    9.20  }
    9.21  
    9.22 @@ -41,6 +44,20 @@
    9.23  	return obj;
    9.24  }
    9.25  
    9.26 +const AABox &Node::get_bounds() const
    9.27 +{
    9.28 +	if(!bbox_valid) {
    9.29 +		bbox = obj ? obj->get_bounds() : AABox();
    9.30 +
    9.31 +		for(int i=0; i<get_children_count(); i++) {
    9.32 +			bbox = aabox_union(bbox, ((Node*)get_child(i))->get_bounds());
    9.33 +		}
    9.34 +		bbox_valid = true;
    9.35 +	}
    9.36 +
    9.37 +	return bbox;
    9.38 +}
    9.39 +
    9.40  void g3dimpl::delete_node_tree(Node *n)
    9.41  {
    9.42  	if(!n) return;
    10.1 --- a/src/node.h	Tue May 06 13:26:52 2014 +0300
    10.2 +++ b/src/node.h	Thu May 08 00:50:16 2014 +0300
    10.3 @@ -20,6 +20,7 @@
    10.4  
    10.5  #include "xform_node.h"
    10.6  #include "object.h"
    10.7 +#include "aabox.h"
    10.8  
    10.9  namespace g3dimpl {
   10.10  
   10.11 @@ -27,12 +28,17 @@
   10.12  private:
   10.13  	Object *obj;
   10.14  
   10.15 +	mutable AABox bbox;
   10.16 +	mutable bool bbox_valid;
   10.17 +
   10.18  public:
   10.19  	Node();
   10.20  
   10.21  	void set_object(Object *obj);
   10.22  	Object *get_object();
   10.23  	const Object *get_object() const;
   10.24 +
   10.25 +	const AABox &get_bounds() const;
   10.26  };
   10.27  
   10.28  void delete_node_tree(Node *n);
    11.1 --- a/src/object.h	Tue May 06 13:26:52 2014 +0300
    11.2 +++ b/src/object.h	Thu May 08 00:50:16 2014 +0300
    11.3 @@ -20,6 +20,9 @@
    11.4  
    11.5  #include <string>
    11.6  #include <vmath/vmath.h>
    11.7 +#include "aabox.h"
    11.8 +
    11.9 +namespace g3dimpl {
   11.10  
   11.11  class Object {
   11.12  public:
   11.13 @@ -27,8 +30,14 @@
   11.14  
   11.15  	Vector3 pos;
   11.16  	Quaternion rot;
   11.17 +	Vector3 scale;
   11.18  
   11.19 -	virtual ~Object() {};
   11.20 +	Object() : scale(1, 1, 1) {}
   11.21 +	virtual ~Object() {}
   11.22 +
   11.23 +	virtual AABox get_bounds(const Matrix4x4 &xform) const { return AABox(); }
   11.24  };
   11.25  
   11.26 +}
   11.27 +
   11.28  #endif	// OBJECT_H_
    12.1 --- a/src/scene.cc	Tue May 06 13:26:52 2014 +0300
    12.2 +++ b/src/scene.cc	Thu May 08 00:50:16 2014 +0300
    12.3 @@ -26,6 +26,7 @@
    12.4  	: name("unnamed"), ambient(0.05, 0.05, 0.05)
    12.5  {
    12.6  	goat = 0;
    12.7 +	bbox_valid = false;
    12.8  }
    12.9  
   12.10  Scene::~Scene()
   12.11 @@ -61,6 +62,7 @@
   12.12  	nodes.clear();
   12.13  
   12.14  	name = "unnamed";
   12.15 +	bbox_valid = false;
   12.16  }
   12.17  
   12.18  void Scene::set_name(const char *name)
   12.19 @@ -222,6 +224,24 @@
   12.20  	return (int)nodes.size();
   12.21  }
   12.22  
   12.23 +const AABox &Scene::get_bounds() const
   12.24 +{
   12.25 +	if(!bbox_valid) {
   12.26 +		bbox = AABox();
   12.27 +
   12.28 +		for(size_t i=0; i<nodes.size(); i++) {
   12.29 +			if(nodes[i]->get_parent()) {
   12.30 +				continue;
   12.31 +			}
   12.32 +
   12.33 +			bbox = aabox_union(bbox, nodes[i]->get_bounds());
   12.34 +		}
   12.35 +		bbox_valid = true;
   12.36 +	}
   12.37 +
   12.38 +	return bbox;
   12.39 +}
   12.40 +
   12.41  // Scene::load is defined in goat3d_read.cc
   12.42  // Scene::loadxml is defined in goat3d_readxml.cc
   12.43  // Scene::save is defined in goat3d_write.cc