void Path::collapseTo(PathEdge &target) const {
	BDAssert(m_edges.size() > 0);

	target.pdf[ERadiance] = 1.0f;
	target.pdf[EImportance] = 1.0f;
	target.weight[ERadiance] = Spectrum(1.0f);
	target.weight[EImportance] = Spectrum(1.0f);
	target.d = m_edges[0]->d;
	target.medium = m_edges[0]->medium;
	target.length = 0;

	for (size_t i=0; i<m_edges.size(); ++i) {
		const PathEdge *edge = m_edges[i];
		target.weight[ERadiance] *= edge->weight[ERadiance];
		target.weight[EImportance] *= edge->weight[EImportance];
		target.pdf[ERadiance] *= edge->pdf[ERadiance];
		target.pdf[EImportance] *= edge->pdf[EImportance];
		target.length += edge->length;
		if (target.medium != edge->medium)
			target.medium = NULL;
	}

	for (size_t i=0; i<m_vertices.size(); ++i) {
		const PathVertex *vertex = m_vertices[i];
		BDAssert(vertex->isSurfaceInteraction() &&
			vertex->componentType == BSDF::ENull);
		target.weight[ERadiance] *= vertex->weight[ERadiance];
		target.weight[EImportance] *= vertex->weight[EImportance];
		target.pdf[ERadiance] *= vertex->pdf[ERadiance];
		target.pdf[EImportance] *= vertex->pdf[EImportance];
	}
}
std::string Path::summarize() const {
	std::ostringstream oss;

	BDAssert(m_vertices.size() == m_edges.size() + 1);

	for (size_t i=0; i<m_vertices.size(); ++i) {
		const PathVertex *vertex = m_vertices[i];
		switch (vertex->type) {
			case PathVertex::EEmitterSupernode: oss << "E"; break;
			case PathVertex::ESensorSupernode: oss << "C"; break;
			case PathVertex::EEmitterSample: oss << "e"; break;
			case PathVertex::ESensorSample: oss << "c"; break;
			case PathVertex::ESurfaceInteraction: oss << "S"; break;
			case PathVertex::EMediumInteraction: oss << "M"; break;
			default:
				SLog(EError, "Unknown vertex typ!");
		}
		if (!vertex->isConnectable())
			oss << "d";

		if (i<m_edges.size()) {
			const PathEdge *edge = m_edges[i];
			if (edge->medium == NULL)
				oss << " - ";
			else
				oss << " = ";
		}
	}
	return oss.str();
}
Example #3
0
Float MutatorBase::pdfMediumPerturbation(const PathVertex *oldVertex,
		const PathEdge *oldEdge, const PathEdge *newEdge) const {
	BDAssert(oldEdge->medium && newEdge->medium);
	const MediumSamplingRecord &mRec = oldVertex->getMediumSamplingRecord();
#if MTS_BD_MEDIUM_PERTURBATION_MONOCHROMATIC == 1
	Float sigmaT = (mRec.sigmaA + mRec.sigmaS).average() * m_mediumDensityMultiplier;
	Float diff = std::abs(oldEdge->length - newEdge->length);
	return 0.5f * sigmaT*math::fastexp(-sigmaT*diff);
#else
	Spectrum sigmaT = (mRec.sigmaA + mRec.sigmaS) * m_mediumDensityMultiplier;
	Float diff = std::abs(oldEdge->length - newEdge->length);
	Float sum = 0.0f;
	for (int i=0; i<SPECTRUM_SAMPLES; ++i)
		sum += sigmaT[i]*math::fastexp(-sigmaT[i]*diff);
	return sum * (0.5f / SPECTRUM_SAMPLES);
#endif
}
Example #4
0
bool CausticPerturbation::sampleMutation(
		Path &source, Path &proposal, MutationRecord &muRec) {
	int k = source.length(), m = k - 1, l = m - 1;

	if (k < 4 || !source.vertex(l)->isConnectable())
		return false;
	--l;

	while (l >= 0 && !source.vertex(l)->isConnectable())
		--l;

	if (l < 1)
		return false;

	muRec = MutationRecord(ECausticPerturbation, l, m, m-l,
		source.getPrefixSuffixWeight(l, m));
	statsAccepted.incrementBase();
	statsGenerated.incrementBase();

	/* Heuristic perturbation size computation (Veach, p.354) */
	Float lengthE = source.edge(m-1)->length;
	Float lengthL = 0;
	for (int i=l; i<m-1; ++i)
		lengthL += source.edge(i)->length;
	Float factor = lengthE/lengthL,
		theta1 = m_theta1 * factor,
		theta2 = m_theta2 * factor;

	Vector woSource = normalize(source.vertex(l+1)->getPosition()
			- source.vertex(l)->getPosition());
	Float phi = m_sampler->next1D() * 2 * M_PI;
	Float theta = theta2 * math::fastexp(m_logRatio * m_sampler->next1D());
	Vector wo = Frame(woSource).toWorld(sphericalDirection(theta, phi));

	/* Allocate memory for the proposed path */
	proposal.clear();
	proposal.append(source, 0, l+1);
	proposal.append(m_pool.allocEdge());
	for (int i=l+1; i<m; ++i) {
		proposal.append(m_pool.allocVertex());
		proposal.append(m_pool.allocEdge());
	}
	proposal.append(source, m, k+1);
	proposal.vertex(l) = proposal.vertex(l)->clone(m_pool);
	proposal.vertex(m) = proposal.vertex(m)->clone(m_pool);
	BDAssert(proposal.vertexCount() == source.vertexCount());
	BDAssert(proposal.edgeCount() == source.edgeCount());

	Float dist = source.edge(l)->length +
		perturbMediumDistance(m_sampler, source.vertex(l+1));

	/* Sample a perturbation and propagate it through specular interactions */
	if (!proposal.vertex(l)->perturbDirection(m_scene,
			proposal.vertex(l-1), proposal.edge(l-1),
			proposal.edge(l), proposal.vertex(l+1), wo, dist,
			source.vertex(l+1)->getType(), EImportance)) {
		proposal.release(l, m+1, m_pool);
		return false;
	}

	Vector woProposal = normalize(proposal.vertex(l+1)->getPosition()
			- source.vertex(l)->getPosition());
	theta = unitAngle(woSource, woProposal);
	if (theta >= theta2 || theta <= theta1) {
		proposal.release(l, m+1, m_pool);
		return false;
	}

	/* If necessary, propagate the perturbation through a sequence of
	   ideally specular interactions */
	for (int i=l+1; i<m-1; ++i) {
		Float dist = source.edge(i)->length +
			perturbMediumDistance(m_sampler, source.vertex(i+1));

		if (!proposal.vertex(i)->propagatePerturbation(m_scene,
				proposal.vertex(i-1), proposal.edge(i-1),
				proposal.edge(i), proposal.vertex(i+1),
				source.vertex(i)->getComponentType(), dist,
				source.vertex(i+1)->getType(), EImportance)) {
			proposal.release(l, m+1, m_pool);
			return false;
		}
	}

	if (!PathVertex::connect(m_scene,
			proposal.vertex(m-2),
			proposal.edge(m-2),
			proposal.vertex(m-1),
			proposal.edge(m-1),
			proposal.vertex(m),
			proposal.edge(m),
			proposal.vertex(m+1))) {
		proposal.release(l, m+1, m_pool);
		return false;
	}

	proposal.vertex(k-1)->updateSamplePosition(
		proposal.vertex(k-2));

	++statsGenerated;
	return true;
}
	void process(const WorkUnit *workUnit, WorkResult *workResult, const bool &stop) {
		ImageBlock *result = static_cast<ImageBlock *>(workResult);
		const SeedWorkUnit *wu = static_cast<const SeedWorkUnit *>(workUnit);
		Path *current = new Path(), *proposed = new Path();
		Spectrum relWeight(0.0f);

		result->clear();

		/// Reconstruct the seed path
		m_pathSampler->reconstructPath(wu->getSeed(), m_config.importanceMap, *current);
		relWeight = current->getRelativeWeight();
		BDAssert(!relWeight.isZero());

		DiscreteDistribution suitabilities(m_mutators.size());
		MutationRecord muRec, currentMuRec(Mutator::EMutationTypeCount,0,0,0,Spectrum(0.f));
		ref<Timer> timer = new Timer();

		size_t consecRejections = 0;
		Float accumulatedWeight = 0;

		#if defined(MTS_DEBUG_FP)
			enableFPExceptions();
		#endif

		#if defined(MTS_BD_DEBUG_HEAVY)
			std::ostringstream oss;
			Path backup;
		#endif
		for (size_t mutationCtr=0; mutationCtr < m_config.nMutations
				&& !stop; ++mutationCtr) {
			if (wu->getTimeout() > 0 && (mutationCtr % 8192) == 0 &&
					(int) timer->getMilliseconds() > wu->getTimeout())
				break;

			/* Query all mutators for their suitability */
			suitabilities.clear();
			for (size_t j=0; j<m_mutators.size(); ++j)
				suitabilities.append(m_mutators[j]->suitability(*current));
			#if defined(MTS_BD_DEBUG_HEAVY)
				current->clone(backup, *m_pool);
			#endif

			size_t mutatorIdx = 0;
			bool success = false;
			Mutator *mutator = NULL;

			if (suitabilities.normalize() == 0) {
				/* No mutator can handle this path -- give up */
				size_t skip = m_config.nMutations - mutationCtr;
				accumulatedWeight += skip;
				consecRejections += skip;
				break;
			}

			mutatorIdx = suitabilities.sample(m_sampler->next1D());
			mutator = m_mutators[mutatorIdx].get();

			/* Sample a mutated path */
			success = mutator->sampleMutation(*current, *proposed, muRec, currentMuRec);

			#if defined(MTS_BD_DEBUG_HEAVY)
				if (backup != *current)
					Log(EError, "Detected an unexpected path modification after a "
						"mutation of type %s (k=%i)!", muRec.toString().c_str(),
						current->length());
				if (success) {
					bool fail = false;
					for (int i=0; i<muRec.l; ++i)
						if (*backup.vertex(i) != *proposed->vertex(i))
							fail = true;

					for (int i=1; i <= backup.length() - muRec.m; ++i)
						if (*backup.vertex(muRec.m+i) != *proposed->vertex(muRec.l+muRec.ka+i))
							fail = true;
					if (fail)
						Log(EError, "Detected an unexpected path modification outside of the "
							"specified range after a mutation of type %s (k=%i)!",
							muRec.toString().c_str(), current->length());
				}
				backup.release(*m_pool);
			#endif

			statsAccepted.incrementBase(1);
			if (success) {
				Float Qxy = mutator->Q(*current, *proposed, muRec) * suitabilities[mutatorIdx];
				suitabilities.clear();
				for (size_t j=0; j<m_mutators.size(); ++j)
					suitabilities.append(m_mutators[j]->suitability(*proposed));
				suitabilities.normalize();
				Float Qyx = mutator->Q(*proposed, *current, muRec.reverse()) * suitabilities[mutatorIdx];

				Float a;
				if (!m_config.importanceMap) {
					if(Qxy > RCPOVERFLOW)
					a = std::min((Float) 1, Qyx / Qxy);
					else
						a = 0.f;
				} else {
					const Float *luminanceValues = m_config.importanceMap->getFloatData();
					const Point2 &curPos = current->getSamplePosition();
					const Point2 &propPos = proposed->getSamplePosition();
					Vector2i size = m_config.importanceMap->getSize();
					Point2i curPosI(
						std::min(std::max(0, (int) curPos.x), size.x-1),
						std::min(std::max(0, (int) curPos.y), size.y-1));
					Point2i propPosI(
						std::min(std::max(0, (int) propPos.x), size.x-1),
						std::min(std::max(0, (int) propPos.y), size.y-1));

					Float curValue = luminanceValues[curPosI.x + curPosI.y * size.x];
					Float propValue = luminanceValues[propPosI.x + propPosI.y * size.x];

					a = std::min((Float) 1, (Qyx * curValue) / (Qxy * propValue));
				}

				#if defined(MTS_BD_DEBUG_HEAVY)
					if (!proposed->verify(m_scene, EImportance, oss)) {
						Log(EWarn, "%s proposed as %s, Qxy=%f, Qyx=%f", oss.str().c_str(),
								muRec.toString().c_str(), Qxy, Qyx);
						proposed->release(muRec.l, muRec.l + muRec.ka + 1, *m_pool);
						oss.str("");
						continue;
					}
				#endif

				if (Qxy == 0) { // be tolerant of this (can occasionally happen due to floating point inaccuracies)
					a = 0;
				} else if (Qxy < 0 || Qyx < 0 || std::isnan(Qxy) || std::isnan(Qyx)) {
					#if defined(MTS_BD_DEBUG)
						Log(EDebug, "Source path: %s", current->toString().c_str());
						Log(EDebug, "Proposal path: %s", proposed->toString().c_str());
						Log(EWarn, "Internal error while computing acceptance probabilities: "
							"Qxy=%f, Qyx=%f, muRec=%s", Qxy, Qyx, muRec.toString().c_str());
					#endif
					a = 0;
				}

				accumulatedWeight += 1-a;

				/* Accept with probability 'a' */
				if (a == 1 || m_sampler->next1D() < a) {
					current->release(muRec.l, muRec.m+1, *m_pool);
					Spectrum value = relWeight * accumulatedWeight;
					if (!value.isZero())
						result->put(current->getSamplePosition(), &value[0]);

					/* The mutation was accepted */
					std::swap(current, proposed);
					relWeight = current->getRelativeWeight();
					mutator->accept(muRec);
					currentMuRec = muRec;
					accumulatedWeight = a;
					consecRejections = 0;
					++statsAccepted;
				} else {
					/* The mutation was rejected */
					proposed->release(muRec.l, muRec.l + muRec.ka + 1, *m_pool);
					consecRejections++;
					if (a > 0) {
						Spectrum value = proposed->getRelativeWeight() * a;
						result->put(proposed->getSamplePosition(), &value[0]);
					}
				}
			} else {
				accumulatedWeight += 1;
				consecRejections++;
			}
		}
		#if defined(MTS_BD_DEBUG)
			if (consecRejections == m_config.nMutations)
				Log(EWarn, "Encountered a path that could *never* be mutated!: %s",
					current->toString().c_str());
		#endif

		if (accumulatedWeight > 0) {
			Spectrum value = relWeight * accumulatedWeight;
			result->put(current->getSamplePosition(), &value[0]);
		}

		#if defined(MTS_DEBUG_FP)
			disableFPExceptions();
		#endif

		current->release(*m_pool);
		delete current;
		delete proposed;
		if (!m_pool->unused())
			Log(EError, "Internal error: detected a memory pool leak!");
	}
