std::vector<PathNode> BidirectionalIntegrator::generateEyePath(Ray r)
{
    std::vector<PathNode> eyePath;
    eyePath.clear();

    Intersection isx = intersection_engine->GetIntersection(r);
    int depth=0;
    while(depth<max_depth && isx.t > 0)
    {
        // store the path node
        PathNode node;
        node.isx = isx;
        node.dirIn_world = -r.direction;
        node.dirIn_local = isx.ToLocalNormalCoordinate(-r.direction);
        node.F = isx.object_hit->material->SampleAndEvaluateScatteredEnergy(isx,node.dirIn_local,node.dirOut_local,node.pdf);
        node.dirOut_world = isx.ToWorldNormalCoordinate(node.dirOut_local);

        if(node.pdf != 0)
            eyePath.push_back(node);
        else
            break;

        //update r
        r = Ray(isx.point + glm::sign(glm::dot(node.dirOut_world,isx.normal)) * isx.normal*1e-3f, node.dirOut_world);

        // update isx and depth info
        depth++;
        isx = intersection_engine->GetIntersection(r);

    }

    return eyePath;
}
// MIS: sampling BRDF
glm::vec3 BidirectionalIntegrator::MIS_SampleBRDF(Intersection &intersection, Ray &r, Geometry* &light)
{
    if(Number_BRDF == 0)
        return glm::vec3(0);

    // Direct light estimator: sample BRDF
    glm::vec3 sum_brdf_sample(0.0f);
    for(int i = 0; i < Number_BRDF; i++)
    {
        glm::vec3 wo_local = intersection.ToLocalNormalCoordinate(-r.direction);
        glm::vec3 wj_local;
        float pdf_brdf;
        glm::vec3 F = intersection.object_hit->material->SampleAndEvaluateScatteredEnergy(intersection,wo_local,wj_local,pdf_brdf);

        glm::vec3 wj_world = intersection.ToWorldNormalCoordinate(wj_local);
        glm::vec3 wo_world = - r.direction;

        Intersection isxOnLight = intersection_engine->GetIntersection(Ray(intersection.point+float(1e-3)*intersection.normal, wj_world));

        if(isxOnLight.t > 0 && isxOnLight.object_hit == light && pdf_brdf > 0)
        {
            float temp,pdf_light = light->RayPDF(intersection, Ray(intersection.point, wj_world));
            float W = PowerHeuristic(pdf_brdf,float(Number_BRDF),pdf_light,float(Number_Light));
            glm::vec3 Ld = light->material->EvaluateScatteredEnergy(isxOnLight,wo_world,-wj_world,temp);

            if(pdf_light > 0 )
            {
                if(isinf(pdf_brdf)) // delta specular surface
                {
                    sum_brdf_sample = sum_brdf_sample +
                            F * Ld * float(fabs(glm::dot(wj_world, intersection.normal))) / pdf_light;
                }
                else
                {
                    sum_brdf_sample = sum_brdf_sample +
                            W * F * Ld * float(fabs(glm::dot(wj_world,intersection.normal))) / pdf_brdf;
                }
            }
        }

    }
    return sum_brdf_sample / float(Number_BRDF);
}
// MIS: sampling light source
glm::vec3 BidirectionalIntegrator::MIS_SampleLight(Intersection &intersection, Ray &r, Geometry* &light)
{
    if(Number_Light == 0)
        return glm::vec3(0);

    // Direct light estimator: sample Light source
    glm::vec3 sum_light_sample(0);
    for(int i = 0; i < Number_Light; i++)
    {
        float u = uniform_distribution(generator);
        float v = uniform_distribution(generator);

        Intersection lightSample = light->SampleOnGeometrySurface(u, v, intersection.point + 1e-3f * intersection.normal);
        //Intersection lightSample = light->RandomSampleOnSurface(u,v);
        glm::vec3 wj = glm::normalize(lightSample.point - intersection.point);
        glm::vec3 wo = - r.direction;
        glm::vec3 P = intersection.point;
        glm::vec3 N = intersection.normal;

        float pdf_light = light->RayPDF(intersection, Ray(P + float(1e-3)*N, wj));

        Intersection lightIntersection = intersection_engine->GetIntersection(Ray(P + float(1e-3)*N, wj));
        float temp, pdf_brdf;

        glm::vec3 wo_local = intersection.ToLocalNormalCoordinate(wo);
        glm::vec3 wj_local = intersection.ToLocalNormalCoordinate(wj);

        glm::vec3 F = intersection.object_hit->material->EvaluateScatteredEnergy(intersection, wo_local, wj_local, pdf_brdf);
        // reach light directly && pdf(wj) > 0
        if(lightIntersection.t > 0 && lightIntersection.object_hit == light && pdf_light > 0 && pdf_brdf > 0)
        {

            glm::vec3 Ld = light->material->EvaluateScatteredEnergy(lightSample, wo, -wj, temp);
            float W = PowerHeuristic(pdf_light, float(Number_Light), pdf_brdf, float(Number_BRDF)); // cause shadow in center

            sum_light_sample = sum_light_sample +
                                  W * F * Ld * float(fabs(glm::dot(wj, N))) / pdf_light;

        }
    }
    return sum_light_sample / float(Number_Light);
}
std::vector<PathNode> BidirectionalIntegrator::generateLightPath(Geometry* &light)
{
    std::vector<PathNode> lightPath;
    lightPath.clear();

    Intersection lightSample = light->RandomSampleOnSurface(uniform_distribution(generator),uniform_distribution(generator));

    Ray r(lightSample.point, lightSample.point - light->transform.position());

    Intersection isx = intersection_engine->GetIntersection(r);
    int depth = 0;
    while(isx.t > 0 && depth < max_depth)
    {
        // store pathnode on the light path
        PathNode node;
        node.isx = isx;
        node.dirIn_world = -r.direction;
        node.dirIn_local = isx.ToLocalNormalCoordinate(-r.direction);
        node.F = isx.object_hit->material->SampleAndEvaluateScatteredEnergy(isx,node.dirIn_local,node.dirOut_local,node.pdf);
        node.dirOut_world = isx.ToWorldNormalCoordinate(node.dirOut_local);

        if(node.pdf != 0)
            lightPath.push_back(node);
        else
            break;

        //update r
        r = Ray(isx.point + glm::sign(glm::dot(node.dirOut_world,isx.normal)) * isx.normal*1e-3f, node.dirOut_world);

        // update isx and depth info
        depth++;
        isx = intersection_engine->GetIntersection(r);
    }

    return lightPath;
}