kd-tree building - check all axes for best split, add additional shape-bbox check pyrit
authorRadek Brich <radek.brich@devl.cz>
Sun, 20 Apr 2008 16:48:24 +0200
branchpyrit
changeset 72 7c3f38dff082
parent 71 4fedf7290929
child 73 a5127346fbcd
kd-tree building - check all axes for best split, add additional shape-bbox check extent Container bounds by Eps to fix invisible triangles on borders new Camera constructor with more intuitive lookat/up vectors fix camera axes (mirrored images) better camera angle-of-view change capitalization of addShape and addLight
TODO
ccdemos/common_ply.h
ccdemos/common_sdl.h
ccdemos/realtime.cc
ccdemos/realtime_bunny.cc
ccdemos/realtime_dragon.cc
ccdemos/spheres_shadow.cc
ccdemos/textures.cc
demos/SConscript
demos/lworeader.py
demos/render_nff.py
demos/vector.py
include/raytracer.h
include/scene.h
src/container.cc
src/kdtree.cc
src/raytracer.cc
src/raytracermodule.cc
--- a/TODO	Sat Apr 19 18:00:27 2008 +0200
+++ b/TODO	Sun Apr 20 16:48:24 2008 +0200
@@ -10,7 +10,7 @@
  * kd-tree:
    - optimize structures
    - optimize construction: use box-shape intersection instead of bounding boxes of shapes
-   - optimize traversal -- no std::vector
+   - optimize traversal -- no std::vector, no dynamic memory allocation
    - save/load
  * textures (3D procedural, pixmaps)
  * update Python binding: Camera, other new classes
@@ -18,7 +18,7 @@
  * absorbtion of refracted rays in dense materials (can be computed using shape distance and some 'absorbance' constant)
  * implement efficient AABB-ray intersection using Plucker coordinates
  * generalization: Camera "shader" (ray generator), surface shader and maybe light & background shaders
-
+ * unify capitalization of method names in C++ and Python
 
 New Classes?
 ============
--- a/ccdemos/common_ply.h	Sat Apr 19 18:00:27 2008 +0200
+++ b/ccdemos/common_ply.h	Sun Apr 20 16:48:24 2008 +0200
@@ -58,7 +58,7 @@
 		}
 		f >> v1 >> v2 >> v3;
 		face = new Triangle(vertices.at(v1), vertices.at(v3), vertices.at(v2), mat);
-		rt.addshape(face);
+		rt.addShape(face);
 
 		normals.at(v1) += face->getNormal();
 		vertex_face_num.at(v1)++;
--- a/ccdemos/common_sdl.h	Sat Apr 19 18:00:27 2008 +0200
+++ b/ccdemos/common_sdl.h	Sun Apr 20 16:48:24 2008 +0200
@@ -94,11 +94,11 @@
 						break;
 					}
 					if (event.key.keysym.sym == SDLK_LEFT) {
-						roty = -0.01;
+						roty = +0.01;
 						break;
 					}
 					if (event.key.keysym.sym == SDLK_RIGHT) {
-						roty = +0.01;
+						roty = -0.01;
 						break;
 					}
 					if (event.key.keysym.sym == SDLK_DOWN) {
--- a/ccdemos/realtime.cc	Sat Apr 19 18:00:27 2008 +0200
+++ b/ccdemos/realtime.cc	Sun Apr 20 16:48:24 2008 +0200
@@ -16,16 +16,16 @@
 
 	Light light1(Vector3(2.0, -5.0, -5.0), Colour(0.7, 0.3, 0.6));
 	light1.castShadows(false);
-	rt.addlight(&light1);
+	rt.addLight(&light1);
 
 	Light light2(Vector3(-2.0, 10.0, 2.0), Colour(0.4, 0.6, 0.3));
 	light2.castShadows(false);
-	rt.addlight(&light2);
+	rt.addLight(&light2);
 
 	Material mat_sph(Colour(1.0, 1.0, 1.0));
 	for (int y=0; y<10; y++)
 		for (int x=0; x<10; x++)
-			rt.addshape(new Sphere(Vector3(x*2-10, (Float)rand()/RAND_MAX*5.0, y*2-10), 0.45, &mat_sph));
+			rt.addShape(new Sphere(Vector3(x*2-10, (Float)rand()/RAND_MAX*5.0, y*2-10), 0.45, &mat_sph));
 
 	rt.setCamera(&cam);
 	cam.setEye(Vector3(0,0,10));
--- a/ccdemos/realtime_bunny.cc	Sat Apr 19 18:00:27 2008 +0200
+++ b/ccdemos/realtime_bunny.cc	Sun Apr 20 16:48:24 2008 +0200
@@ -15,11 +15,11 @@
 
 	Light light1(Vector3(-5.0, 2.0, 8.0), Colour(0.9, 0.3, 0.6));
 	light1.castShadows(false);
-	rt.addlight(&light1);
+	rt.addLight(&light1);
 
 	//Light light2(Vector3(-2.0, 10.0, 2.0), Colour(0.4, 0.6, 0.3));
 	//light2.castShadows(false);
-	//rt.addlight(&light2);
+	//rt.addLight(&light2);
 
 	Material mat(Colour(0.9, 0.9, 0.9));
 	mat.setSmooth(true);
--- a/ccdemos/realtime_dragon.cc	Sat Apr 19 18:00:27 2008 +0200
+++ b/ccdemos/realtime_dragon.cc	Sun Apr 20 16:48:24 2008 +0200
@@ -15,7 +15,7 @@
 
 	Light light1(Vector3(-5.0, 2.0, 8.0), Colour(0.9, 0.3, 0.6));
 	light1.castShadows(false);
-	rt.addlight(&light1);
+	rt.addLight(&light1);
 
 	//Light light2(Vector3(-2.0, 10.0, 2.0), Colour(0.4, 0.6, 0.3));
 	//light2.castShadows(false);
--- a/ccdemos/spheres_shadow.cc	Sat Apr 19 18:00:27 2008 +0200
+++ b/ccdemos/spheres_shadow.cc	Sun Apr 20 16:48:24 2008 +0200
@@ -18,7 +18,7 @@
 	if (lz != 0.0)
 		light.pos.z += lz;
 	if (cf != 0.0)
-		cam.f += cf;
+		cam.F += cf;
 }
 
 void key_callback(int key, int down)
