thimni 0.3.2

efficient SDF collision without discretizatio, neural nets, or interval arithmetic
Documentation
use std::f32::INFINITY;

use ::glam::Vec3Swizzles;
use macroquad::prelude::*;
use macroquad::window::miniquad::*;
use thimni::raycast::RayCast;
use thimni::sdf::SDF;
use thimni::utils::{AABB, CollisionParameters};

const VERTEX: &str = r#"#version 100
attribute vec3 position;
attribute vec2 texcoord;

varying mediump vec2 uv;

uniform mat4 Model;
uniform mat4 Projection;

void main() {
    gl_Position = Projection * Model * vec4(position, 1);
    uv = texcoord;
}"#;

const FRAGMENT: &str = include_str!("resources/raymarch.glsl");

const GRAVITY: ::glam::Vec3 = ::glam::Vec3 {
    x: 0.0,
    y: 0.1,
    z: 0.0,
};

#[derive(Copy, Clone)]
struct YPlane {
    h: f32,
    thickness: f32,
}

impl SDF<3, ::glam::Vec3> for YPlane {
    fn dist(&self, p: ::glam::Vec3) -> f32 {
        (-p.y + self.h).abs() - self.thickness
    }

    fn gradient(
        &self,
        _p: ::glam::Vec3,
        _params: &thimni::utils::CollisionParameters,
    ) -> ::glam::Vec3 {
        -::glam::Vec3::Y
    }

    fn aabb(&self) -> thimni::utils::AABB<3, ::glam::Vec3> {
        AABB {
            min: ::glam::Vec3::new(-INFINITY, self.h - self.thickness, -INFINITY),
            max: ::glam::Vec3::new(INFINITY, self.h + self.thickness, INFINITY),
        }
    }
}

#[derive(Copy, Clone)]
struct Sphere {
    center: ::glam::Vec3,
    radius: f32,
    vel: ::glam::Vec3,
    floor_cast: RayCast<3, ::glam::Vec3>,
}

impl SDF<3, ::glam::Vec3> for Sphere {
    fn dist(&self, p: ::glam::Vec3) -> f32 {
        let k = p - self.center;

        k.length() - self.radius
    }

    fn gradient(
        &self,
        p: ::glam::Vec3,
        _params: &thimni::utils::CollisionParameters,
    ) -> ::glam::Vec3 {
        let k = p - self.center;

        k.normalize_or_zero()
    }

    fn aabb(&self) -> AABB<3, ::glam::Vec3> {
        AABB {
            min: self.center - self.radius,
            max: self.center + self.radius,
        }
    }
}

#[derive(Copy, Clone)]
struct Cylinder {
    pub pos: ::glam::Vec3,
    pub h: f32,
    pub r: f32,
}

impl SDF<3, ::glam::Vec3> for Cylinder {
    fn dist(&self, p: ::glam::Vec3) -> f32 {
        let k = p - self.pos;

        let d = ::glam::Vec2::new(k.xz().length(), k.y).abs() - ::glam::Vec2::new(self.r, self.h);
        d.max_element().min(0.0) + d.max(::glam::Vec2::ZERO).length()
    }

    fn aabb(&self) -> AABB<3, ::glam::Vec3> {
        AABB {
            min: self.pos + ::glam::Vec3::new(-self.r, -self.h, -self.r),
            max: self.pos + ::glam::Vec3::new(self.r, self.h, self.r),
        }
    }
}

#[macroquad::main("threed")]
async fn main() {
    let pipeline_params = PipelineParams {
        color_blend: Some(BlendState::new(
            Equation::Add,
            BlendFactor::Value(BlendValue::SourceAlpha),
            BlendFactor::OneMinusValue(BlendValue::SourceAlpha),
        )),
        ..Default::default()
    };

    let mat = load_material(
        ShaderSource::Glsl {
            vertex: VERTEX,
            fragment: FRAGMENT,
        },
        MaterialParams {
            uniforms: vec![
                UniformDesc::new("res", UniformType::Float2),
                UniformDesc::new("mouse", UniformType::Float2),
                UniformDesc::new("sphere", UniformType::Float4),
            ],
            pipeline_params,
            ..Default::default()
        },
    )
    .unwrap();

    let plane = YPlane {
        h: 5.0,
        thickness: 0.5,
    };

    let cylinder = Cylinder {
        pos: ::glam::Vec3::new(3.0, 3.0, 0.0),
        h: 1.0,
        r: 1.0,
    };

    let mut sphere = Sphere {
        center: ::glam::Vec3::new(0.0, -1.0, 3.0),
        radius: 1.0,
        vel: ::glam::Vec3::new(0.0, 0.0, 0.0),
        floor_cast: RayCast::new(::glam::Vec3::new(0.0, -1.0, 3.0), ::glam::Vec3::Y, 1.2),
    };

    let mut params = CollisionParameters::default();
    params.normal_epsilon = 0.01;

    loop {
        clear_background(BLACK);

        gl_use_material(&mat);

        mat.set_uniform("res", Vec2::new(screen_width(), screen_height()));
        mat.set_uniform("mouse", Vec2::new(0.0, 0.0));
        mat.set_uniform("sphere", sphere.center.extend(sphere.radius));

        draw_rectangle(0.0, 0.0, screen_width(), screen_height(), WHITE);

        sphere.vel.x *= 0.0;
        sphere.vel.z *= 0.0;

        sphere.floor_cast.origin = sphere.center;
        sphere.floor_cast.is_colliding = false;
        sphere.floor_cast.query(&plane, &params);
        sphere.floor_cast.query(&cylinder, &params);

        if is_key_pressed(KeyCode::Space) && sphere.floor_cast.is_colliding {
            sphere.vel += ::glam::Vec3::Y * -6.0;
        }

        sphere.vel += GRAVITY;

        let mut dir = ::glam::Vec3::ZERO;

        if is_key_down(KeyCode::W) {
            dir += ::glam::Vec3::Z * 1.0;
        }

        if is_key_down(KeyCode::S) {
            dir += ::glam::Vec3::Z * -1.0;
        }

        if is_key_down(KeyCode::D) {
            dir += ::glam::Vec3::X * 1.0;
        }

        if is_key_down(KeyCode::A) {
            dir += ::glam::Vec3::X * -1.0;
        }

        sphere.vel += dir.normalize_or_zero() * 9.0;

        if let Some(res) = sphere.get_coll_point(&plane, &mut params) {
            let scaled = res.gradient * get_frame_time();

            sphere.center += scaled;

            let d = sphere.dist(res.point);
            sphere.center -= scaled * d;
            sphere.vel = scaled;
        }

        if let Some(res) = sphere.get_coll_point(&cylinder, &mut params) {
            let scaled = res.gradient * get_frame_time();

            sphere.center += scaled;

            let d = sphere.dist(res.point);
            sphere.center -= scaled * d;
            sphere.vel = scaled;
        }

        sphere.center += sphere.vel * get_frame_time();

        next_frame().await
    }
}