/*
    This file is part of Mitsuba, a physically based rendering system.

    Copyright (c) 2007-2011 by Wenzel Jakob and others.

    Mitsuba is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License Version 3
    as published by the Free Software Foundation.

    Mitsuba is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <mitsuba/render/scene.h>
#include <mitsuba/render/renderjob.h>
#include <mitsuba/core/plugin.h>

MTS_NAMESPACE_BEGIN

Scene::Scene(const Properties &props)
 : NetworkedObject(props), m_blockSize(32) {
	m_kdtree = new ShapeKDTree();
	/* When test case mode is active (Mitsuba is started with the -t parameter), 
	  this specifies the type of test performed. Mitsuba will expect a reference 
	  solution file of the name <tt>&lt;sceneName&gt;.ref</tt>. When set to 
	  <tt>t-test</tt>, a two-sided t-test on equality to the reference will be 
	  performed at the (1 - <tt>testThresh</tt>) level (99% by default). 
	  When set to <tt>relerr</tt>, the test will fail if the relative error 
	  exceeds <tt>testThresh</tt> (default: 0.01%).
    */
	std::string testType = props.getString("testType", "none");
	if (testType == "none")
		m_testType = ENone;
	else if (testType == "t-test")
		m_testType = ETTest;
	else if (testType == "relerr")
		m_testType = ERelativeError;
	else
		Log(EError, "Unknown test mode \"%s\" specified (must be \"t-test\" or \"relerr\")",
			testType.c_str());
	/* Error threshold for use with <tt>testType</tt> */
	m_testThresh = props.getFloat("testThresh", 0.01);
	/* By default, luminaire sampling chooses a luminaire with a probability 
	  dependent on the emitted power. Setting this parameter to false switches 
	  to uniform sampling. */
	m_importanceSampleLuminaires = props.getBoolean("importanceSampleLuminaires", true);
	/* kd-tree construction: Enable primitive clipping? Generally leads to a 
	  significant improvement of the resulting tree. */
	if (props.hasProperty("kdClip"))
		m_kdtree->setClip(props.getBoolean("kdClip"));
	/* kd-tree construction: Relative cost of a triangle intersection operation 
	   in the surface area heuristic. */
	if (props.hasProperty("kdIntersectionCost"))
		m_kdtree->setQueryCost(props.getFloat("kdIntersectionCost"));
	/* kd-tree construction: Relative cost of a kd-tree traversal operation 
	   in the surface area heuristic. */
	if (props.hasProperty("kdTraversalCost"))
		m_kdtree->setTraversalCost(props.getFloat("kdTraversalCost"));
	/* kd-tree construction: Bonus factor for cutting away regions of empty space */
	if (props.hasProperty("kdEmptySpaceBonus"))
		m_kdtree->setEmptySpaceBonus(props.getFloat("kdEmptySpaceBonus"));
	/* kd-tree construction: A kd-tree node containing this many or fewer 
	   primitives will not be split */
	if (props.hasProperty("kdStopPrims"))
		m_kdtree->setStopPrims(props.getInteger("kdStopPrims"));
	/* kd-tree construction: Maximum tree depth */
	if (props.hasProperty("kdMaxDepth"))
		m_kdtree->setMaxDepth(props.getInteger("kdMaxDepth"));
	/* kd-tree construction: Specify the number of primitives, at which the 
	   builder will switch from (approximate) Min-Max binning to the accurate 
	   O(n log n) SAH-based optimization method. */
	if (props.hasProperty("kdExactPrimitiveThreshold"))
		m_kdtree->setExactPrimitiveThreshold(props.getInteger("kdExactPrimitiveThreshold"));
	/* kd-tree construction: use multiple processors? */
	if (props.hasProperty("kdParallelBuild"))
		m_kdtree->setParallelBuild(props.getBoolean("kdParallelBuild"));
	/* kd-tree construction: specify whether or not bad splits can be "retracted". */
	if (props.hasProperty("kdRetract"))
		m_kdtree->setRetract(props.getBoolean("kdRetract"));
	/* kd-tree construction: Set the number of bad refines allowed to happen
	   in succession before a leaf node will be created.*/
	if (props.hasProperty("kdMaxBadRefines"))
		m_kdtree->setMaxBadRefines(props.getInteger("kdMaxBadRefines"));
}

