Vec3f TraceBase::attenuatedEmission(PathSampleGenerator &sampler, const Primitive &light, const Medium *medium, float expectedDist, IntersectionTemporary &data, IntersectionInfo &info, int bounce, bool startsOnSurface, Ray &ray, Vec3f *transmittance) { CONSTEXPR float fudgeFactor = 1.0f + 1e-3f; if (light.isDirac()) { ray.setFarT(expectedDist); } else { if (!light.intersect(ray, data) || ray.farT()*fudgeFactor < expectedDist) return Vec3f(0.0f); } info.p = ray.pos() + ray.dir()*ray.farT(); info.w = ray.dir(); light.intersectionInfo(data, info); Vec3f shadow = generalizedShadowRay(sampler, ray, medium, &light, startsOnSurface, true, bounce); if (transmittance) *transmittance = shadow; if (shadow == 0.0f) return Vec3f(0.0f); return shadow*light.evalDirect(data, info); }
SurfaceScatterEvent TraceBase::makeLocalScatterEvent(IntersectionTemporary &data, IntersectionInfo &info, Ray &ray, PathSampleGenerator *sampler) const { TangentFrame frame; info.primitive->setupTangentFrame(data, info, frame); bool hitBackside = frame.normal.dot(ray.dir()) > 0.0f; bool isTransmissive = info.bsdf->lobes().isTransmissive(); bool flipFrame = _settings.enableTwoSidedShading && hitBackside && !isTransmissive; if (flipFrame) { // TODO: Should we flip info.Ns here too? It doesn't seem to be used at the moment, // but it may be in the future. Modifying the intersection info itself could be a bad // idea though frame.normal = -frame.normal; frame.tangent = -frame.tangent; } return SurfaceScatterEvent( &info, sampler, frame, frame.toLocal(-ray.dir()), BsdfLobes::AllLobes, flipFrame ); }
bool Quad::intersect(Ray &ray, IntersectionTemporary &data) const { float nDotW = ray.dir().dot(_frame.normal); if (std::abs(nDotW) < 1e-6f) return false; float t = _frame.normal.dot(_base - ray.pos())/nDotW; if (t < ray.nearT() || t > ray.farT()) return false; Vec3f q = ray.pos() + t*ray.dir(); Vec3f v = q - _base; float l0 = v.dot(_edge0)*_invUvSq.x(); float l1 = v.dot(_edge1)*_invUvSq.y(); if (l0 < 0.0f || l0 > 1.0f || l1 < 0.0f || l1 > 1.0f) return false; ray.setFarT(t); QuadIntersection *isect = data.as<QuadIntersection>(); isect->p = q; isect->u = l0; isect->v = 1.0f - l1; isect->backSide = nDotW >= 0.0f; data.primitive = this; return true; }
Vec3f AtmosphericMedium::transmittance(PathSampleGenerator &/*sampler*/, const Ray &ray) const { Vec3f p = (ray.pos() - _center); float t0 = p.dot(ray.dir()); float t1 = ray.farT() + t0; float h = (p - t0*ray.dir()).length(); return std::exp(-_sigmaT*densityIntegral(h, t0, t1)); }
bool Cylinder::intersect_unit_circle( const Ray& inc, Ray& hit ) { double Z = inc.from().z(); double z = -inc.dir().z(); Vec3 v = inc.from() + inc.dir() * Z / z; v.z() = 0; if( v.l2() > 1 ) return false; hit.from() = v; return true; }
bool Triangle::hit ( const Ray& original_ray, interval_t& tmin ) const { Ray ray = original_ray.transform(inv_trans_); float A = a_[0] - b_[0]; float B = a_[1] - b_[1]; float C = a_[2] - b_[2]; float D = a_[0] - c_[0]; float E = a_[1] - c_[1]; float F = a_[2] - c_[2]; float G = ray.dir()[0]; float H = ray.dir()[1]; float I = ray.dir()[2]; float J = a_[0] - ray.origin()[0]; float K = a_[1] - ray.origin()[1]; float L = a_[2] - ray.origin()[2]; float EIHF = E*I - H*F; float GFDI = G*F - D*I; float DHEG = D*H - E*G; float denom = A*EIHF + B*GFDI + C*DHEG; float beta = ( J*EIHF + K*GFDI + L*DHEG ) / denom; if ( beta <= 0.0 or beta >= 1.0 ) return false; float AKJB = A*K - J*B; float JCAL = J*C - A*L; float BLKC = B*L - K*C; float gamma = ( I*AKJB + H*JCAL + G*BLKC ) / denom; if ( gamma <= 0.0 or beta + gamma >= 1.0 ) return false; float t = -( F*AKJB + E*JCAL + D*BLKC ) / denom; if ( t > epsilon ) { tmin = t; return true; } return false; }
inline RTCRay convert(const Ray &r) { RTCRay ray; ray.org[0] = r.pos().x(); ray.org[1] = r.pos().y(); ray.org[2] = r.pos().z(); ray.dir[0] = r.dir().x(); ray.dir[1] = r.dir().y(); ray.dir[2] = r.dir().z(); ray.tnear = r.nearT(); ray.tfar = r.farT(); ray.geomID = RTC_INVALID_GEOMETRY_ID; ray.primID = RTC_INVALID_GEOMETRY_ID; return ray; }
Vec3f TraceBase::volumePhaseSample(const Primitive &light, PathSampleGenerator &sampler, MediumSample &mediumSample, const Medium *medium, int bounce, const Ray &parentRay) { PhaseSample phaseSample; if (!mediumSample.phase->sample(sampler, parentRay.dir(), phaseSample)) return Vec3f(0.0f); Ray ray = parentRay.scatter(mediumSample.p, phaseSample.w, 0.0f); ray.setPrimaryRay(false); IntersectionTemporary data; IntersectionInfo info; Vec3f e = attenuatedEmission(sampler, light, medium, -1.0f, data, info, bounce, false, ray, nullptr); if (e == Vec3f(0.0f)) return Vec3f(0.0f); Vec3f phaseF = e*phaseSample.weight; phaseF *= SampleWarp::powerHeuristic(phaseSample.pdf, light.directPdf(_threadId, data, info, mediumSample.p)); return phaseF; }
Vec3f TraceBase::volumeLightSample(PathSampleGenerator &sampler, MediumSample &mediumSample, const Primitive &light, const Medium *medium, int bounce, const Ray &parentRay) { LightSample lightSample; if (!light.sampleDirect(_threadId, mediumSample.p, sampler, lightSample)) return Vec3f(0.0f); Vec3f f = mediumSample.phase->eval(parentRay.dir(), lightSample.d); if (f == 0.0f) return Vec3f(0.0f); Ray ray = parentRay.scatter(mediumSample.p, lightSample.d, 0.0f); ray.setPrimaryRay(false); IntersectionTemporary data; IntersectionInfo info; Vec3f e = attenuatedEmission(sampler, light, medium, lightSample.dist, data, info, bounce, false, ray, nullptr); if (e == 0.0f) return Vec3f(0.0f); Vec3f lightF = f*e/lightSample.pdf; if (!light.isDirac()) lightF *= SampleWarp::powerHeuristic(lightSample.pdf, mediumSample.phase->pdf(parentRay.dir(), lightSample.d)); return lightF; }
bool TraceBase::volumeLensSample(const Camera &camera, PathSampleGenerator &sampler, MediumSample &mediumSample, const Medium *medium, int bounce, const Ray &parentRay, Vec3f &weight, Vec2f &pixel) { LensSample lensSample; if (!camera.sampleDirect(mediumSample.p, sampler, lensSample)) return false; Vec3f f = mediumSample.phase->eval(parentRay.dir(), lensSample.d); if (f == 0.0f) return false; Ray ray = parentRay.scatter(mediumSample.p, lensSample.d, 0.0f); ray.setPrimaryRay(false); ray.setFarT(lensSample.dist); Vec3f transmittance = generalizedShadowRay(sampler, ray, medium, nullptr, false, true, bounce); if (transmittance == 0.0f) return false; weight = f*transmittance*lensSample.weight; pixel = lensSample.pixel; return true; }
bool intersectAabb(const Aabb& box, const Ray& ray, floating& a, floating& b) { const Point origin = ray.origin(); const Vector dir = ray.dir(); a = std::numeric_limits<floating>::min(); b = std::numeric_limits<floating>::max(); for (uint8_t i = 0; i < 3; ++i) { if (almost_zero(dir[i])) { if (origin[i] < box.m_p1[i] || origin[i] > box.m_p2[i]) { return false; } continue; } floating idir = 1.0 / dir[i]; floating at = (box.m_p1[i] - origin[i]) * idir; floating bt = (box.m_p2[i] - origin[i]) * idir; if (at > bt) idir = at, at = bt, bt = idir; if (at > a) a = at; if (bt < b) b = bt; if (b < 0 || a > b) return false; } return true; }
bool Cube::intersect(Ray &ray, IntersectionTemporary &data) const { Vec3f p = _invRot*(ray.pos() - _pos); Vec3f d = _invRot*ray.dir(); Vec3f invD = 1.0f/d; Vec3f relMin((-_scale - p)); Vec3f relMax(( _scale - p)); float ttMin = ray.nearT(), ttMax = ray.farT(); for (int i = 0; i < 3; ++i) { if (invD[i] >= 0.0f) { ttMin = max(ttMin, relMin[i]*invD[i]); ttMax = min(ttMax, relMax[i]*invD[i]); } else { ttMax = min(ttMax, relMin[i]*invD[i]); ttMin = max(ttMin, relMax[i]*invD[i]); } } if (ttMin <= ttMax) { if (ttMin > ray.nearT() && ttMin < ray.farT()) { data.primitive = this; ray.setFarT(ttMin); data.as<CubeIntersection>()->backSide = false; } else if (ttMax > ray.nearT() && ttMax < ray.farT()) { data.primitive = this; ray.setFarT(ttMax); data.as<CubeIntersection>()->backSide = true; } return true; } return false; }
bool Sphere::intersectP(Ray &ray){ float a, b, c,t1, t2, discriminant; Vector3f dir = ray.dir(); Vector3f e_minus_c = ray.pos().sub(center); a = dir.dot(dir); b= (2*dir).dot((e_minus_c)); c = (e_minus_c).dot(e_minus_c)-pow(radius,2.0); discriminant = pow(b,2.0)-(4*a*c); if(discriminant < 0){ return false; } else{ float sqrtdisc = sqrt(discriminant); t1 = (-b + sqrtdisc) / (2 * a); t2 = (-b - sqrtdisc) / (2 * a); if((t1 > ray.t_min() && t1 < ray.t_max()) || (t2 > ray.t_min() && t2 < ray.t_max())){ return true; } else{ return false; } } }
/** * Performs ray/box intersection. Returns true if the ray * intersects the box, and the hit time for the entry/exit * in tmin/tmax respectively. */ bool AABB::intersect(const Ray& ray, float& tmin, float& tmax) const { float t0 = ray.minT; float t1 = ray.maxT; // Loop over the three axes and compute the hit time for the // two axis-aligned bounding box planes in each, decreasing the // parametric range of the ray until start>end time, which means // the ray missed the box, or until we finish which means there // is an intersection. for (int i = 0; i < 3; i++) { float invDir = 1.0f / ray.dir(i); float tNear = (mMin(i) - ray.orig(i)) * invDir; float tFar = (mMax(i) - ray.orig(i)) * invDir; if (tNear > tFar) swap(tNear,tFar); if (tNear > t0) t0 = tNear; if (tFar < t1) t1 = tFar; if (t0 > t1) return false; } tmin = t0; tmax = t1; return true; }
bool Sphere::intersect(Ray &ray, IntersectionTemporary &data) const { Vec3f p = ray.pos() - _pos; float B = p.dot(ray.dir()); float C = p.lengthSq() - _radius*_radius; float detSq = B*B - C; if (detSq >= 0.0f) { float det = std::sqrt(detSq); float t = -B - det; if (t < ray.farT() && t > ray.nearT()) { ray.setFarT(t); data.primitive = this; data.as<SphereIntersection>()->backSide = false; return true; } t = -B + det; if (t < ray.farT() && t > ray.nearT()) { ray.setFarT(t); data.primitive = this; data.as<SphereIntersection>()->backSide = true; return true; } } return false; }
bool Quad::occluded(const Ray &ray) const { float nDotW = ray.dir().dot(_frame.normal); float t = _frame.normal.dot(_base - ray.pos())/nDotW; if (t < ray.nearT() || t > ray.farT()) return false; Vec3f q = ray.pos() + t*ray.dir(); Vec3f v = q - _base; float l0 = v.dot(_edge0)*_invUvSq.x(); float l1 = v.dot(_edge1)*_invUvSq.y(); if (l0 < 0.0f || l0 > 1.0f || l1 < 0.0f || l1 > 1.0f) return false; return true; }
float AtmosphericMedium::pdf(PathSampleGenerator &/*sampler*/, const Ray &ray, bool onSurface) const { if (_absorptionOnly) { return 1.0f; } else { Vec3f p = (ray.pos() - _center); float t0 = p.dot(ray.dir()); float t1 = ray.farT() + t0; float h = (p - t0*ray.dir()).length(); Vec3f transmittance = std::exp(-_sigmaT*densityIntegral(h, t0, t1)); if (onSurface) { return transmittance.avg(); } else { return (density(h, t0)*_sigmaT*transmittance).avg(); } } }
Vec3f AtmosphericMedium::transmittanceAndPdfs(PathSampleGenerator &/*sampler*/, const Ray &ray, bool startOnSurface, bool endOnSurface, float &pdfForward, float &pdfBackward) const { Vec3f p = (ray.pos() - _center); float t0 = p.dot(ray.dir()); float t1 = ray.farT() + t0; float h = (p - t0*ray.dir()).length(); Vec3f transmittance = std::exp(-_sigmaT*densityIntegral(h, t0, t1)); if (_absorptionOnly) { pdfForward = pdfBackward = 1.0f; } else { pdfForward = endOnSurface ? transmittance.avg() : (density(h, t1)*_sigmaT*transmittance).avg(); pdfBackward = startOnSurface ? transmittance.avg() : (density(h, t0)*_sigmaT*transmittance).avg(); } return transmittance; }
Vec3f ExponentialMedium::transmittance(PathSampleGenerator &/*sampler*/, const Ray &ray) const { float x = _falloffScale*(ray.pos() - _unitPoint).dot(_unitFalloffDirection); float dx = _falloffScale*ray.dir().dot(_unitFalloffDirection); if (ray.farT() == Ray::infinity() && dx <= 0.0f) return Vec3f(0.0f); else return std::exp(-_sigmaT*densityIntegral(x, dx, ray.farT())); }
bool ExponentialMedium::sampleDistance(PathSampleGenerator &sampler, const Ray &ray, MediumState &state, MediumSample &sample) const { if (state.bounce > _maxBounce) return false; float x = _falloffScale*(ray.pos() - _unitPoint).dot(_unitFalloffDirection); float dx = _falloffScale*ray.dir().dot(_unitFalloffDirection); float maxT = ray.farT(); if (_absorptionOnly) { if (maxT == Ray::infinity() && dx <= 0.0f) return false; sample.t = maxT; sample.weight = std::exp(-_sigmaT*densityIntegral(x, dx, ray.farT())); sample.pdf = 1.0f; sample.exited = true; } else { int component = sampler.nextDiscrete(3); float sigmaTc = _sigmaT[component]; float xi = 1.0f - sampler.next1D(); float logXi = std::log(xi); float t = inverseOpticalDepth(x, dx, sigmaTc, logXi); sample.t = min(t, maxT); sample.weight = std::exp(-_sigmaT*densityIntegral(x, dx, sample.t)); sample.exited = (t >= maxT); if (sample.exited) { sample.pdf = sample.weight.avg(); } else { float rho = density(x, dx, sample.t); sample.pdf = (rho*_sigmaT*sample.weight).avg(); sample.weight *= rho*_sigmaS; } sample.weight /= sample.pdf; state.advance(); } sample.p = ray.pos() + sample.t*ray.dir(); sample.phase = _phaseFunction.get(); return true; }
bool AtmosphericMedium::sampleDistance(PathSampleGenerator &sampler, const Ray &ray, MediumState &state, MediumSample &sample) const { if (state.bounce > _maxBounce) return false; Vec3f p = (ray.pos() - _center); float t0 = p.dot(ray.dir()); float h = (p - t0*ray.dir()).length(); float maxT = ray.farT() + t0; if (_absorptionOnly) { sample.t = ray.farT(); sample.weight = std::exp(-_sigmaT*densityIntegral(h, t0, maxT)); sample.pdf = 1.0f; sample.exited = true; } else { int component = sampler.nextDiscrete(3); float sigmaTc = _sigmaT[component]; float xi = 1.0f - sampler.next1D(); float t = inverseOpticalDepth(h, t0, sigmaTc, xi); sample.t = min(t, maxT); sample.weight = std::exp(-_sigmaT*densityIntegral(h, t0, sample.t)); sample.exited = (t >= maxT); if (sample.exited) { sample.pdf = sample.weight.avg(); } else { float rho = density(h, sample.t); sample.pdf = (rho*_sigmaT*sample.weight).avg(); sample.weight *= rho*_sigmaS; } sample.weight /= sample.pdf; sample.t -= t0; state.advance(); } sample.p = ray.pos() + sample.t*ray.dir(); sample.phase = _phaseFunction.get(); return true; }
bool TraceBase::handleSurface(SurfaceScatterEvent &event, IntersectionTemporary &data, IntersectionInfo &info, const Medium *&medium, int bounce, bool adjoint, bool enableLightSampling, Ray &ray, Vec3f &throughput, Vec3f &emission, bool &wasSpecular, Medium::MediumState &state, Vec3f *transmittance) { const Bsdf &bsdf = *info.bsdf; // For forward events, the transport direction does not matter (since wi = -wo) Vec3f transparency = bsdf.eval(event.makeForwardEvent(), false); float transparencyScalar = transparency.avg(); Vec3f wo; if (event.sampler->nextBoolean(transparencyScalar) ){ wo = ray.dir(); event.pdf = transparencyScalar; event.weight = transparency/transparencyScalar; event.sampledLobe = BsdfLobes::ForwardLobe; throughput *= event.weight; } else { if (!adjoint) { if (enableLightSampling && bounce < _settings.maxBounces - 1) emission += estimateDirect(event, medium, bounce + 1, ray, transmittance)*throughput; if (info.primitive->isEmissive() && bounce >= _settings.minBounces) { if (!enableLightSampling || wasSpecular || !info.primitive->isSamplable()) emission += info.primitive->evalDirect(data, info)*throughput; } } event.requestedLobe = BsdfLobes::AllLobes; if (!bsdf.sample(event, adjoint)) return false; wo = event.frame.toGlobal(event.wo); if (!isConsistent(event, wo)) return false; throughput *= event.weight; wasSpecular = event.sampledLobe.hasSpecular(); if (!wasSpecular) ray.setPrimaryRay(false); } bool geometricBackside = (wo.dot(info.Ng) < 0.0f); medium = info.primitive->selectMedium(medium, geometricBackside); state.reset(); ray = ray.scatter(ray.hitpoint(), wo, info.epsilon); return true; }
inline bool YZRect::hit(const Ray& r, float t_min, float t_max, hit_record& rec) const { auto t = (_k-r.origin().x)/r.dir().x; if(t < t_min || t > t_max) return false; auto interesction = r.pointAtParam(t); if(interesction.y < _y_dim.x || interesction.y > _y_dim.y || interesction.z < _z_dim.x || interesction.z > _z_dim.y) return false; rec.uv = glm::vec2((interesction.y - _y_dim.x)/(_y_dim.y - _y_dim.x), (interesction.z - _z_dim.x)/(_z_dim.y - _z_dim.x)); rec.t = t; rec.mat = _mat; rec.p = interesction; rec.n = glm::vec3(1,0,0); return true; }
bool Sphere::intersect(Ray& ray, float *thit, LocalGeo *local){ /*If the discriminant is positive, there are two solutions: one solution where the ray enters the sphere and one where it leaves. If the discriminant is zero, the ray grazes the sphere touching it at exactly one point.*/ float a, b, c, discriminant; Vector3f dir = ray.dir(); Vector3f e_minus_c = ray.pos().sub(center); a = dir.dot(dir); b= (2*dir).dot((e_minus_c)); c = (e_minus_c).dot(e_minus_c)-pow(radius,2.0); discriminant = pow(b,2.0)-(4*a*c); if(discriminant < 0){ return false; } else { float discr = pow( dir.dot(e_minus_c) ,2.0) - a*c; float t1 = (-dir.dot(e_minus_c) - sqrt( discr ))/a; float t2= (-dir.dot(e_minus_c) + sqrt( discr ))/a; float t = fmin(t1,t2); if (t<ray.t_min() || t>ray.t_max()){ return false; } else { *thit=t; ray.pos().add(t*ray.dir()); local->setPos(ray.pos()); local->setNormal((ray.pos().sub(center))/radius); //normal will be vector3f } return true; } }
float Triangle::crossing(const Ray & r) { //_mm_prefetch(&v0, _MM_HINT_T0); vec4 pos = r.pos(); vec4 dir = r.dir(); vec4 t = v0 - pos; vec4 q = cross(dir, t); vec4 tmp (dot(t, normal), dot(e2, q), dot(e1, q), 0.0f); vec4 tuv = tmp * 1.0f / dot (dir, normal); int b = (tuv[1]>=0.0f && tuv[2]>=0.0f && tuv[0]>=0.0f && (tuv[1] + tuv[2] <= 1.0f)); return b*tuv[0] + (b - 1); }
float ExponentialMedium::pdf(PathSampleGenerator &/*sampler*/, const Ray &ray, bool onSurface) const { if (_absorptionOnly) { return 1.0f; } else { float x = _falloffScale*(ray.pos() - _unitPoint).dot(_unitFalloffDirection); float dx = _falloffScale*ray.dir().dot(_unitFalloffDirection); Vec3f transmittance = std::exp(-_sigmaT*densityIntegral(x, dx, ray.farT())); if (onSurface) { return transmittance.avg(); } else { return (density(x, dx, ray.farT())*_sigmaT*transmittance).avg(); } } }
bool Sphere::occluded(const Ray &ray) const { Vec3f p = ray.pos() - _pos; float B = p.dot(ray.dir()); float C = p.lengthSq() - _radius*_radius; float detSq = B*B - C; if (detSq >= 0.0f) { float det = std::sqrt(detSq); float t = -B - det; if (t < ray.farT() && t > ray.nearT()) return true; t = -B + det; if (t < ray.farT() && t > ray.nearT()) return true; } return false; }
bool TraceBase::handleVolume(PathSampleGenerator &sampler, MediumSample &mediumSample, const Medium *&medium, int bounce, bool adjoint, bool enableLightSampling, Ray &ray, Vec3f &throughput, Vec3f &emission, bool &wasSpecular) { wasSpecular = !enableLightSampling; if (!adjoint && enableLightSampling && bounce < _settings.maxBounces - 1) emission += throughput*volumeEstimateDirect(sampler, mediumSample, medium, bounce + 1, ray); PhaseSample phaseSample; if (!mediumSample.phase->sample(sampler, ray.dir(), phaseSample)) return false; ray = ray.scatter(mediumSample.p, phaseSample.w, 0.0f); ray.setPrimaryRay(false); throughput *= phaseSample.weight; return true; }
Vec3f ExponentialMedium::transmittanceAndPdfs(PathSampleGenerator &/*sampler*/, const Ray &ray, bool startOnSurface, bool endOnSurface, float &pdfForward, float &pdfBackward) const { float x = _falloffScale*(ray.pos() - _unitPoint).dot(_unitFalloffDirection); float dx = _falloffScale*ray.dir().dot(_unitFalloffDirection); if (ray.farT() == Ray::infinity() && dx <= 0.0f) { pdfForward = pdfBackward = 0.0f; return Vec3f(0.0f); } Vec3f transmittance = std::exp(-_sigmaT*densityIntegral(x, dx, ray.farT())); if (_absorptionOnly) { pdfForward = pdfBackward = 1.0f; } else { pdfForward = endOnSurface ? transmittance.avg() : (density(x, dx, ray.farT())*_sigmaT*transmittance).avg(); pdfBackward = startOnSurface ? transmittance.avg() : (density(x, dx, 0.0f)*_sigmaT*transmittance).avg(); } return transmittance; }
bool Cube::occluded(const Ray &ray) const { Vec3f p = _invRot*(ray.pos() - _pos); Vec3f d = _invRot*ray.dir(); Vec3f invD = 1.0f/d; Vec3f relMin((-_scale - p)); Vec3f relMax(( _scale - p)); float ttMin = ray.nearT(), ttMax = ray.farT(); for (int i = 0; i < 3; ++i) { if (invD[i] >= 0.0f) { ttMin = max(ttMin, relMin[i]*invD[i]); ttMax = min(ttMax, relMax[i]*invD[i]); } else { ttMax = min(ttMax, relMin[i]*invD[i]); ttMin = max(ttMin, relMax[i]*invD[i]); } } return ttMin <= ttMax; }