double InteractionRays::score(bool visibilityOptional, const ml::TriMeshRayAcceleratorf &occluder, const mat4f &modelToWorld) const
{
    double score = 0.0;
    double maxScore = (double)rays.size();
    for (const auto &r : rays)
    {
        if (visibilityOptional)
        {
            score += r.score;
        }
        else if (r.visible)
        {
            //
            // check to see if the ray is occluded by occluder
            //
            bool visible = true;
            Rayf modelRay = modelToWorld.getInverse() * r.ray;
            auto intersection = occluder.intersect(modelRay);
            if (intersection.valid())
            {
                double centroidDist = ml::dist(r.ray.origin(), r.centroidPos);
                double intersectionDist = ml::dist(r.ray.origin(), modelToWorld.transformAffine(intersection.getSurfacePosition()));
                if (intersectionDist < centroidDist)
                    visible = false;
            }
            if (visible)
                score += r.score;
        }
    }
    if (maxScore == 0.0)
        return 0.0;
    return score / maxScore;
}
void Synthesizer::makeInteractionMapRays(const DisembodiedObject &object, const mat4f &modelToWorld, const Agent &agent, const string &interactionMapName, InteractionRays &result) const
{
    const string &categoryName = object.model->categoryName;

    result.rays.clear();

    if (!database->interactionMaps.hasInteractionMapEntry(object.modelId, interactionMapName))
    {
        return;
    }
    const InteractionMapEntry &entry = database->interactionMaps.getInteractionMapEntry(object.modelId, interactionMapName);

    const auto &centroids = entry.centroids;
    if (centroids.size() == 0 || (centroids.size() > 10 && synthParams().scanName == "dining-large-nowalld"))
    {
        return;
    }

    vector < pair<const ml::TriMeshAcceleratorBVHf*, mat4f> > allAccelerators;
    
    for (UINT objectIndex = 0; objectIndex < scene.objects.size(); objectIndex++)
    {
        //
        // skip the base architecture, since collisions with that are very unlikely to occlude things
        //
        if (object.contactType == ContactWall || objectIndex != 0)
        {
            const auto &object = scene.objects[objectIndex];
            const auto &accelerator = assets->loadModel(*graphics, object.object.modelId).getAcceleratorOriginal();
            allAccelerators.push_back(std::make_pair(&accelerator, object.modelToWorld.getInverse()));
        }
    }
    
	const auto &selfAccelerator = assets->loadModel(*graphics, object.modelId).getAcceleratorOriginal();
    allAccelerators.push_back(std::make_pair(&selfAccelerator, modelToWorld.getInverse()));

    result.rays.resize(centroids.size());

    bool visibilityOptional = (interactionMapName == "backSupport" || interactionMapName == "hips");

    vec3f rayOrigin = agent.headPos;

    if (interactionMapName == "fingertip")
    {
        rayOrigin = agent.handPos();
    }

    if (interactionMapName == "backSupport")
    {
        rayOrigin = agent.spinePos();
    }

    UINT centroidIndex = 0;
    for (const auto &centroid : centroids)
    {
        InteractionRayEntry &rayEntry = result.rays[centroidIndex];
        rayEntry.centroidPos = modelToWorld * centroid.pos;
        rayEntry.ray = ml::Rayf(rayOrigin, rayEntry.centroidPos - rayOrigin);
        rayEntry.score = 1.0;
        rayEntry.visible = false;

        ml::TriMeshRayAcceleratorf::Intersection intersection;
        UINT intersectObjectIndex;
        if (ml::TriMeshRayAcceleratorf::getFirstIntersectionTransform(rayEntry.ray, allAccelerators, intersection, intersectObjectIndex))
        {
            //
            // NOTE: intersection is in model space.
            //

            double centroidDist = ml::dist(rayOrigin, rayEntry.centroidPos);
            double intersectDist = ml::dist(rayOrigin, modelToWorld.transformAffine(intersection.getSurfacePosition()));

            rayEntry.visible = (fabs(centroidDist - intersectDist) < 0.01);

            if (visibilityOptional || rayEntry.visible)
            {
                vec3f surfaceNormal = modelToWorld.transformNormalAffine(intersection.getSurfaceNormal()).getNormalized();
                double minNormalAngle = std::min(vec3f::angleBetween( surfaceNormal, rayEntry.ray.direction()),
                                                 vec3f::angleBetween(-surfaceNormal, rayEntry.ray.direction()));
                double cosineVisiblityScore = std::max(0.0, cos(ml::math::degreesToRadians(minNormalAngle)));

                double score = 1.0;

                if (interactionMapName == "gaze")
                {
                    // for monitors, gaze is better when directly viewing the object head-on
                    //if (categoryName == "Monitor" || categoryName == "Laptop")
                    score = cosineVisiblityScore;
                }

                if (interactionMapName == "fingertip")
                {
                    score -= 0.01f * centroidDist;
                }

                if (interactionMapName == "backSupport")
                {
                    // back support only counts as visible when it is behind the agent
                    if ((rayEntry.ray.direction() | agent.gazeDir) > 0.0f)
                    {
                        score = 0.0;
                    }
                    else
                    {
                        // back support is best when directly behind the agent
                        score = -(rayEntry.ray.direction() | agent.gazeDir);
                    }
                }

                rayEntry.score = score;
            }    
        }
        centroidIndex++;
    }
}