Scene::Scene(Scene *scene) : NetworkedObject(Properties()) {
	m_kdtree = scene->m_kdtree;
	m_testType = scene->m_testType;
	m_testThresh = scene->m_testThresh;
	m_blockSize = scene->m_blockSize;
	m_aabb = scene->m_aabb;
	m_bsphere = scene->m_bsphere;
	m_backgroundLuminaire = scene->m_backgroundLuminaire;
	m_camera = scene->m_camera;
	m_integrator = scene->m_integrator;
	m_sourceFile = scene->m_sourceFile;
	m_destinationFile = scene->m_destinationFile;
	m_luminairePDF = scene->m_luminairePDF;
	m_importanceSampleLuminaires = scene->m_importanceSampleLuminaires;
	m_shapes = scene->m_shapes;
	for (size_t i=0; i<m_shapes.size(); ++i)
		m_shapes[i]->incRef();
	m_meshes = scene->m_meshes;
	for (size_t i=0; i<m_meshes.size(); ++i)
		m_meshes[i]->incRef();
	m_luminaires = scene->m_luminaires;
	for (size_t i=0; i<m_luminaires.size(); ++i)
		m_luminaires[i]->incRef();
	m_media = scene->m_media;
	for (std::set<Medium *>::iterator it = m_media.begin();
			it != m_media.end(); ++it)
		(*it)->incRef();
	m_ssIntegrators = scene->m_ssIntegrators;
	for (size_t i=0; i<m_ssIntegrators.size(); ++i)
		m_ssIntegrators[i]->incRef();
	m_objects = scene->m_objects;
	for (size_t i=0; i<m_objects.size(); ++i)
		m_objects[i]->incRef();
	m_netObjects = scene->m_netObjects;
	for (size_t i=0; i<m_netObjects.size(); ++i)
		m_netObjects[i]->incRef();
}


Scene::Scene(Stream *stream, InstanceManager *manager) 
 : NetworkedObject(stream, manager) {
	m_kdtree = new ShapeKDTree();
	m_kdtree->setQueryCost(stream->readFloat());
	m_kdtree->setTraversalCost(stream->readFloat());
	m_kdtree->setEmptySpaceBonus(stream->readFloat());
	m_kdtree->setStopPrims(stream->readInt());
	m_kdtree->setClip(stream->readBool());
	m_kdtree->setMaxDepth(stream->readUInt());
	m_kdtree->setExactPrimitiveThreshold(stream->readUInt());
	m_kdtree->setParallelBuild(stream->readBool());
	m_kdtree->setRetract(stream->readBool());
	m_kdtree->setMaxBadRefines(stream->readUInt());
	m_importanceSampleLuminaires = stream->readBool();
	m_testType = (ETestType) stream->readInt();
	m_testThresh = stream->readFloat();
	m_blockSize = stream->readInt();
	m_aabb = AABB(stream);
	m_bsphere = BSphere(stream);
	m_backgroundLuminaire = static_cast<Luminaire *>(manager->getInstance(stream));
	int count = stream->readInt();
	for (int i=0; i<count; ++i) {
		Shape *shape = static_cast<Shape *>(manager->getInstance(stream));
		shape->incRef();
		m_shapes.push_back(shape);
	}
	count = stream->readInt();
	for (int i=0; i<count; ++i) {
		TriMesh *trimesh = static_cast<TriMesh *>(manager->getInstance(stream));
		trimesh->incRef();
		m_meshes.push_back(trimesh);
	}
	count = stream->readInt();
	for (int i=0; i<count; ++i) {
		Luminaire *luminaire = static_cast<Luminaire *>(manager->getInstance(stream));
		luminaire->incRef();
		m_luminaires.push_back(luminaire);
	}
	count = stream->readInt();
	for (int i=0; i<count; ++i) {
		Medium *medium = static_cast<Medium *>(manager->getInstance(stream));
		medium->incRef();
		m_media.insert(medium);
	}
	count = stream->readInt();
	for (int i=0; i<count; ++i) {
		Subsurface *ssIntegrator = static_cast<Subsurface *>(manager->getInstance(stream));
		ssIntegrator->incRef();
		m_ssIntegrators.push_back(ssIntegrator);
	}
	count = stream->readInt();
	for (int i=0; i<count; ++i) {
		ConfigurableObject *obj = static_cast<ConfigurableObject *>(manager->getInstance(stream));
		obj->incRef();
		m_objects.push_back(obj);
	}
	count = stream->readInt();
	for (int i=0; i<count; ++i) {
		NetworkedObject *obj = static_cast<NetworkedObject *>(manager->getInstance(stream));
		m_netObjects.push_back(obj); // Do not increase the ref. count
	}
	initialize();
}