@@ -60,35 +60,35 @@
 	Octree top;
 	rt.setTop(&top);
 
-	rt.addlight(&light);
+	rt.addLight(&light);
 
 	//Light light2;
 	//light2.colour = Colour(0.7, 0.3, 0.6);
-	//rt.addlight(&light2);
+	//rt.addLight(&light2);
 
 	Material mat0a(Colour(0.7, 0.7, 0.7));
 	mat0a. setReflectivity(0.0);
 	Box box(Vector3(-10.0, -1.2, -20.0), Vector3(10.0, -1.0, 0.0), &mat0a);
-	rt.addshape(&box);
+	rt.addShape(&box);
 
 	Material mat0b(Colour(0.1, 0.7, 0.8));
 	mat0b.setReflectivity(0.7);
 	Box box2(Vector3(-10.0, -1.2, -20.0), Vector3(10.0, 10.0, -20.2), &mat0b);
-	rt.addshape(&box2);
+	rt.addShape(&box2);
 
 	Material mat1(Colour(1.0, 0.0, 0.0));
 	Sphere bigsphere(Vector3(3.0, 2.0, -7.0), 3.0, &mat1);
-	rt.addshape(&bigsphere);
+	rt.addShape(&bigsphere);
 
 	Material mat2(Colour(0.0, 1.0, 0.0));
 	Sphere smallsphere(Vector3(-5.5, 1.5, -8.0), 2.0, &mat2);
-	rt.addshape(&smallsphere);
+	rt.addShape(&smallsphere);
 
 	Material mat3(Colour(0.0, 0.0, 1.0));
 	mat3.setReflectivity(0.1);
 	mat3.setTransmissivity(0.8, 1.5);
 	Sphere tinysphere(Vector3(-1.2, 0.0, -2.0), 0.7, &mat3);
-	rt.addshape(&tinysphere);
+	rt.addShape(&tinysphere);
 
 	top.optimize();
 
--- a/ccdemos/textures.cc	Sat Apr 19 18:00:27 2008 +0200
+++ b/ccdemos/textures.cc	Sun Apr 20 16:48:24 2008 +0200
@@ -4,7 +4,7 @@
 #include "image.h"
 #include "common_sdl.h"
 
-Camera cam;
+Camera cam(Vector3(0.,6.,6.), Vector3(0.,2.,-7.), Vector3(0.,0.,-1.));
 Light light(Vector3(-2.0, 10.0, -2.0), Colour(0.9, 0.9, 0.9));
 
 Float lx, ly, lz, cf;
@@ -149,7 +149,7 @@
 	if (lz != 0.0)
 		light.pos.z += lz;
 	if (cf != 0.0)
-		cam.f += cf;
+		cam.F += cf;
 }
 
 void key_callback(int key, int down)
@@ -189,62 +189,63 @@
 	Raytracer rt;
 
 	Octree top;
+	rt.setCamera(&cam);
 	rt.setTop(&top);
 
-	rt.addlight(&light);
+	rt.addLight(&light);
 	light.castShadows(false);
 
 	Material mat0a(Colour(0.7, 0.7, 0.7));
 	mat0a. setReflectivity(0.0);
 	Box box(Vector3(-12.0, -1.2, -20.0), Vector3(12.0, -1.0, 0.0), &mat0a);
-	rt.addshape(&box);
+	rt.addShape(&box);
 
 	Material mat0b(Colour(0.1, 0.7, 0.8));
 	mat0b.setReflectivity(0.7);
 	Box box2(Vector3(-12.0, -1.2, -10.0), Vector3(12.0, 10.0, -10.2), &mat0b);
