FORCE_INLINE virtual double evaluate_pdf(
            const void*         data,
            const Vector3d&     geometric_normal,
            const Basis3d&      shading_basis,
            const Vector3d&     outgoing,
            const Vector3d&     incoming,
            const int           modes) const
        {
            if (!(modes & Glossy))
                return 0.0;

            // If incoming and outgoing are on the same hemisphere
            // this is not a refraction.
            const Vector3d& n = shading_basis.get_normal();
            if (dot(incoming, n) * dot(outgoing, n) >= 0.0)
                return Absorption;

            const InputValues* values = static_cast<const InputValues*>(data);

            double ht_norm;
            const Vector3d ht = half_refraction_vector(
                outgoing,
                incoming,
                n,
                values->m_from_ior,
                values->m_to_ior,
                ht_norm);

            const Vector3d m = shading_basis.transform_to_local(ht);
            const double dwh_dwo = refraction_jacobian(
                incoming,
                values->m_to_ior,
                ht,
                ht_norm);

            return m_mdf->pdf(m, values->m_ax, values->m_ay) * dwh_dwo;
        }
        FORCE_INLINE virtual double evaluate(
            const void*         data,
            const bool          adjoint,
            const bool          cosine_mult,
            const Vector3d&     geometric_normal,
            const Basis3d&      shading_basis,
            const Vector3d&     outgoing,
            const Vector3d&     incoming,
            const int           modes,
            Spectrum&           value) const
        {
            if (!(modes & Glossy))
                return 0.0;

            // If incoming and outgoing are on the same hemisphere
            // this is not a refraction.
            const Vector3d& n = shading_basis.get_normal();
            if (dot(incoming, n) * dot(outgoing, n) >= 0.0)
                return Absorption;

            const InputValues* values = static_cast<const InputValues*>(data);

            double ht_norm;
            const Vector3d ht = half_refraction_vector(
                outgoing,
                incoming,
                n,
                values->m_from_ior,
                values->m_to_ior,
                ht_norm);

            const Vector3d m = shading_basis.transform_to_local(ht);

            const double G =
                m_mdf->G(
                    shading_basis.transform_to_local(incoming),
                    shading_basis.transform_to_local(outgoing),
                    m,
                    values->m_ax,
                    values->m_ay);

            if (G == 0.0)
                return 0.0;

            const double D = m_mdf->D(m, values->m_ax, values->m_ay);

            const double cos_oh = dot(outgoing, ht);
            const double cos_ih = dot(incoming, ht);
            const double cos_in = dot(incoming, n);
            const double cos_on = dot(outgoing, n);
 
            // [1] equation 21.
            double v = abs((cos_ih * cos_oh) / (cos_in * cos_on));
            v *= square(values->m_to_ior) * D * G;
            const double denom = values->m_to_ior * cos_ih + values->m_from_ior * cos_oh;
            v /= square(denom);
            value.set(static_cast<float>(v));

            const double dwh_dwo = refraction_jacobian(
                incoming,
                values->m_to_ior,
                ht,
                ht_norm);

            return m_mdf->pdf(m, values->m_ax, values->m_ay) * dwh_dwo;
        }
        FORCE_INLINE virtual Mode sample(
            SamplingContext&    sampling_context,
            const void*         data,
            const bool          adjoint,
            const bool          cosine_mult,
            const Vector3d&     geometric_normal,
            const Basis3d&      shading_basis,
            const Vector3d&     outgoing,
            Vector3d&           incoming,
            Spectrum&           value,
            double&             probability) const
        {
            const InputValues* values = static_cast<const InputValues*>(data);

            // Compute the incoming direction by sampling the MDF.
            sampling_context.split_in_place(2, 1);
            const Vector2d s = sampling_context.next_vector2<2>();
            const Vector3d m = m_mdf->sample(s, values->m_ax, values->m_ay);
            const Vector3d ht = shading_basis.transform_to_parent(m);

            if (!refract(
                    outgoing,
                    ht,
                    values->m_from_ior / values->m_to_ior,
                    incoming))
            {
                // Ignore TIR.
                return Absorption;
            }

            // If incoming and outgoing are on the same hemisphere
            // this is not a refraction.
            const Vector3d& n = shading_basis.get_normal();
            if (dot(incoming, n) * dot(outgoing, n) >= 0.0)
                return Absorption;

            const double G =
                m_mdf->G(
                    shading_basis.transform_to_local(incoming),
                    shading_basis.transform_to_local(outgoing),
                    m,
                    values->m_ax,
                    values->m_ay);

            if (G == 0.0)
                return Absorption;

            const double D = m_mdf->D(m, values->m_ax, values->m_ay);

            const double cos_oh = dot(outgoing, ht);
            const double cos_ih = dot(incoming, ht);
            const double cos_in = dot(incoming, n);
            const double cos_on = dot(outgoing, n);
 
            // [1] equation 21.
            double v = abs((cos_ih * cos_oh) / (cos_in * cos_on));
            v *= square(values->m_to_ior) * D * G;
            const double denom = values->m_to_ior * cos_ih + values->m_from_ior * cos_oh;
            v /= square(denom);
            value.set(static_cast<float>(v));

            const double ht_norm = norm(values->m_from_ior * outgoing + values->m_to_ior * incoming);
            const double dwh_dwo = refraction_jacobian(
                incoming,
                values->m_to_ior,
                ht,
                ht_norm);

            probability = m_mdf->pdf(m, values->m_ax, values->m_ay) * dwh_dwo;
            return Glossy;
        }