Scene::~Scene() {
	for (size_t i=0; i<m_shapes.size(); i++)
		m_shapes[i]->decRef();
	for (size_t i=0; i<m_meshes.size(); i++)
		m_meshes[i]->decRef();
	for (size_t i=0; i<m_objects.size(); i++)
		m_objects[i]->decRef();
	for (size_t i=0; i<m_ssIntegrators.size(); i++)
		m_ssIntegrators[i]->decRef();
	for (size_t i=0; i<m_luminaires.size(); i++)
		m_luminaires[i]->decRef();
	for (std::set<Medium *>::iterator it = m_media.begin();
			it != m_media.end(); ++it)
		(*it)->decRef();
}

void Scene::bindUsedResources(ParallelProcess *proc) const {
	for (size_t i=0; i<m_netObjects.size(); ++i)
		m_netObjects[i]->bindUsedResources(proc);
}

void Scene::wakeup(std::map<std::string, SerializableObject *> &params) {
	for (size_t i=0; i<m_netObjects.size(); ++i)
		m_netObjects[i]->wakeup(params);
}

void Scene::configure() {
	if (m_integrator == NULL) {
		/* Create a direct integrator by default */
		m_integrator = static_cast<Integrator *> (PluginManager::getInstance()->
				createObject(MTS_CLASS(Integrator), Properties("direct")));
		m_integrator->configure();
	}
	if (m_camera == NULL) {
		Properties props("perspective");
		/* Create a perspective camera with 45deg. FOV, which can see the whole scene */
		AABB aabb;
		for (size_t i=0; i<m_shapes.size(); ++i)
			aabb.expandBy(m_shapes[i]->getAABB());
		if (aabb.isValid()) {
			Log(EInfo, "No camera found! Adding a default camera.");
			Point center = aabb.getCenter();
			Vector extents = aabb.getExtents();
			Float maxExtents = std::max(extents.x, extents.y);
			Float distance = maxExtents/(2.0f * std::tan(45 * .5f * M_PI/180));

			props.setTransform("toWorld", Transform::translate(Vector(center.x, 
					center.y, aabb.min.z - distance)));
			props.setFloat("fov", 45.0f);

			m_camera = static_cast<Camera *> (PluginManager::getInstance()->
					createObject(MTS_CLASS(Camera), props));
			m_camera->configure();
			m_sampler = m_camera->getSampler();
		} else {
			Log(EWarn, "Unable to set up a default camera -- does the scene "
				"contain anything at all?");
		}
	}

	m_integrator->configureSampler(m_sampler);

	/**
	 * make it possible to serialize the integrator by 
	 * itself without all the extra baggage
	 */
	m_integrator->setParent(NULL); 
}