-	rt.addshape(&box2);
+	rt.addShape(&box2);
 
 	// spheres
 	Material mat1(Colour(1.0, 1.0, 1.0));
-	mat1.texture = new PlanarMapTexture(Vector3(4.0, 2.0, -7.0));
-	Sphere sphere1(Vector3(4.0, 2.0, -7.0), 1.0, &mat1);
-	rt.addshape(&sphere1);
+	mat1.texture = new PlanarMapTexture(Vector3(-4.5, 2.0, -7.0));
+	Sphere sphere1(Vector3(-4.5, 2.0, -7.0), 1.0, &mat1);
+	rt.addShape(&sphere1);
 
 	Material mat2(Colour(1.0, 1.0, 1.0));
-	mat2.texture = new CubicMapTexture(Vector3(1.0, 2.0, -7.0));
-	Sphere sphere2(Vector3(1.0, 2.0, -7.0), 1.0, &mat2);
-	rt.addshape(&sphere2);
+	mat2.texture = new CubicMapTexture(Vector3(-1.5, 2.0, -7.0));
+	Sphere sphere2(Vector3(-1.5, 2.0, -7.0), 1.0, &mat2);
+	rt.addShape(&sphere2);
 
 	Material mat3(Colour(1.0, 1.0, 1.0));
-	mat3.texture = new CylinderMapTexture(Vector3(-2.0, 2.0, -7.0));
-	Sphere sphere3(Vector3(-2.0, 2.0, -7.0), 1.0, &mat3);
-	rt.addshape(&sphere3);
+	mat3.texture = new CylinderMapTexture(Vector3(1.5, 2.0, -7.0));
+	Sphere sphere3(Vector3(1.5, 2.0, -7.0), 1.0, &mat3);
+	rt.addShape(&sphere3);
 
 	Material mat4(Colour(1.0, 1.0, 1.0));
-	mat4.texture = new SphereMapTexture(Vector3(-5.0, 2.0, -7.0));
-	Sphere sphere4(Vector3(-5.0, 2.0, -7.0), 1.0, &mat4);
-	rt.addshape(&sphere4);
+	mat4.texture = new SphereMapTexture(Vector3(4.5, 2.0, -7.0));
+	Sphere sphere4(Vector3(4.5, 2.0, -7.0), 1.0, &mat4);
+	rt.addShape(&sphere4);
 
 	// cubes
 	Material mat5(Colour(1.0, 1.0, 1.0));
-	mat5.texture = new PlanarMapTexture(Vector3(4.0, 0.0, -7.0));
-	Box cube1(Vector3(4.0, 0.0, -7.0)-1.0, Vector3(4.0, 0.0, -7.0)+1.0, &mat5);
-	rt.addshape(&cube1);
+	mat5.texture = new PlanarMapTexture(Vector3(-4.5, 0.0, -7.0));
+	Box cube1(Vector3(-4.5, 0.0, -7.0)-1.0, Vector3(-4.5, 0.0, -7.0)+1.0, &mat5);
+	rt.addShape(&cube1);
 
 	Material mat6(Colour(1.0, 1.0, 1.0));
-	mat6.texture = new CubicMapTexture(Vector3(1.0, 0.0, -7.0));
-	Box cube2(Vector3(1.0, 0.0, -7.0)-1.0, Vector3(1.0, 0.0, -7.0)+1.0, &mat6);
-	rt.addshape(&cube2);
+	mat6.texture = new CubicMapTexture(Vector3(-1.5, 0.0, -7.0));
+	Box cube2(Vector3(-1.5, 0.0, -7.0)-1.0, Vector3(-1.5, 0.0, -7.0)+1.0, &mat6);
+	rt.addShape(&cube2);
 
 	Material mat7(Colour(1.0, 1.0, 1.0));
-	mat7.texture = new CylinderMapTexture(Vector3(-2.0, 0.0, -7.0));
-	Box cube3(Vector3(-2.0, 0.0, -7.0)-1.0, Vector3(-2.0, 0.0, -7.0)+1.0, &mat7);
-	rt.addshape(&cube3);
+	mat7.texture = new CylinderMapTexture(Vector3(1.5, 0.0, -7.0));
+	Box cube3(Vector3(1.5, 0.0, -7.0)-1.0, Vector3(1.5, 0.0, -7.0)+1.0, &mat7);
+	rt.addShape(&cube3);
 
 	Material mat8(Colour(1.0, 1.0, 1.0));
-	mat8.texture = new SphereMapTexture(Vector3(-5.0, 0.0, -7.0));
-	Box cube4(Vector3(-5.0, 0.0, -7.0)-1.0, Vector3(-5.0, 0.0, -7.0)+1.0, &mat8);
-	rt.addshape(&cube4);
+	mat8.texture = new SphereMapTexture(Vector3(4.5, 0.0, -7.0));
+	Box cube4(Vector3(4.5, 0.0, -7.0)-1.0, Vector3(4.5, 0.0, -7.0)+1.0, &mat8);
+	rt.addShape(&cube4);
 
 	mat1.setReflectivity(0);
 	mat2.setReflectivity(0);
