Find best value in array

I want to iterate over array and find the best element with some corresponding values and then perform some calculation, now I perform the calculation on every element and return the best calculation which is unnecessary. The additional values here is closest_intersection_info. The compare method of IntersectionInfo :

    pub fn compare(&mut self, other : Self) -> bool {
        if (self.distance > other.distance) {
            *self = other;
            return true;
        }

        false
    }

I just want to get closest_intersection_info and the corresponding element of the array without using unsafe or clone, in c++ I would use pointers, what experienced rustinace would use in this case

    fn reytrace(&mut self, ray : Ray, scene : &Scene) -> Color {
        let mut closest_intersection_info = IntersectionInfo::new();
        let mut result_color = Color::new(0.0,0.0,0.0);
        for element  in &scene.elements {
            if let Some(info) = element.geometry().intersect(&ray) {
                if closest_intersection_info.compare(info) {
                    result_color = element.material().color(&ray, &closest_intersection_info, &scene.lights);
                }
            }
        }

        return result_color;
    }

Here's a straightforward way to store a reference to the element, and then perform a computation on it after the loop:

fn reytrace(&mut self, ray : Ray, scene : &Scene) -> Color {
    let mut closest_intersection_info = IntersectionInfo::new();
    let mut closest_element = None;
    
    for element in &scene.elements {
        if let Some(info) = element.geometry().intersect(&ray) {
            if closest_intersection_info.compare(info) {
                closest_element = Some(element);
            }
        }
    }
    match closest_element {
        Some(element) => element.material().color(&ray, &closest_intersection_info, &scene.lights),
        None => Color::new(0.0,0.0,0.0),
    }
}

You could also use a more "rustic" approach with some iterator methods like filter_map and min_by_key to replace the compare method:

fn reytrace(&mut self, ray : Ray, scene : &Scene) -> Color {
    let intersections = scene.elements.iter().filter_map(|element| {
        let info = element.geometry().intersect(&ray)?;
        Some((element, info))
    });
    
    let closest_intersection = intersections.min_by_key(|(_, info)| info.distance);

    match closest_intersection {
        Some((element, info)) => element.material().color(&ray, &info, &scene.lights),
        None => Color::new(0.0,0.0,0.0),
    }
}
1 Like

Here's one possibility (as far as I can tell from the size of the snippet). It uses ordered-float, which may or may not be appropriate for your use case (you should consider what you want to happen should you hit a NaN value and judge for yourself).

// This could be a method on Scene or such
pub fn closest_intersection<'a>(
    ray: &Ray,
    scene: &'a Scene,
) -> Option<(&'a Element, IntersectionInfo)> {
    scene
        .elements
        .iter()
        .flat_map(|e| e.geometry().intersect(ray).map(|info| (e, info)))
        .max_by_key(|tuple| OrderedFloat(tuple.1.distance))
}

impl Universe {
    // Unclear why `&mut self` or even `&self` is needed here
    // Maybe you could use `.map(|tuple| ...).unwrap_or_default()` or such here
    pub fn reytrace(&mut self, ray: Ray, scene: &Scene) -> Color {
        match closest_intersection(&ray, scene) {
            None => Color::new(0.0, 0.0, 0.0),
            Some((element, info)) => element.material().color(&ray, &info, &scene.lights),
        }
    }
}

Playground.

2 Likes