void Scene::initialize() {
	if (!m_kdtree->isBuilt()) {
		/* Expand all geometry */
		std::vector<Shape *> tempShapes;
		tempShapes.reserve(m_shapes.size());
		m_shapes.swap(tempShapes);
		for (size_t i=0; i<tempShapes.size(); ++i) {
			addShape(tempShapes[i]);
			tempShapes[i]->decRef();
		}

		/* Build the kd-tree */
		m_kdtree->build();

		m_aabb = m_kdtree->getAABB();
		m_bsphere = m_kdtree->getBSphere();
	}

	if (!m_luminairePDF.isReady()) {
		if (m_luminaires.size() == 0) {
			Log(EWarn, "No luminaires found -- adding a constant environment source");
			Properties constantProps("constant");
			constantProps.setSpectrum("intensity", Spectrum(0.9f));
			ref<Luminaire> luminaire = static_cast<Luminaire *>(
				PluginManager::getInstance()->createObject(MTS_CLASS(Luminaire), constantProps));
			addChild("", luminaire);
			luminaire->configure();
		}

		/* Calculate a discrete PDF to importance sample luminaires */
		for (std::vector<Luminaire *>::iterator it = m_luminaires.begin();
			it != m_luminaires.end(); ++it) {
			(*it)->preprocess(this);
			/* Add with a probability proportional to the luminaire's power */
			if (m_importanceSampleLuminaires)
				m_luminairePDF.put((*it)->getSamplingWeight());
			else
				m_luminairePDF.put(1.0f);
		}
		m_luminairePDF.build();
	} else {
		for (std::vector<Luminaire *>::iterator it = m_luminaires.begin();
			it != m_luminaires.end(); ++it) 
			(*it)->preprocess(this);
	}
}

bool Scene::preprocess(RenderQueue *queue, const RenderJob *job, 
		int sceneResID, int cameraResID, int samplerResID) {
	initialize();

	/* Pre-process step for the main scene integrator */
	if (!m_integrator->preprocess(this, queue, job,
		sceneResID, cameraResID, samplerResID))
		return false;

	/* Pre-process step for all sub-surface integrators */
	for (std::vector<Subsurface *>::iterator it = m_ssIntegrators.begin();
		it != m_ssIntegrators.end(); ++it) 
		if (!(*it)->preprocess(this, queue, job, 
			sceneResID, cameraResID, samplerResID))
			return false;
	return true;
}

bool Scene::render(RenderQueue *queue, const RenderJob *job,
		int sceneResID, int cameraResID, int samplerResID) {
	m_camera->getFilm()->clear();
	return m_integrator->render(this, queue, job, sceneResID, 
		cameraResID, samplerResID);
}

void Scene::cancel() {
	for (std::vector<Subsurface *>::iterator it = m_ssIntegrators.begin();
		it != m_ssIntegrators.end(); ++it) 
		(*it)->cancel();
	m_integrator->cancel();
}

void Scene::flush() {
	m_camera->getFilm()->develop(m_destinationFile);
}

void Scene::postprocess(RenderQueue *queue, const RenderJob *job,
		int sceneResID, int cameraResID, int samplerResID) {
	m_integrator->postprocess(this, queue, job, sceneResID, 
		cameraResID, samplerResID);
	m_camera->getFilm()->develop(m_destinationFile);
}

Float Scene::pdfLuminaire(const Point &p,
		const LuminaireSamplingRecord &lRec, bool delta) const {
	const Luminaire *luminaire = lRec.luminaire;
	Float luminance;

	if (m_importanceSampleLuminaires)
		luminance = luminaire->getSamplingWeight();
	else 
		luminance = 1.0f;

	/* Calculate the probability of importance sampling this luminaire */
	const Float fraction = luminance / m_luminairePDF.getOriginalSum();
	return luminaire->pdf(p, lRec, delta) * fraction;
}