@@ -257,12 +258,6 @@
 
 	top.optimize();
 
-	cam.setEye(Vector3(-0.530505, 11.0964, 11.2208));
-	cam.p = Vector3(-4.18144e-08, -0.461779, -0.886995);
-	cam.u = Vector3(-1, 0, 6.3393e-11);
-	cam.v = Vector3(3.19387e-08, 0.886995, -0.461779);
-	rt.setCamera(&cam);
-
 	w = 1024;
 	h = 600;
 
--- a/demos/SConscript	Sat Apr 19 18:00:27 2008 +0200
+++ b/demos/SConscript	Sun Apr 20 16:48:24 2008 +0200
@@ -6,7 +6,8 @@
 	'boxes.py', 'buddha.py', 'bunny.py', 'car.py', 'dragon.py',
 	'spheres_ao.py', 'spheres_glass.py', 'spheres_shadow.py',
 	'triangles_monkey.py', 'triangles_sphere.py',
-	'objreader.py', 'plyreader.py', 'lworeader.py']
+	'objreader.py', 'plyreader.py', 'lworeader.py',
+	'vector.py', 'render_nff.py']
 
 l = []
 for file in files:
--- a/demos/lworeader.py	Sat Apr 19 18:00:27 2008 +0200
+++ b/demos/lworeader.py	Sun Apr 20 16:48:24 2008 +0200
@@ -3,6 +3,7 @@
 from math import *
 from struct import *
 from raytracer import Triangle, NormalVertex, Material
+from vector import dot
 
 def read_int4(f):
 	return unpack('>i', f.read(4))[0]
@@ -153,12 +154,6 @@
 		(ID,size) = read_chunk(f)
 	return (points, faces, tags, surfaces)
 
-def dot(a,b):
-	sum = 0
-	for i in range(min(len(a),len(b))):
-		sum += a[i]*b[i]
-	return sum
-
 def LoadLightwaveLwoFile(rt, filename, scale=(1,1,1), trans=(0,0,0)):
 	if (type(scale) == float or type(scale) == int):
 		scale = (scale,)*3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/render_nff.py	Sun Apr 20 16:48:24 2008 +0200
@@ -0,0 +1,81 @@
+#!/usr/bin/python
+
+# read nff data from standart input and render image to render_nff.png
+# see http://tog.acm.org/resources/SPD/
+# only spheres and triangles are implemented
+
+from raytracer import Raytracer, Camera, Light, Material, Sphere, NormalVertex, Triangle
+from math import pi
+import sys, Image
+
+rt = Raytracer()
+imagesize = (800, 600)
+
+mat = Material(colour=(1.0, 1.0, 1.0))
+
+f = sys.stdin
+while True:
+	line = f.readline()
+	if line == "":
+		break;
+	ln = line.split()
+	if ln[0] == 'v':	# Viewpoint location
+		# from
+		ln = f.readline().split()
+		assert ln[0] == 'from'
+		eye = (float(ln[1]), float(ln[2]), float(ln[3]))
+		# at
+		ln = f.readline().split()
+		assert ln[0] == 'at'
+		lookat = (float(ln[1]), float(ln[2]), float(ln[3]))
+		# up
+		ln = f.readline().split()
+		assert ln[0] == 'up'
+		up = (float(ln[1]), float(ln[2]), float(ln[3]))
+		# angle
+		ln = f.readline().split()
+		assert ln[0] == 'angle'
+		angle = float(ln[1])
+		# hither
+		ln = f.readline().split()
+		assert ln[0] == 'hither'
+		hither = float(ln[1])
+		# resolution
+		ln = f.readline().split()
+		assert ln[0] == 'resolution'
+		imagesize = (int(ln[1]), int(ln[2]))
+		# set camera as specified
+		cam = Camera(eye=eye, lookat=lookat, up=up)
+		cam.setAngle(angle/180*pi)
+		rt.setcamera(cam)
+	elif ln[0] == 'b':	# Background color
+		rt.setbgcolour((float(ln[1]), float(ln[2]), float(ln[3])))
+	elif ln[0] == 'l':	# Light
+		pos = (float(ln[1]), float(ln[2]), float(ln[3]))
+		rt.addlight(Light(position=pos))
+	elif ln[0] == 'f':	# Fill color and shading parameters
+		colour = (float(ln[1]), float(ln[2]), float(ln[3]))
+		mat = Material(colour=colour)
+		mat.setPhong(0,float(ln[4]),float(ln[5]),float(ln[6]))
+		mat.setTransmissivity(float(ln[7]),float(ln[8]))
+	elif ln[0] == 's':	# Sphere
+		center = (float(ln[1]), float(ln[2]), float(ln[3]))
+		radius = float(ln[4])
+		rt.addshape(Sphere(centre=center, radius=radius, material=mat))
+	elif ln[0] == 'p':	# Polygon
+		vertex_count = int(ln[1])
+		vertices = []
+		for i in range(vertex_count):
+			ln = f.readline().split()
+			vertex = (float(ln[0]), float(ln[1]), float(ln[2]))
+			vertices.append(NormalVertex(vertex))
+		rt.addshape(Triangle(vertices[0], vertices[1], vertices[2], mat))
+		for i in range(vertex_count)[3:]:
+			rt.addshape(Triangle(vertices[0], vertices[i-1], vertices[i], mat))
+	else:
+		print "Not implemented:", line
+f.close()
+
+data = rt.render(imagesize)
+img = Image.fromstring("RGB", imagesize, data)
+img.save('render_nff.png')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/vector.py	Sun Apr 20 16:48:24 2008 +0200
@@ -0,0 +1,24 @@
+from math import *
+
+def dot(a,b):
+	sum = 0
+	for i in range(min(len(a),len(b))):
+		sum += a[i]*b[i]
+	return sum
+
+def cross(a,b):
+	return (
+		a[1]*b[2] - a[2]*b[1],
+		a[2]*b[0] - a[0]*b[2],
+		a[0]*b[1] - a[1]*b[0]
+	)
+
+def unit(a):
+	m = mag(a)
+	return (a[0]/m, a[1]/m, a[2]/m)
+
+def mag(a):
+	return sqrt(mag2(a))
+
+def mag2(a):
+	return a[0]*a[0] + a[1]*a[1] + a[2]*a[2]
--- a/include/raytracer.h	Sat Apr 19 18:00:27 2008 +0200
+++ b/include/raytracer.h	Sun Apr 20 16:48:24 2008 +0200
@@ -70,7 +70,7 @@
 
 	static void *raytrace_worker(void *d);
 public:
-	Raytracer(): top(NULL), camera(NULL), lights(), bg_colour(0.0, 0.0, 0.0),
+	Raytracer(): top(NULL), camera(NULL), lights(), bg_colour(0., 0., 0.),
 		ao_samples(0), num_threads(2), max_depth(3)
 	{
 		pthread_mutex_init(&sample_queue_mutex, NULL);
@@ -88,13 +88,14 @@
 
 	void render();
 	Colour raytrace(Ray &ray, int depth, Shape *origin_shape);
-	void addshape(Shape *shape) { top->addShape(shape); };
-	void addlight(Light *light);
+	void addShape(Shape *shape) { top->addShape(shape); };
+	void addLight(Light *light) { lights.push_back(light); };
 	void setSampler(Sampler *sampl) { sampler = sampl; };
 	void setCamera(Camera *cam) { camera = cam; };
 	void setTop(Container *atop) { top = atop; };
 	Container *getTop() { return top; };
 
+	void setBgColour(const Colour &bg) { bg_colour = bg; };
 	void setMaxDepth(int newdepth) { max_depth = newdepth; };
 
 	void ambientocclusion(int samples, Float distance, Float angle);
--- a/include/scene.h	Sat Apr 19 18:00:27 2008 +0200
+++ b/include/scene.h	Sun Apr 20 16:48:24 2008 +0200
@@ -64,19 +64,26 @@
 {
 public:
 	Vector3 eye, p, u, v;
-	Float f,F;
+	Float F;
 
-	Camera(): eye(0,0,10), p(0,0,-1), u(-1,0,0), v(0,1,0), f(3.14/4.0), F(0.5/f) {};
+	Camera(): eye(0,0,10), p(0,0,-1), u(-1,0,0), v(0,1,0), F(0.7071) {};
 	Camera(const Vector3 &C, const Vector3 &ap, const Vector3 &au, const Vector3 &av):
-		eye(C), p(ap), u(au), v(av), f(3.14/4.0), F(0.5/f) {};
+		eye(C), p(ap), u(au), v(av), F(0.7071) {};
+	Camera(const Vector3 &from, const Vector3 &lookat, const Vector3 &up):
+		eye(from), F(0.7071)
+	{
+		p = lookat - from; u = cross(up, p);
+		p.normalize(); u.normalize();
+		v = cross(p, u);
+	};
 	void setEye(const Vector3 &aeye) { eye = aeye; };
-	void setFocalLength(const Float af) { f = af; F = 0.5/f; };
+	void setAngle(const Float angle) { F = sin(angle); };
 	void rotate(const Quaternion &q);
 	void move(const Float fw, const Float left, const Float up);
 
 	Ray makeRay(Sample &samp)
 	{
-		Vector3 dir = p + (u*samp.x - v*samp.y)*F;
+		Vector3 dir = p - (u*samp.x + v*samp.y)*F;
 		dir.normalize();
 		return Ray(eye, dir);
 	};
--- a/src/container.cc	Sat Apr 19 18:00:27 2008 +0200
+++ b/src/container.cc	Sun Apr 20 16:48:24 2008 +0200
@@ -33,15 +33,17 @@
 	if (shapes.size() == 0) {
 		/* initialize bounding box */
 		bbox = aShape->get_bbox();
+		Vector3 eps(Eps,Eps,Eps);
+		bbox = BBox(bbox.L - eps, bbox.H + eps);
 	} else {
 		/* adjust bounding box */
 		BBox shapebb = aShape->get_bbox();
-		if (shapebb.L.x < bbox.L.x)  bbox.L.x = shapebb.L.x;
-		if (shapebb.L.y < bbox.L.y)  bbox.L.y = shapebb.L.y;
-		if (shapebb.L.z < bbox.L.z)  bbox.L.z = shapebb.L.z;
-		if (shapebb.H.x > bbox.H.x)  bbox.H.x = shapebb.H.x;
-		if (shapebb.H.y > bbox.H.y)  bbox.H.y = shapebb.H.y;
-		if (shapebb.H.z > bbox.H.z)  bbox.H.z = shapebb.H.z;
+		if (shapebb.L.x - Eps < bbox.L.x)  bbox.L.x = shapebb.L.x - Eps;
+		if (shapebb.L.y - Eps < bbox.L.y)  bbox.L.y = shapebb.L.y - Eps;
+		if (shapebb.L.z - Eps < bbox.L.z)  bbox.L.z = shapebb.L.z - Eps;
+		if (shapebb.H.x + Eps > bbox.H.x)  bbox.H.x = shapebb.H.x + Eps;
+		if (shapebb.H.y + Eps > bbox.H.y)  bbox.H.y = shapebb.H.y + Eps;
+		if (shapebb.H.z + Eps > bbox.H.z)  bbox.H.z = shapebb.H.z + Eps;
 	}
 };
 
--- a/src/kdtree.cc	Sat Apr 19 18:00:27 2008 +0200
+++ b/src/kdtree.cc	Sun Apr 20 16:48:24 2008 +0200
@@ -78,60 +78,65 @@
 	}
 
 	// choose split axis
