double compute_fast_ambient_occlusion(
    const SamplingContext&          sampling_context,
    const AOVoxelTreeIntersector&   intersector,
    const Vector3d&                 point,
    const Vector3d&                 geometric_normal,
    const Basis3d&                  shading_basis,
    const double                    max_distance,
    const size_t                    sample_count,
    double&                         min_distance)
{
    // Create a sampling context.
    SamplingContext child_sampling_context = sampling_context.split(2, sample_count);

    // Construct the ambient occlusion ray.
    ShadingRay::RayType ray;
    ray.m_org = point;
    ray.m_tmin = 0.0;
    ray.m_tmax = max_distance;

    size_t computed_samples = 0;
    size_t occluded_samples = 0;

    min_distance = max_distance;

    for (size_t i = 0; i < sample_count; ++i)
    {
        // Generate a cosine-weighted direction over the unit hemisphere.
        ray.m_dir = sample_hemisphere_cosine(child_sampling_context.next_vector2<2>());

        // Transform the direction to world space.
        ray.m_dir = shading_basis.transform_to_parent(ray.m_dir);

        // Don't cast rays on or below the geometric surface.
        if (dot(ray.m_dir, geometric_normal) <= 0.0)
            continue;

        // Count the number of computed samples.
        ++computed_samples;

        // Trace the ambient occlusion ray and count the number of occluded samples.
        double distance;
        if (intersector.trace(ray, true, distance))
        {
            ++occluded_samples;
            min_distance = min(min_distance, distance);
        }
    }

    // Compute occlusion as a scalar between 0.0 and 1.0.
    double occlusion = static_cast<double>(occluded_samples);
    if (computed_samples > 1)
        occlusion /= computed_samples;
    assert(occlusion >= 0.0);
    assert(occlusion <= 1.0);

    return occlusion;
}
        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;
        }