Example #6
0
bool BidirectionalMutator::sampleMutation(
		Path &source, Path &proposal, MutationRecord &muRec) {
	TwoTailedGeoDistr desiredLength(2), deletionLength(2);
	int k = source.length();

	/* Sample the desired path length of the proposal. This
	   is done using a two-tailed geometric distribution that is
	   centered around the current path length, and which respects
	   the specified minimum and maximum length constraints. */

	desiredLength.configure(k, m_kmin, m_kmax);
	int kPrime = desiredLength.sample(m_sampler->next1D());

	/* Sample the length of the deletion (in # of edges, 1 means
	   no vertices are removed). When kPrime is smaller than k,
	   we must delete at least k-kPrime+1 edges to be able to
	   achieve the desired path length.

	   When k==kPrime, we must delete *something*, or the mutation
	   is trivial, hence the conditional below expression. */

	int minDeletion = std::max((k == kPrime) ? 2 : 1, k-kPrime+1);
	deletionLength.configure(2, minDeletion, k);
	int kd = deletionLength.sample(m_sampler->next1D());

	/* Based on the desired length, this tells us how many
	   edges need to be added (k' = k - kd + ka) */
	int ka = kPrime-k+kd;

	/* Sample the left endpoint of the deleted range */
	int lMin = 0, lMax = k - kd;
	if (kd == 1 || ka == 1) {
		/* This will help to avoid certain path changes that would otherwise
		   always be rejected. Specifically, we don't want to remove the sensor
		   or emitter sample vertex, and we don't want to insert
		   vertices between a sensor/emitter sample and its supernode */
		lMin++; lMax--;
	}
	m_temp.clear();
	for (int l=lMin; l<=lMax; ++l) {
		int m = l+kd;
		if (!source.vertex(l)->isDegenerate() &&
			!source.vertex(m)->isDegenerate())
			m_temp.push_back(l);
	}
	if (m_temp.size() == 0)
		return false;

	int l = m_temp[std::min((int) (m_temp.size() *
			m_sampler->next1D()), (int) m_temp.size()-1)];
	int m = l+kd;

	/* Don't try to hit the emitter or sensor if they are degenerate */
	int sMin = 0, sMax = ka-1;
	if (l == 0 && m_scene->hasDegenerateEmitters())
		++sMin;
	else if (m == k && m_scene->hasDegenerateSensor())
		--sMax;

	/* Sample the number of SIS-type steps to take from the emitter direction */
	int s = std::min(sMin + (int) ((sMax-sMin+1) * m_sampler->next1D()), sMax);
	int t = ka - s - 1;

	/* Check a few assumptions */
	BDAssert(ka >= 1 && kd >= 1 && kd <= k
			&& l >= lMin && l <= lMax
			&& kPrime == k - kd + ka
			&& kPrime >= m_kmin
			&& kPrime <= m_kmax);

	/* Construct a mutation record */
	muRec = MutationRecord(EBidirectionalMutation, l, m, ka,
		source.getPrefixSuffixWeight(l, m));

	/* Keep some statistics */
	statsGenerated.incrementBase();
	statsAccepted.incrementBase();

	proposal.clear();
	proposal.append(source, 0, l+1);
	proposal.vertex(l) = proposal.vertex(l)->clone(m_pool);

	/* Perform a random walk from the emitter direction */
	if (proposal.randomWalk(m_scene, m_sampler, s, -1, EImportance, m_pool) != s) {
		proposal.release(l, proposal.vertexCount(), m_pool);
		return false;
	}

	/* Perform a random walk from the sensor direction */
	m_tempPath.clear();
	m_tempPath.append(source, m, k+1, true);
	m_tempPath.vertex(k-m) = m_tempPath.vertex(k-m)->clone(m_pool);

	if (m_tempPath.randomWalk(m_scene, m_sampler, t, -1, ERadiance, m_pool) != t) {
		proposal.release(l, proposal.vertexCount(), m_pool);
		m_tempPath.release(k-m, m_tempPath.vertexCount(), m_pool);
		return false;
	}

	PathEdge *connectionEdge = m_pool.allocEdge();
	proposal.append(connectionEdge);
	proposal.append(m_tempPath, 0, m_tempPath.vertexCount(), true);

	BDAssert(proposal.length() == kPrime &&
			 proposal.vertexCount() == proposal.edgeCount() + 1);

	const PathVertex
		*vsPred = l+s > 0 ? proposal.vertex(l+s-1) : NULL,
		*vtPred = l+s+2 <= kPrime ? proposal.vertex(l+s+2) : NULL;
	const PathEdge
		*vsEdge = l+s > 0 ? proposal.edge(l+s-1) : NULL,
		*vtEdge = l+s+1 < kPrime ? proposal.edge(l+s+1) : NULL;

	/* Now try to connect the two subpaths and reject
	   the proposal if there is no throughput */
	PathVertex *vs = proposal.vertex(l+s),
			   *vt = proposal.vertex(l+s+1);

	if (!PathVertex::connect(m_scene, vsPred,
			vsEdge, vs, connectionEdge, vt, vtEdge, vtPred)) {
		proposal.release(l, l+ka+1, m_pool);
		return false;
	}

	if (m >= k-1)
		proposal.vertex(kPrime-1)->updateSamplePosition(
			proposal.vertex(kPrime-2));

	++statsGenerated;
	return true;
}
Example #7
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;
}