-	axis = 0;
+	/*axis = 0;
 	if (bounds.h() > bounds.w() && bounds.h() > bounds.d())
 		axis = 1;
 	if (bounds.d() > bounds.w() && bounds.d() > bounds.h())
 		axis = 2;
-
+*/
 	// create sorted list of shape bounds (= find all posible splits)
 	vector<ShapeBound> edges[3];
 	ShapeList::iterator shape;
 	for (shape = shapes->begin(); shape != shapes->end(); shape++)
 	{
 		BBox shapebounds = (*shape)->get_bbox();
-	//	for (int ax = 0; ax < 3; ax++)
-	//	{
-			edges[axis].push_back(ShapeBound(*shape, shapebounds.L[axis], 0));
-			edges[axis].push_back(ShapeBound(*shape, shapebounds.H[axis], 1));
-	//	}
+		for (int ax = 0; ax < 3; ax++)
+		{
+			edges[ax].push_back(ShapeBound(*shape, shapebounds.L[ax], 0));
+			edges[ax].push_back(ShapeBound(*shape, shapebounds.H[ax], 1));
+		}
 	}
-	sort(edges[axis].begin(), edges[axis].end());
+	for (int ax = 0; ax < 3; ax++)
+		sort(edges[ax].begin(), edges[ax].end());
 
 	// choose best split pos
 	const Float K = 1.4; // constant, K = cost of traversal / cost of ray-triangle intersection
 	Float SAV = (bounds.w()*bounds.h() +  // surface area of node
 		bounds.w()*bounds.d() + bounds.h()*bounds.d());
 	Float cost = SAV * (K + shapes->size()); // initial cost = non-split cost
-	BBox lbb = bounds;
-	BBox rbb = bounds;
 