bool Scene::sampleLuminaire(const Point &p, Float time,
		LuminaireSamplingRecord &lRec, const Point2 &s,
		bool testVisibility) const {
	Point2 sample(s);
	Float lumPdf;
	size_t index = m_luminairePDF.sampleReuse(sample.x, lumPdf);
	const Luminaire *luminaire = m_luminaires[index];
	luminaire->sample(p, lRec, sample);

	if (lRec.pdf != 0) {
		if (testVisibility && isOccluded(p, lRec.sRec.p, time)) 
			return false;
		lRec.pdf *= lumPdf;
		lRec.value /= lRec.pdf;
		lRec.luminaire = luminaire;
		return true;
	} else {
		return false;
	}
}

Spectrum Scene::getTransmittance(const Point &p1, const Point &p2,
		Float time, const Medium *medium, Sampler *sampler) const {
	if (m_media.size() == 0) {
		return Spectrum(isOccluded(p1, p2, time)
			? 0.0f : 1.0f);
	} else {
		Vector d = p2 - p1;
		Float remaining = d.length();
		d /= remaining;
		remaining *= 1-Epsilon;

		const Shape *shape;
		Ray ray(p1, d, time);
		Spectrum transmittance(1.0f);
		int iterations = 0;

		while (remaining > 0) {
			Normal n;
			Float t;

			bool surface = rayIntersect(ray, t, shape, n);

			if (medium) 
				transmittance *= medium->getTransmittance(Ray(ray, 0, std::min(t, remaining)), sampler);

			if (!surface) 
				break;

			ray.o = ray(t);
			remaining -= t;

			if (remaining > 0) {
				if (shape->isOccluder())
					return Spectrum(0.0f);
				else if (shape->isMediumTransition()) 
					medium = dot(n, d) > 0 ? shape->getExteriorMedium()
						: shape->getInteriorMedium();
				if (++iterations > 100) { /// Just a precaution..
					Log(EWarn, "getTransmittance(): round-off error issues?");
					break;
				}
			}
		}

		return transmittance;
	}
}

bool Scene::attenuatedRayIntersect(const Ray &_ray, const Medium *medium,
		Intersection &its, bool &indexMatchedMediumTransition,
		Spectrum &transmittance, Sampler *sampler) const {
	Ray ray(_ray);
	transmittance = Spectrum(1.0f);
	int iterations = 0;

	while (true) {
		bool surface = m_kdtree->rayIntersect(ray, its);

		if (medium) 
			transmittance *= medium->getTransmittance(Ray(ray, 0, its.t), sampler);

		if (!surface)
			return false;
		else if (its.shape->isOccluder())
			return true;
		else if (its.shape->isMediumTransition()) {
			medium = dot(its.geoFrame.n, ray.d) > 0 ?
				  its.shape->getExteriorMedium()
				: its.shape->getInteriorMedium();
			indexMatchedMediumTransition = true;
		}

		ray.o = ray(its.t);
		ray.mint = Epsilon;

		if (++iterations > 100) { /// Just a precaution..
			Log(EWarn, "attenuatedRayIntersect(): round-off error issues?");
			return false;
		}
	}
}

bool Scene::sampleAttenuatedLuminaire(const Point &p, Float time, 
	const Medium *medium, LuminaireSamplingRecord &lRec, 
	const Point2 &s, Sampler *sampler) const {
	Point2 sample(s);
	Float lumPdf;
	const Luminaire *luminaire = m_luminaires[
		m_luminairePDF.sampleReuse(sample.x, lumPdf)];
	luminaire->sample(p, lRec, sample);

	if (lRec.pdf != 0) {
		lRec.value *= getTransmittance(p, lRec.sRec.p, time, medium, sampler);
		if (lRec.value.isZero())
			return false;
		lRec.pdf *= lumPdf;
		lRec.value /= lRec.pdf;
		lRec.luminaire = luminaire;
		return true;
	}
	return false;
}

