Exemple #1
0
	void process(const WorkUnit *workUnit, WorkResult *workResult,
		const bool &stop) {
		const RectangularWorkUnit *rect = static_cast<const RectangularWorkUnit *>(workUnit);
		IrradianceRecordVector *result = static_cast<IrradianceRecordVector *>(workResult);
		const SamplingIntegrator *integrator = m_subIntegrator.get();

		Intersection its;
		RadianceQueryRecord rRec(m_scene, m_sampler);

		const int sx = rect->getOffset().x,
				sy = rect->getOffset().y,
				ex = sx + rect->getSize().x,
				ey = sy + rect->getSize().y;
		result->clear();

		for (int y = sy; y < ey; y++) {
			for (int x = sx; x < ex; x++) {
				if (stop)
					break;
				Point2 pixelSample(x + .5f, y + .5f);
				RayDifferential ray;
				if (m_sensor->sampleRayDifferential(ray, pixelSample, Point2(0.5f), 0.5f).isZero())
					continue;
				if (m_scene->rayIntersect(ray, its)) {
					const BSDF *bsdf = its.getBSDF();
					if ((bsdf->getType() & BSDF::EAll) != BSDF::EDiffuseReflection)
						continue;

					Spectrum E;
					if (m_irrCache->get(its, E))
						continue;

					/* Irradiance cache miss - create a record. The following
					   generates stratified cosine-weighted samples and computes
					   rotational + translational gradients */
					m_hs->generateDirections(its);
					m_sampler->generate(Point2i(0));

					for (unsigned int j=0; j<m_hs->getM(); j++) {
						for (unsigned int k=0; k<m_hs->getN(); k++) {
							HemisphereSampler::SampleEntry &entry = (*m_hs)(j, k);
							entry.dist = std::numeric_limits<Float>::infinity();
							rRec.newQuery(RadianceQueryRecord::ERadianceNoEmission
								| RadianceQueryRecord::EDistance, m_sensor->getMedium());
							rRec.extra = RadianceQueryRecord::ECacheQuery;
							rRec.depth = 2;
							entry.L = integrator->Li(RayDifferential(its.p, entry.d, 0.0f), rRec);
							entry.dist = rRec.dist;
							m_sampler->advance();
						}
					}

					m_hs->process(its);
					result->put(m_irrCache->put(ray, its, *m_hs));
				}
			}
		}
	}