-	vector<ShapeBound>::iterator edge, splitedge = edges[axis].end();
-	int lnum = 0, rnum = shapes->size();
-	for (edge = edges[axis].begin(); edge != edges[axis].end(); edge++)
+	vector<ShapeBound>::iterator edge, splitedge = edges[2].end();
+	for (int ax = 0; ax < 3; ax++)
 	{
-		if (edge->end)
-			rnum--;
+		int lnum = 0, rnum = shapes->size();
+		BBox lbb = bounds;
+		BBox rbb = bounds;
+		for (edge = edges[ax].begin(); edge != edges[ax].end(); edge++)
+		{
+			if (edge->end)
+				rnum--;
 
-		// calculate SAH cost of this split
-		lbb.H.cell[axis] = edge->pos;
-		rbb.L.cell[axis] = edge->pos;
-		Float SAL = (lbb.w()*lbb.h() + lbb.w()*lbb.d() + lbb.h()*lbb.d());
-		Float SAR = (rbb.w()*rbb.h() + rbb.w()*rbb.d() + rbb.h()*rbb.d());
-		Float splitcost = K*SAV + SAL*(K + lnum) + SAR*(K + rnum);
+			// calculate SAH cost of this split
+			lbb.H.cell[ax] = edge->pos;
+			rbb.L.cell[ax] = edge->pos;
+			Float SAL = (lbb.w()*lbb.h() + lbb.w()*lbb.d() + lbb.h()*lbb.d());
+			Float SAR = (rbb.w()*rbb.h() + rbb.w()*rbb.d() + rbb.h()*rbb.d());
+			Float splitcost = K*SAV + SAL*(K + lnum) + SAR*(K + rnum);
 
-		if (splitcost < cost)
-		{
-			splitedge = edge;
-			cost = splitcost;
-			split = edge->pos;
+			if (splitcost < cost)
+			{
+				axis = ax;
+				splitedge = edge;
+				cost = splitcost;
+				split = edge->pos;
+			}
+
+			if (!edge->end)
+				lnum++;
 		}
-
-		if (!edge->end)
-			lnum++;
 	}
 
-	if (splitedge == edges[axis].end())
+	if (splitedge == edges[2].end())
 	{
 		setLeaf();
 		return;
@@ -162,17 +167,19 @@
 
 	// split this node
 	delete shapes;
+	BBox lbb = bounds;
+	BBox rbb = bounds;
+	lbb.H.cell[axis] = split;
+	rbb.L.cell[axis] = split;
 	children = new KdNode[2];
+
 	for (edge = edges[axis].begin(); edge != splitedge; edge++)
-		if (!edge->end)
+		if (!edge->end && edge->shape->intersect_bbox(lbb))
 			children[0].addShape(edge->shape);
 	for (edge = splitedge; edge < edges[axis].end(); edge++)
-		if (edge->end)
+		if (edge->end && edge->shape->intersect_bbox(rbb))
 			children[1].addShape(edge->shape);
 
-	lbb.H.cell[axis] = split;
-	rbb.L.cell[axis] = split;
-
 	children[0].subdivide(lbb, maxdepth-1);
 	children[1].subdivide(rbb, maxdepth-1);
 }
--- a/src/raytracer.cc	Sat Apr 19 18:00:27 2008 +0200
+++ b/src/raytracer.cc	Sun Apr 20 16:48:24 2008 +0200
@@ -544,11 +544,6 @@
 	delete[] sample_queue;
 }
 