bool Scene::sampleAttenuatedLuminaire(const Intersection &its, 
	const Medium *medium, LuminaireSamplingRecord &lRec, 
	const Point2 &s, Sampler *sampler) const {
	Point2 sample(s);
	Float lumPdf;
	const Luminaire *luminaire = m_luminaires[
		m_luminairePDF.sampleReuse(sample.x, lumPdf)];
	luminaire->sample(its.p, lRec, sample);

	if (lRec.pdf != 0) {
		if (its.isMediumTransition())
			medium = its.getTargetMedium(lRec.sRec.p - its.p);
		lRec.value *= getTransmittance(its.p, lRec.sRec.p, its.time, medium, sampler);
		if (lRec.value.isZero())
			return false;
		lRec.pdf *= lumPdf;
		lRec.value /= lRec.pdf;
		lRec.luminaire = luminaire;
		return true;
	}
	return false;
}


void Scene::sampleEmission(EmissionRecord &eRec, Point2 &sample1, Point2 &sample2) const {
	Float lumPdf;
	size_t index = m_luminairePDF.sampleReuse(sample1.x, lumPdf);
	const Luminaire *luminaire = m_luminaires[index];
	luminaire->sampleEmission(eRec, sample1, sample2);
	eRec.pdfArea *= lumPdf;
	eRec.luminaire = luminaire;
	Float cosTheta = (eRec.luminaire->getType() & Luminaire::EOnSurface)
		? absDot(eRec.sRec.n, eRec.d) : 1;
	eRec.value *= cosTheta / (eRec.pdfArea * eRec.pdfDir);
}

void Scene::sampleEmissionArea(EmissionRecord &eRec, Point2 &sample) const {
	Float lumPdf;
	size_t index = m_luminairePDF.sampleReuse(sample.x, lumPdf);
	const Luminaire *luminaire = m_luminaires[index];
	luminaire->sampleEmissionArea(eRec, sample);
	eRec.pdfArea *= lumPdf;
	eRec.luminaire = luminaire;
}

void Scene::pdfEmission(EmissionRecord &eRec, bool delta) const {
	const Luminaire *luminaire = eRec.luminaire;
	Float luminance;
	if (m_importanceSampleLuminaires)
		luminance = luminaire->getSamplingWeight();
	else 
		luminance = 1.0f;
	/* Calculate the probability of importance sampling this luminaire */
	const Float fraction = luminance / m_luminairePDF.getOriginalSum();

	luminaire->pdfEmission(eRec, delta);
	eRec.pdfArea *= fraction;
}