void CaptureParticleWorker::handleSurfaceInteraction(int depth, int nullInteractions,
		bool caustic, const Intersection &its, const Medium *medium,
		const Spectrum &weight) {

	if (its.isSensor()) {
		if (!m_bruteForce && !caustic)
			return;

		const Sensor *sensor = its.shape->getSensor();
		if (sensor != m_sensor)
			return;

		Vector wi = its.toWorld(its.wi);
		Point2 uv;
		Spectrum value = sensor->eval(its, wi, uv) * weight;
		if (value.isZero())
			return;

		m_workResult->put(uv, (Float *) &value[0]);
		return;
	}

	if (m_bruteForce || (depth >= m_maxPathDepth && m_maxPathDepth > 0))
		return;

	int maxInteractions = m_maxPathDepth - depth - 1;

	DirectSamplingRecord dRec(its);
	Spectrum value = weight * m_scene->sampleAttenuatedSensorDirect(
			dRec, its, medium, maxInteractions,
			m_sampler->next2D(), m_sampler);

	if (value.isZero())
		return;

	const BSDF *bsdf = its.getBSDF();

	Vector wo = dRec.d;
	BSDFSamplingRecord bRec(its, its.toLocal(wo), EImportance);

	/* Prevent light leaks due to the use of shading normals -- [Veach, p. 158] */
	Vector wi = its.toWorld(its.wi);
	Float wiDotGeoN = dot(its.geoFrame.n, wi),
		  woDotGeoN = dot(its.geoFrame.n, wo);
	if (wiDotGeoN * Frame::cosTheta(bRec.wi) <= 0 ||
		woDotGeoN * Frame::cosTheta(bRec.wo) <= 0)
		return;

	/* Adjoint BSDF for shading normals -- [Veach, p. 155] */
	Float correction = std::abs(
		(Frame::cosTheta(bRec.wi) * woDotGeoN)/
		(Frame::cosTheta(bRec.wo) * wiDotGeoN));
	value *= bsdf->eval(bRec) * correction;

	/* Splat onto the accumulation buffer */
	m_workResult->put(dRec.uv, (Float *) &value[0]);
}
Exemple #3
0
size_t generateVPLs(const Scene *scene, Random *random,
		size_t offset, size_t count, int maxDepth, bool prune, std::deque<VPL> &vpls) {
	if (maxDepth <= 1)
		return 0;

	static Sampler *sampler = NULL;
	if (!sampler) {
		Properties props("halton");
		props.setInteger("scramble", 0);
		sampler = static_cast<Sampler *> (PluginManager::getInstance()->
			createObject(MTS_CLASS(Sampler), props));
		sampler->configure();
	}

	const Sensor *sensor = scene->getSensor();
	Float time = sensor->getShutterOpen()
		+ 0.5f * sensor->getShutterOpenTime();

	const Frame stdFrame(Vector(1,0,0), Vector(0,1,0), Vector(0,0,1));

	while (vpls.size() < count) {
		sampler->setSampleIndex(++offset);

		PositionSamplingRecord pRec(time);
		DirectionSamplingRecord dRec;
		Spectrum weight = scene->sampleEmitterPosition(pRec,
			sampler->next2D());

		size_t start = vpls.size();

		/* Sample an emitted particle */
		const Emitter *emitter = static_cast<const Emitter *>(pRec.object);

		if (!emitter->isEnvironmentEmitter() && emitter->needsDirectionSample()) {
			VPL lumVPL(EPointEmitterVPL, weight);
			lumVPL.its.p = pRec.p;
			lumVPL.its.shFrame = pRec.n.isZero() ? stdFrame : Frame(pRec.n);
			lumVPL.emitter = emitter;
			appendVPL(scene, random, lumVPL, prune, vpls);

			weight *= emitter->sampleDirection(dRec, pRec, sampler->next2D());
		} else {
			/* Hack to get the proper information for directional VPLs */
			DirectSamplingRecord diRec(
				scene->getKDTree()->getAABB().getCenter(), pRec.time);

			Spectrum weight2 = emitter->sampleDirect(diRec, sampler->next2D())
				/ scene->pdfEmitterDiscrete(emitter);

			if (weight2.isZero())
				continue;

			VPL lumVPL(EDirectionalEmitterVPL, weight2);
			lumVPL.its.p = Point(0.0);
			lumVPL.its.shFrame = Frame(-diRec.d);
			lumVPL.emitter = emitter;
			appendVPL(scene, random, lumVPL, false, vpls);
			dRec.d = -diRec.d;

			Point2 offset = Warp::squareToUniformDiskConcentric(sampler->next2D());
			Vector perpOffset = Frame(diRec.d).toWorld(Vector(offset.x, offset.y, 0));
			BSphere geoBSphere = scene->getKDTree()->getAABB().getBSphere();
			pRec.p = geoBSphere.center + (perpOffset - dRec.d) * geoBSphere.radius;
			weight = weight2 * M_PI * geoBSphere.radius * geoBSphere.radius;
		}

		int depth = 2;
		Ray ray(pRec.p, dRec.d, time);
		Intersection its;

		while (!weight.isZero() && (depth < maxDepth || maxDepth == -1)) {
			if (!scene->rayIntersect(ray, its))
				break;

			const BSDF *bsdf = its.getBSDF();
			BSDFSamplingRecord bRec(its, sampler, EImportance);
			Spectrum bsdfVal = bsdf->sample(bRec, sampler->next2D());
			if (bsdfVal.isZero())
				break;

			/* Assuming that BSDF importance sampling is perfect,
				the following should equal the maximum albedo
				over all spectral samples */
			Float approxAlbedo = std::min((Float) 0.95f, bsdfVal.max());
			if (sampler->next1D() > approxAlbedo)
				break;
			else
				weight /= approxAlbedo;

			VPL vpl(ESurfaceVPL, weight);
			vpl.its = its;

			if (BSDF::getMeasure(bRec.sampledType) == ESolidAngle)
				appendVPL(scene, random, vpl, prune, vpls);

			weight *= bsdfVal;

			Vector wi = -ray.d, wo = its.toWorld(bRec.wo);
			ray = Ray(its.p, wo, 0.0f);

			/* Prevent light leaks due to the use of shading normals -- [Veach, p. 158] */
			Float wiDotGeoN = dot(its.geoFrame.n, wi),
				woDotGeoN = dot(its.geoFrame.n, wo);
			if (wiDotGeoN * Frame::cosTheta(bRec.wi) <= 0 ||
				woDotGeoN * Frame::cosTheta(bRec.wo) <= 0)
				break;

			/* Disabled for now -- this increases VPL weights
			   and accuracy is not really a big requirement */
			#if 0
				/* Adjoint BSDF for shading normals -- [Veach, p. 155] */
				weight *= std::abs(
					(Frame::cosTheta(bRec.wi) * woDotGeoN)/
					(Frame::cosTheta(bRec.wo) * wiDotGeoN));
			#endif

			++depth;
		}

		size_t end = vpls.size();
		for (size_t i=start; i<end; ++i)
			vpls[i].emitterScale = 1.0f / (end - start);
	}

	return offset;
}
Exemple #4
0
void ParticleTracer::process(const WorkUnit *workUnit, WorkResult *workResult,
		const bool &stop) {
	const RangeWorkUnit *range = static_cast<const RangeWorkUnit *>(workUnit);
	MediumSamplingRecord mRec;
	Intersection its;
	ref<Sensor> sensor    = m_scene->getSensor();
	bool needsTimeSample  = sensor->needsTimeSample();
	PositionSamplingRecord pRec(sensor->getShutterOpen()
		+ 0.5f * sensor->getShutterOpenTime());
	Ray ray;

	m_sampler->generate(Point2i(0));

	for (size_t index = range->getRangeStart(); index <= range->getRangeEnd() && !stop; ++index) {
		m_sampler->setSampleIndex(index);

		/* Sample an emission */
		if (needsTimeSample)
			pRec.time = sensor->sampleTime(m_sampler->next1D());

		const Emitter *emitter = NULL;
		const Medium *medium;

		Spectrum power;
		Ray ray;

		if (m_emissionEvents) {
			/* Sample the position and direction component separately to
			   generate emission events */
			power = m_scene->sampleEmitterPosition(pRec, m_sampler->next2D());
			emitter = static_cast<const Emitter *>(pRec.object);
			medium = emitter->getMedium();

			/* Forward the sampling event to the attached handler */
			handleEmission(pRec, medium, power);

			DirectionSamplingRecord dRec;
			power *= emitter->sampleDirection(dRec, pRec,
					emitter->needsDirectionSample() ? m_sampler->next2D() : Point2(0.5f));
			ray.setTime(pRec.time);
			ray.setOrigin(pRec.p);
			ray.setDirection(dRec.d);
		} else {
			/* Sample both components together, which is potentially
			   faster / uses a better sampling strategy */

			power = m_scene->sampleEmitterRay(ray, emitter,
				m_sampler->next2D(), m_sampler->next2D(), pRec.time);
			medium = emitter->getMedium();
			handleNewParticle();
		}

		int depth = 1, nullInteractions = 0;
		bool delta = false;

		Spectrum throughput(1.0f); // unitless path throughput (used for russian roulette)
		while (!throughput.isZero() && (depth <= m_maxDepth || m_maxDepth < 0)) {
			m_scene->rayIntersectAll(ray, its);

            /* ==================================================================== */
            /*                 Radiative Transfer Equation sampling                 */
            /* ==================================================================== */
			if (medium && medium->sampleDistance(Ray(ray, 0, its.t), mRec, m_sampler)) {
				/* Sample the integral
				  \int_x^y tau(x, x') [ \sigma_s \int_{S^2} \rho(\omega,\omega') L(x,\omega') d\omega' ] dx'
				*/

				throughput *= mRec.sigmaS * mRec.transmittance / mRec.pdfSuccess;

				/* Forward the medium scattering event to the attached handler */
				handleMediumInteraction(depth, nullInteractions,
						delta, mRec, medium, -ray.d, throughput*power);

				PhaseFunctionSamplingRecord pRec(mRec, -ray.d, EImportance);

				throughput *= medium->getPhaseFunction()->sample(pRec, m_sampler);
				delta = false;

				ray = Ray(mRec.p, pRec.wo, ray.time);
				ray.mint = 0;
			} else if (its.t == std::numeric_limits<Float>::infinity()) {
				/* There is no surface in this direction */
				break;
			} else {
				/* Sample
					tau(x, y) (Surface integral). This happens with probability mRec.pdfFailure
					Account for this and multiply by the proper per-color-channel transmittance.
				*/
				if (medium)
					throughput *= mRec.transmittance / mRec.pdfFailure;

				const BSDF *bsdf = its.getBSDF();

				/* Forward the surface scattering event to the attached handler */
				handleSurfaceInteraction(depth, nullInteractions, delta, its, medium, throughput*power);

				BSDFSamplingRecord bRec(its, m_sampler, EImportance);
				Spectrum bsdfWeight = bsdf->sample(bRec, m_sampler->next2D());
				if (bsdfWeight.isZero())
					break;

				/* Prevent light leaks due to the use of shading normals -- [Veach, p. 158] */
				Vector wi = -ray.d, wo = its.toWorld(bRec.wo);
				Float wiDotGeoN = dot(its.geoFrame.n, wi),
				      woDotGeoN = dot(its.geoFrame.n, wo);
				if (wiDotGeoN * Frame::cosTheta(bRec.wi) <= 0 ||
					woDotGeoN * Frame::cosTheta(bRec.wo) <= 0)
					break;

				/* Keep track of the weight, medium and relative
				   refractive index along the path */
				throughput *= bsdfWeight;
				if (its.isMediumTransition())
					medium = its.getTargetMedium(woDotGeoN);

				if (bRec.sampledType & BSDF::ENull)
					++nullInteractions;
				else
					delta = bRec.sampledType & BSDF::EDelta;

#if 0
				/* This is somewhat unfortunate: for accuracy, we'd really want the
				   correction factor below to match the path tracing interpretation
				   of a scene with shading normals. However, this factor can become
				   extremely large, which adds unacceptable variance to output
				   renderings.

				   So for now, it is disabled. The adjoint particle tracer and the
				   photon mapping variants still use this factor for the last
				   bounce -- just not for the intermediate ones, which introduces
				   a small (though in practice not noticeable) amount of error. This
				   is also what the implementation of SPPM by Toshiya Hachisuka does.

				   Ultimately, we'll need better adjoint BSDF sampling strategies
				   that incorporate these extra terms */

				/* Adjoint BSDF for shading normals -- [Veach, p. 155] */
				throughput *= std::abs(
					(Frame::cosTheta(bRec.wi) * woDotGeoN)/
					(Frame::cosTheta(bRec.wo) * wiDotGeoN));
#endif

				ray.setOrigin(its.p);
				ray.setDirection(wo);
				ray.mint = Epsilon;
			}

			if (depth++ >= m_rrDepth) {
				/* Russian roulette: try to keep path weights equal to one,
				   Stop with at least some probability to avoid
				   getting stuck (e.g. due to total internal reflection) */

				Float q = std::min(throughput.max(), (Float) 0.95f);
				if (m_sampler->next1D() >= q)
					break;
				throughput /= q;
			}
		}
	}
}
Exemple #5
0
bool PathEdge::pathConnectAndCollapse(const Scene *scene, const PathEdge *predEdge,
		const PathVertex *vs, const PathVertex *vt,
		const PathEdge *succEdge, int &interactions) {
	if (vs->isEmitterSupernode() || vt->isSensorSupernode()) {
		Float radianceTransport   = vt->isSensorSupernode() ? 1.0f : 0.0f,
		      importanceTransport = 1-radianceTransport;
		medium = NULL;
		length = 0.0f;
		d = Vector(0.0f);
		pdf[ERadiance]   = radianceTransport;
		pdf[EImportance] = importanceTransport;
		weight[ERadiance] = Spectrum(radianceTransport);
		weight[EImportance] = Spectrum(importanceTransport);
		interactions = 0;
	} else {
		Point vsp = vs->getPosition(), vtp = vt->getPosition();
		d = vsp-vtp;
		length = d.length();
		int maxInteractions = interactions;
		interactions = 0;

		if (length == 0) {
			#if defined(MTS_BD_DEBUG)
				SLog(EWarn, "Tried to connect %s and %s, which are located at exactly the same position!",
					vs->toString().c_str(), vt->toString().c_str());
			#endif
			return false;
		}

		d /= length;
		Float lengthFactor = vs->isOnSurface() ? (1-ShadowEpsilon) : 1;
		Ray ray(vtp, d, vt->isOnSurface() ? Epsilon : 0, length * lengthFactor, vs->getTime());

		weight[ERadiance] = Spectrum(1.0f);
		weight[EImportance] = Spectrum(1.0f);
		pdf[ERadiance] = 1.0f;
		pdf[EImportance] = 1.0f;

		Intersection its;
		Float remaining = length;
		medium = vt->getTargetMedium(succEdge, d);

		while (true) {
			bool surface = scene->rayIntersectAll(ray, its);

			if (surface && (interactions == maxInteractions ||
				!(its.getBSDF()->getType() & BSDF::ENull))) {
				/* Encountered an occluder -- zero transmittance. */
				return false;
			}

			if (medium) {
				Float segmentLength = std::min(its.t, remaining);
				MediumSamplingRecord mRec;
				medium->eval(Ray(ray, 0, segmentLength), mRec);

				Float pdfRadiance = (surface || !vs->isMediumInteraction())
					? mRec.pdfFailure : mRec.pdfSuccess;
				Float pdfImportance = (interactions > 0 || !vt->isMediumInteraction())
					? mRec.pdfFailure : mRec.pdfSuccessRev;

				if (pdfRadiance == 0 || pdfImportance == 0 || mRec.transmittance.isZero()) {
					/* Zero transmittance */
					return false;
				}

				weight[EImportance] *= mRec.transmittance / pdfImportance;
				weight[ERadiance] *= mRec.transmittance / pdfRadiance;
				pdf[EImportance] *= pdfImportance;
				pdf[ERadiance] *= pdfRadiance;
			}

			if (!surface || remaining - its.t < 0)
				break;

			/* Advance the ray */
			ray.o = ray(its.t);
			remaining -= its.t;
			ray.mint = Epsilon;
			ray.maxt = remaining * lengthFactor;

			/* Account for the ENull interaction */
			const BSDF *bsdf = its.getBSDF();
			Vector wo = its.toLocal(ray.d);
			BSDFSamplingRecord bRec(its, -wo, wo, ERadiance);
			bRec.component = BSDF::ENull;
			Float nullPdf = bsdf->pdf(bRec, EDiscrete);
			if (nullPdf == 0)
				return false;

			Spectrum nullWeight = bsdf->eval(bRec, EDiscrete) / nullPdf;

			weight[EImportance] *= nullWeight;
			weight[ERadiance] *= nullWeight;
			pdf[EImportance] *= nullPdf;
			pdf[ERadiance] *= nullPdf;

			if (its.isMediumTransition()) {
				const Medium *expected = its.getTargetMedium(-ray.d);
				if (medium != expected) {
					#if defined(MTS_BD_TRACE)
						SLog(EWarn, "PathEdge::pathConnectAndCollapse(): attempted two connect "
							"two vertices that disagree about the medium in between! "
							"Please check your scene for leaks.");
					#endif
					++mediumInconsistencies;
					return false;
				}
				medium = its.getTargetMedium(ray.d);
			}

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

		if (medium != vs->getTargetMedium(predEdge, -d)) {
			#if defined(MTS_BD_TRACE)
				SLog(EWarn, "PathEdge::pathConnectAndCollapse(): attempted two connect "
					"two vertices that disagree about the medium in between! "
					"Please check your scene for leaks.");
			#endif
			++mediumInconsistencies;
			return false;
		}
	}

	return true;
}
Exemple #6
0
bool PathEdge::pathConnect(const Scene *scene, const PathEdge *predEdge,
		const PathVertex *vs, Path &result, const PathVertex *vt,
		const PathEdge *succEdge, int maxInteractions, MemoryPool &pool) {
	BDAssert(result.edgeCount() == 0 && result.vertexCount() == 0);

	if (vs->isEmitterSupernode() || vt->isSensorSupernode()) {
		Float radianceTransport   = vt->isSensorSupernode() ? 1.0f : 0.0f,
		      importanceTransport = 1-radianceTransport;
		PathEdge *edge = pool.allocEdge();
		edge->medium = NULL;
		edge->length = 0.0f;
		edge->d = Vector(0.0f);
		edge->pdf[ERadiance]   = radianceTransport;
		edge->pdf[EImportance] = importanceTransport;
		edge->weight[ERadiance] = Spectrum(radianceTransport);
		edge->weight[EImportance] = Spectrum(importanceTransport);
		result.append(edge);
	} else {
		Point vsp = vs->getPosition(), vtp = vt->getPosition();
		Vector d(vsp-vtp);
		Float remaining = d.length();
		d /= remaining;
		if (remaining == 0) {
			#if defined(MTS_BD_DEBUG)
				SLog(EWarn, "Tried to connect %s and %s, which are located at exactly the same position!",
					vs->toString().c_str(), vt->toString().c_str());
			#endif
			return false;
		}

		Float lengthFactor = vs->isOnSurface() ? (1-ShadowEpsilon) : 1;
		Ray ray(vtp, d, vt->isOnSurface() ? Epsilon : 0,
				remaining * lengthFactor, vs->getTime());
		const Medium *medium = vt->getTargetMedium(succEdge,  d);

		int interactions = 0;

		Intersection its;
		while (true) {
			bool surface = scene->rayIntersectAll(ray, its);

			if (surface && (interactions == maxInteractions ||
				!(its.getBSDF()->getType() & BSDF::ENull))) {
				/* Encountered an occluder -- zero transmittance. */
				result.release(pool);
				return false;
			}

			/* Construct an edge */
			PathEdge *edge = pool.allocEdge();
			result.append(edge);
			edge->length = std::min(its.t, remaining);
			edge->medium = medium;
			edge->d = d;

			if (medium) {
				MediumSamplingRecord mRec;
				medium->eval(Ray(ray, 0, edge->length), mRec);
				edge->pdf[ERadiance] = (surface || !vs->isMediumInteraction())
					? mRec.pdfFailure : mRec.pdfSuccess;
				edge->pdf[EImportance] = (interactions > 0 || !vt->isMediumInteraction())
					? mRec.pdfFailure : mRec.pdfSuccessRev;

				if (edge->pdf[ERadiance] == 0 || edge->pdf[EImportance] == 0
						|| mRec.transmittance.isZero()) {
					/* Zero transmittance */
					result.release(pool);
					return false;
				}
				edge->weight[EImportance] = mRec.transmittance / edge->pdf[EImportance];
				edge->weight[ERadiance]   = mRec.transmittance / edge->pdf[ERadiance];
			} else {
				edge->weight[ERadiance] = edge->weight[EImportance] = Spectrum(1.0f);
				edge->pdf[ERadiance] = edge->pdf[EImportance] = 1.0f;
			}

			if (!surface || remaining - its.t < 0)
				break;

			/* Advance the ray */
			ray.o = ray(its.t);
			remaining -= its.t;
			ray.mint = Epsilon;
			ray.maxt = remaining * lengthFactor;

			const BSDF *bsdf = its.getBSDF();

			/* Account for the ENull interaction */
			Vector wo = its.toLocal(ray.d);
			BSDFSamplingRecord bRec(its, -wo, wo, ERadiance);
			bRec.component = BSDF::ENull;
			Float nullPdf = bsdf->pdf(bRec, EDiscrete);
			if (nullPdf == 0) {
				result.release(pool);
				return false;
			}

			PathVertex *vertex = pool.allocVertex();
			vertex->type = PathVertex::ESurfaceInteraction;
			vertex->degenerate = !(bsdf->hasComponent(BSDF::ESmooth)
				|| its.shape->isEmitter() || its.shape->isSensor());
			vertex->measure = EDiscrete;
			vertex->componentType = BSDF::ENull;
			vertex->pdf[EImportance] = vertex->pdf[ERadiance] = nullPdf;
			vertex->weight[EImportance] = vertex->weight[ERadiance]
				= bsdf->eval(bRec, EDiscrete) / nullPdf;
			vertex->rrWeight = 1.0f;
			vertex->getIntersection() = its;
			result.append(vertex);

			if (its.isMediumTransition()) {
				const Medium *expected = its.getTargetMedium(-ray.d);
				if (medium != expected) {
					#if defined(MTS_BD_TRACE)
						SLog(EWarn, "PathEdge::pathConnect(): attempted two connect "
							"two vertices that disagree about the medium in between! "
							"Please check your scene for leaks.");
					#endif
					++mediumInconsistencies;
					result.release(pool);
					return false;
				}
				medium = its.getTargetMedium(ray.d);
			}

			if (++interactions > 100) { /// Just a precaution..
				SLog(EWarn, "pathConnect(): round-off error issues?");
				result.release(pool);
				return false;
			}
		}

		if (medium != vs->getTargetMedium(predEdge, -d)) {
			#if defined(MTS_BD_TRACE)
				SLog(EWarn, "PathEdge::pathConnect(): attempted two connect "
					"two vertices that disagree about the medium in between! "
					"Please check your scene for leaks.");
			#endif
			++mediumInconsistencies;
			result.release(pool);
			return false;
		}
	}

	result.reverse();

	BDAssert(result.edgeCount() == result.vertexCount() + 1);
	BDAssert((int) result.vertexCount() <= maxInteractions || maxInteractions < 0);

	return true;
}