-void Raytracer::addlight(Light *light)
-{
-	lights.push_back(light);
-}
-
 void Raytracer::ambientocclusion(int samples, Float distance, Float angle)
 {
 	ao_samples = samples;
--- a/src/raytracermodule.cc	Sat Apr 19 18:00:27 2008 +0200
+++ b/src/raytracermodule.cc	Sun Apr 20 16:48:24 2008 +0200
@@ -73,7 +73,7 @@
 	static char *kwdlist[] = {"position", "colour", NULL};
 	PyObject *TPos, *TCol = NULL;
 	Float px, py, pz;
-	Float cr = 1.0, cg = 1.0, cb = 1.0;
+	Float cr = 0.9, cg = 0.9, cb = 0.9;
 
 	if (!PyArg_ParseTupleAndKeywords(args, kwd, "O!|O!", kwdlist,
 		&PyTuple_Type, &TPos, &PyTuple_Type, &TCol))
@@ -124,6 +124,7 @@
 static void Camera_Destructor(PyObject* self);
 static PyObject *Camera_Getattr(PyObject *self, char *name);
 static PyObject *Camera_setEye(PyObject* self, PyObject* args);
+static PyObject *Camera_setAngle(PyObject* self, PyObject* args);
 static PyObject *Camera_rotate(PyObject* self, PyObject* args);
 
 static PyTypeObject CameraType = {
@@ -147,6 +148,7 @@
 
 static PyMethodDef CameraMethods[] = {
 	{"setEye", (PyCFunction)Camera_setEye, METH_VARARGS, "Set eye of the camera."},
+	{"setAngle", (PyCFunction)Camera_setAngle, METH_VARARGS, "Set vertical angle of view."},
 	{"rotate", (PyCFunction)Camera_rotate, METH_VARARGS, "Rotate camera with a quaternion."},
 	{NULL, NULL}
 };
@@ -154,21 +156,33 @@
 static PyObject* Camera_Constructor(PyObject* self, PyObject* args, PyObject *kwd)
 {
 	CameraObject *v;
-	static char *kwdlist[] = {"eye", "p", "u", "v", NULL};
-	PyObject *TEye = NULL, *Tp = NULL, *Tu = NULL, *Tv = NULL;
+	static char *kwdlist[] = {"eye", "lookat", "up", "p", "u", "v", NULL};
+	PyObject *TEye = NULL, *TLookAt = NULL, *TUp = NULL,
+		*Tp = NULL, *Tu = NULL, *Tv = NULL;
 	Float ex=0.0,  ey=0.0, ez=10.0;
+	Float lax=0.0, lay=0.0, laz=0.0;
+	Float upx=0.0, upy=1.0, upz=0.0;
 	Float px=0.0,  py=0.0, pz=-1.0;
 	Float ux=-1.0, uy=0.0, uz=0.0;
 	Float vx=0.0,  vy=1.0, vz=0.0;
 
-	if (!PyArg_ParseTupleAndKeywords(args, kwd, "|O!O!O!O!", kwdlist,
-		&PyTuple_Type, &TEye, &PyTuple_Type, &Tp, &PyTuple_Type, &Tu, &PyTuple_Type, &Tv))
+	if (!PyArg_ParseTupleAndKeywords(args, kwd, "|O!O!O!O!O!O!", kwdlist,
+		&PyTuple_Type, &TEye, &PyTuple_Type, &TLookAt, &PyTuple_Type, &TUp,
+		&PyTuple_Type, &Tp, &PyTuple_Type, &Tu, &PyTuple_Type, &Tv))
 		return NULL;
 
 	if (TEye)
 		if (!PyArg_ParseTuple(TEye, "fff", &ex, &ey, &ez))
 			return NULL;
 
+	if (TLookAt)
+		if (!PyArg_ParseTuple(TLookAt, "fff", &lax, &lay, &laz))
+			return NULL;
+
+	if (TUp)
+		if (!PyArg_ParseTuple(TUp, "fff", &upx, &upy, &upz))
+			return NULL;
+
 	if (Tp)
 		if (!PyArg_ParseTuple(Tp, "fff", &px, &py, &pz))
 			return NULL;
@@ -182,8 +196,12 @@
 			return NULL;
 
 	v = PyObject_New(CameraObject, &CameraType);
-	v->camera = new Camera(Vector3(ex, ey, ez),
-		Vector3(px, py, pz), Vector3(ux, uy, uz), Vector3(vx, vy, vz));
+	if (TLookAt)
+		v->camera = new Camera(Vector3(ex, ey, ez),
+			Vector3(lax, lay, laz), Vector3(upx, upy, upz));
+	else
+		v->camera = new Camera(Vector3(ex, ey, ez),
+			Vector3(px, py, pz), Vector3(ux, uy, uz), Vector3(vx, vy, vz));
 	return (PyObject*)v;
 }
 
@@ -217,6 +235,19 @@
 	return Py_None;
 }
 
+static PyObject *Camera_setAngle(PyObject* self, PyObject* args)
+{
+	Float angle;
+
+	if (!PyArg_ParseTuple(args, "f", &angle))
+		return NULL;
+
+	((CameraObject *)self)->camera->setAngle(angle);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
 static PyObject *Camera_rotate(PyObject* self, PyObject* args)
 {
 	PyObject *Tq = NULL;
@@ -684,6 +715,7 @@
 static PyObject *Raytracer_Getattr(PyObject *self, char *name);
 static PyObject *Raytracer_render(PyObject* self, PyObject* args);
 static PyObject *Raytracer_setcamera(PyObject* self, PyObject* args);
+static PyObject *Raytracer_setbgcolour(PyObject* self, PyObject* args);
 static PyObject *Raytracer_addshape(PyObject* self, PyObject* args);
 static PyObject *Raytracer_addlight(PyObject* self, PyObject* args);
 static PyObject *Raytracer_ambientocclusion(PyObject* self, PyObject* args, PyObject *kwd);
@@ -710,6 +742,7 @@
 static PyMethodDef RaytracerMethods[] = {
 	{"render", (PyCFunction)Raytracer_render, METH_VARARGS, "Render scene and return image data."},
 	{"setcamera", (PyCFunction)Raytracer_setcamera, METH_VARARGS, "Set camera for the scene."},
+	{"setbgcolour", (PyCFunction)Raytracer_setbgcolour, METH_VARARGS, "Set background colour."},
 	{"addshape", (PyCFunction)Raytracer_addshape, METH_VARARGS, "Add new shape to scene."},
 	{"addlight", (PyCFunction)Raytracer_addlight, METH_VARARGS, "Add new light source to scene."},
 	{"ambientocclusion", (PyCFunction)Raytracer_ambientocclusion, METH_VARARGS | METH_KEYWORDS,
@@ -800,6 +833,19 @@
 	return Py_None;
 }
 
+static PyObject* Raytracer_setbgcolour(PyObject* self, PyObject* args)
+{
+	Float r,g,b;
+
+	if (!PyArg_ParseTuple(args, "(fff)", &r, &g, &b))
+		return NULL;
+
+	((RaytracerObject *)self)->raytracer->setBgColour(Colour(r,g,b));
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
 static PyObject* Raytracer_addshape(PyObject* self, PyObject* args)
 {
 	PyObject *obj;
@@ -807,7 +853,7 @@
 	if (!PyArg_ParseTuple(args, "O", &obj))
 		return NULL;
 
-	((RaytracerObject *)self)->raytracer->addshape(
+	((RaytracerObject *)self)->raytracer->addShape(
 		((BoxObject*)obj)->shape);
 
 	((RaytracerObject *)self)->children->push_back(obj);
@@ -822,7 +868,7 @@
 
 	if (!PyArg_ParseTuple(args, "O!", &LightType, &lightobj))
 		return NULL;
-	((RaytracerObject *)self)->raytracer->addlight(lightobj->light);
+	((RaytracerObject *)self)->raytracer->addLight(lightobj->light);
 	((RaytracerObject *)self)->children->push_back((PyObject*)lightobj);
 	Py_INCREF(lightobj);
 	Py_INCREF(Py_None);