void Scene::addChild(const std::string &name, ConfigurableObject *child) {
	const Class *cClass = child->getClass();

	if (cClass->derivesFrom(MTS_CLASS(NetworkedObject)) &&
	   !cClass->derivesFrom(MTS_CLASS(Integrator))) 
		m_netObjects.push_back(static_cast<NetworkedObject *>(child));
	if (cClass->derivesFrom(MTS_CLASS(Camera))) {
		AssertEx(m_camera == NULL, "There can only be one camera per scene");
		m_camera = static_cast<Camera *>(child);
		m_sampler = m_camera->getSampler();
	} else if (cClass->derivesFrom(MTS_CLASS(Integrator))) {
		AssertEx(m_integrator == NULL, "There can only be one integrator per scene");
		m_integrator = static_cast<Integrator *>(child);
	} else if (cClass->derivesFrom(MTS_CLASS(Texture))
			|| cClass->derivesFrom(MTS_CLASS(BSDF))
			|| cClass->derivesFrom(MTS_CLASS(PhaseFunction))) {
		ConfigurableObject *obj= static_cast<ConfigurableObject *>(child);
		obj->incRef();
		m_objects.push_back(obj);
	} else if (cClass->derivesFrom(MTS_CLASS(Medium))) {
		Medium *medium = static_cast<Medium *>(child);
		if (m_media.find(medium) == m_media.end()) {
			medium->incRef();
			m_media.insert(medium);
		}
	} else if (cClass->derivesFrom(MTS_CLASS(Luminaire))) {
		Luminaire *luminaire = static_cast<Luminaire *>(child);
		luminaire->incRef();
		m_luminaires.push_back(luminaire);
		if (luminaire->isBackgroundLuminaire()) {
			AssertEx(m_backgroundLuminaire.get() == NULL,
				"The scene may only contain one background luminaire");
			m_backgroundLuminaire = luminaire;
		}
	} else if (cClass->derivesFrom(MTS_CLASS(Shape))) {
		ref<Shape> shape = static_cast<Shape *>(child);
		shape->incRef();
		m_shapes.push_back(shape);
	} else if (cClass->derivesFrom(MTS_CLASS(Scene))) {
		ref<Scene> scene = static_cast<Scene *>(child);
		/* A scene from somewhere else has been included.
		   Add all of its contents */
		for (size_t i=0; i<scene->getLuminaires().size(); ++i) {
			Luminaire *lum = scene->getLuminaires()[i];
			lum->setParent(this);
			addChild("luminaire", lum);
		}
		for (size_t i=0; i<scene->getShapes().size(); ++i) {
			Shape *shape = scene->getShapes()[i];
			shape->setParent(this);
			addChild("shape", shape);
		}
		for (size_t i=0; i<scene->getReferencedObjects().size(); ++i) {
			ConfigurableObject *obj = scene->getReferencedObjects()[i];
			obj->setParent(this);
			addChild("object", obj);
		}
		for (std::set<Medium *>::iterator it = scene->getMedia().begin();
				it != scene->getMedia().end(); ++it) {
			Medium *medium = *it;
			medium->setParent(this);
			addChild("medium", medium);
		}
		if (scene->getIntegrator() != NULL) {
			scene->getIntegrator()->setParent(this);
			addChild("integrator", scene->getIntegrator());
		}
		if (scene->getCamera() != NULL) {
			scene->getCamera()->setParent(this);
			addChild("integrator", scene->getCamera());
		}
	} else {
		Log(EError, "Scene: Invalid child node! (\"%s\")",
			cClass->getName().c_str());
	}
}

void Scene::addShape(Shape *shape) {
	if (shape->isCompound()) {
		int index = 0;
		do {
			ref<Shape> element = shape->getElement(index++);
			if (element == NULL)
				break;
			addShape(element);
		} while (true);
	} else {
		if (shape->isLuminaire()) {
			if (std::find(m_luminaires.begin(), m_luminaires.end(), 
					shape->getLuminaire()) == m_luminaires.end()) {
				m_luminaires.push_back(shape->getLuminaire());
				shape->getLuminaire()->incRef();
			}
		}
		if (shape->hasSubsurface()) {
			m_netObjects.push_back(static_cast<NetworkedObject *>(shape->getSubsurface()));
			if (std::find(m_ssIntegrators.begin(), m_ssIntegrators.end(), 
					shape->getSubsurface()) == m_ssIntegrators.end()) {
				m_ssIntegrators.push_back(shape->getSubsurface());
				shape->getSubsurface()->incRef();
			}
		}

		Medium *iMedium = shape->getInteriorMedium(),
		       *eMedium = shape->getExteriorMedium();

		if (eMedium != NULL && m_media.find(eMedium) == m_media.end()) {
			m_media.insert(eMedium);
			eMedium->incRef();
		}

		if (iMedium != NULL && m_media.find(iMedium) == m_media.end()) {
			m_media.insert(iMedium);
			iMedium->incRef();
		}

		if (shape->getClass()->derivesFrom(MTS_CLASS(TriMesh))) {
			if (std::find(m_meshes.begin(), m_meshes.end(), shape)
					== m_meshes.end()) {
				m_meshes.push_back(static_cast<TriMesh *>(shape));
				shape->incRef();
			}
		}

		shape->incRef();
		m_kdtree->addShape(shape);
		m_shapes.push_back(shape);
	}
}

