rocketsim 0.2.4

Simulate Rocket League games at maximum efficiency
Documentation
use glam::{U16Vec3, Vec3A};

use super::{triangle_mesh::TriangleMesh, triangle_shape::TriangleShape};
use crate::shared::{
    Aabb,
    bvh::{BvhNodeType, Node, Tree},
};

struct BvhQuantization {
    min: Vec3A,
    max: Vec3A,
    scale: Vec3A,
}

impl BvhQuantization {
    fn new(aabb: Aabb) -> Self {
        const MARGIN: f32 = 1.0;
        const QUANTIZED_RANGE: f32 = 65533.0;

        let margin = Vec3A::splat(MARGIN);
        let mut result = Self {
            min: aabb.min - margin,
            max: aabb.max + margin,
            scale: Vec3A::ZERO,
        };
        result.update_scale(QUANTIZED_RANGE);

        let quantized_min = result.quantize_point(result.min, false);
        let unquantized_min = result.unquantize_point(quantized_min);
        result.min = result.min.min(unquantized_min - margin);

        let quantized_max = result.quantize_point(result.max, true);
        let unquantized_max = result.unquantize_point(quantized_max);
        result.max = result.max.max(unquantized_max + margin);
        result.update_scale(QUANTIZED_RANGE);

        result
    }

    fn update_scale(&mut self, quantized_range: f32) {
        self.scale = Vec3A::splat(quantized_range) / (self.max - self.min);
    }

    fn quantize_point(&self, point: Vec3A, is_max: bool) -> U16Vec3 {
        let value = (point - self.min) * self.scale;
        let mut result = U16Vec3::ZERO;
        for axis in 0..3 {
            result[axis] = if is_max {
                ((value[axis] + 1.0) as i32 as u16) | 1
            } else {
                (value[axis] as i32 as u16) & 0xfffe
            };
        }
        result
    }

    fn unquantize_point(&self, point: U16Vec3) -> Vec3A {
        point.as_vec3a() / self.scale + self.min
    }

    fn quantize_aabb(&self, aabb: Aabb) -> Aabb {
        Aabb {
            min: self.unquantize_point(self.quantize_point(aabb.min, false)),
            max: self.unquantize_point(self.quantize_point(aabb.max, true)),
        }
    }
}

fn update_triangle_aabb(triangle: &TriangleShape) -> Aabb {
    const MIN_AABB_DIMENSION: f32 = 0.002;
    const MIN_AABB_HALF_DIMENSION: f32 = MIN_AABB_DIMENSION / 2.0;

    let mut aabb = triangle.aabb();
    let diff = (aabb.max - aabb.min).cmplt(Vec3A::splat(MIN_AABB_DIMENSION));
    if diff.any() {
        let [x, y, z] = diff.into();

        if x {
            aabb.max.x += MIN_AABB_HALF_DIMENSION;
            aabb.min.x -= MIN_AABB_HALF_DIMENSION;
        }

        if y {
            aabb.max.y += MIN_AABB_HALF_DIMENSION;
            aabb.min.y -= MIN_AABB_HALF_DIMENSION;
        }

        if z {
            aabb.max.z += MIN_AABB_HALF_DIMENSION;
            aabb.min.z -= MIN_AABB_HALF_DIMENSION;
        }
    }

    aabb
}

pub fn create_bvh(triangles: &TriangleMesh, aabb: Aabb) -> Tree {
    let quantization = BvhQuantization::new(aabb);
    let mut leaf_nodes: Vec<_> = triangles
        .get_tris()
        .iter()
        .map(update_triangle_aabb)
        .map(|triangle_aabb| quantization.quantize_aabb(triangle_aabb))
        .enumerate()
        .map(|(triangle_idx, aabb)| Node {
            aabb,
            node_type: BvhNodeType::Leaf {
                leaf_idx: triangle_idx,
            },
        })
        .collect();

    let tree_aabb = leaf_nodes
        .iter()
        .skip(1)
        .fold(leaf_nodes[0].aabb, |bounds, leaf| {
            bounds.combine(&leaf.aabb)
        });
    Tree::build_bullet(tree_aabb, &mut leaf_nodes)
}