void Scene::serialize(Stream *stream, InstanceManager *manager) const {
	ConfigurableObject::serialize(stream, manager);

	stream->writeFloat(m_kdtree->getQueryCost());
	stream->writeFloat(m_kdtree->getTraversalCost());
	stream->writeFloat(m_kdtree->getEmptySpaceBonus());
	stream->writeInt(m_kdtree->getStopPrims());
	stream->writeBool(m_kdtree->getClip());
	stream->writeUInt(m_kdtree->getMaxDepth());
	stream->writeUInt(m_kdtree->getExactPrimitiveThreshold());
	stream->writeBool(m_kdtree->getParallelBuild());
	stream->writeBool(m_kdtree->getRetract());
	stream->writeUInt(m_kdtree->getMaxBadRefines());
	stream->writeBool(m_importanceSampleLuminaires);
	stream->writeInt(m_testType);
	stream->writeFloat(m_testThresh);
	stream->writeInt(m_blockSize);
	m_aabb.serialize(stream);
	m_bsphere.serialize(stream);
	manager->serialize(stream, m_backgroundLuminaire.get());
	stream->writeUInt((uint32_t) m_shapes.size());
	for (size_t i=0; i<m_shapes.size(); ++i) 
		manager->serialize(stream, m_shapes[i]);
	stream->writeUInt((uint32_t) m_meshes.size());
	for (size_t i=0; i<m_meshes.size(); ++i) 
		manager->serialize(stream, m_meshes[i]);
	stream->writeUInt((uint32_t) m_luminaires.size());
	for (size_t i=0; i<m_luminaires.size(); ++i) 
		manager->serialize(stream, m_luminaires[i]);
	stream->writeUInt((uint32_t) m_media.size());
	for (std::set<Medium *>::const_iterator it = m_media.begin();
			it != m_media.end(); ++it)
		manager->serialize(stream, *it);
	stream->writeUInt((uint32_t) m_ssIntegrators.size());
	for (size_t i=0; i<m_ssIntegrators.size(); ++i) 
		manager->serialize(stream, m_ssIntegrators[i]);
	stream->writeUInt((uint32_t) m_objects.size());
	for (size_t i=0; i<m_objects.size(); ++i) 
		manager->serialize(stream, m_objects[i]);
	stream->writeUInt((uint32_t) m_netObjects.size());
	for (size_t i=0; i<m_netObjects.size(); ++i) 
		manager->serialize(stream, m_netObjects[i]);
}

std::string Scene::toString() const {
	std::ostringstream oss;

	oss << "Scene[" << endl
		<< "  testType = " << ((m_testType == ETTest) ? "t-test" : "relerr") << ", " << endl
		<< "  testThresh = " << m_testThresh << ", " << endl
		<< "  importanceSampleLuminaires = " << (int) m_importanceSampleLuminaires << ", " << endl
		<< "  camera = " << indent(m_camera.toString()) << "," << endl
		<< "  sampler = " << indent(m_sampler.toString()) << "," << endl
		<< "  integrator = " << indent(m_integrator.toString()) << "," << endl
		<< "  kdtree = " << indent(m_kdtree.toString()) << "," << endl
		<< "  backgroundLuminaire = " << indent(m_backgroundLuminaire.toString()) << "," << endl
		<< "  meshes = " << indent(containerToString(m_meshes.begin(), m_meshes.end())) << "," << endl
		<< "  shapes = " << indent(containerToString(m_shapes.begin(), m_shapes.end())) << "," << endl
		<< "  luminaires = " << indent(containerToString(m_luminaires.begin(), m_luminaires.end())) << "," << endl
		<< "  media = " << indent(containerToString(m_media.begin(), m_media.end())) << "," << endl
		<< "  ssIntegrators = " << indent(containerToString(m_ssIntegrators.begin(), m_ssIntegrators.end())) << "," << endl
		<< "  objects = " << indent(containerToString(m_objects.begin(), m_objects.end())) << endl;
	oss << "]";
	return oss.str();
}

MTS_IMPLEMENT_CLASS_S(Scene, false, ConfigurableObject)
MTS_NAMESPACE_END
