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/fractal.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,
}

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,
        }
    }
}

fn box_sdf(p: ::glam::Vec3, b: ::glam::Vec3) -> f32 {
    let q = p.abs() - b;

    q.max_element()
}

#[derive(Copy, Clone)]
struct Menger {
    pub pos: ::glam::Vec3,
    pub alpha: f32,
    pub beta: f32,
    pub size: f32,
}

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

        let s = [
            ::glam::Vec3::new(1.0, 1.0, 1.0),
            ::glam::Vec3::new(1.0, 1.0, 0.0),
        ];

        for _ in 0..5 {
            let mut t = p.xz();
            t = ::glam::Mat2::from_angle(self.alpha) * t;
            p.x = t.x;
            p.z = t.y;

            let mut t = p.yz();
            t = ::glam::Mat2::from_angle(self.beta) * t;
            p.y = t.x;
            p.z = t.y;

            p = p.abs();

            if p.y > p.x {
                let t = p.y;
                p.y = p.x;
                p.x = t;
            }

            if p.z > p.y {
                let t = p.y;
                p.y = p.z;
                p.z = t;
            }

            p -= size * s[if p.z > 0.5 * size { 0 } else { 1 }];
            size /= 3.0;
        }

        box_sdf(p, ::glam::Vec3::ONE * 1.5 * size)
    }

    fn aabb(&self) -> AABB<3, ::glam::Vec3> {
        let r = self.size * 2.5;

        AABB {
            min: self.pos + ::glam::Vec3::new(-r, -r, -r),
            max: self.pos + ::glam::Vec3::new(r, r, 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),
                UniformDesc::new("menger_pos", UniformType::Float3),
                UniformDesc::new("menger_params", UniformType::Float3),
            ],
            pipeline_params,
            ..Default::default()
        },
    )
    .unwrap();

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

    let mut sponge = Menger {
        pos: ::glam::Vec3::new(3.0, 3.0, 0.0),
        alpha: 1.0,
        beta: 0.5,
        size: 2.0,
    };

    let mut sphere = Sphere {
        center: ::glam::Vec3::new(0.0, -2.0, 2.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;
    params.collision_epsilon = 0.001;

    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.2));
        mat.set_uniform("sphere", sphere.center.extend(sphere.radius));
        mat.set_uniform("menger_pos", sponge.pos);
        mat.set_uniform("menger_params", [sponge.alpha, sponge.beta, sponge.size]);

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

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

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

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

        if is_key_pressed(KeyCode::R) {
            sphere.center = ::glam::Vec3::new(0.0, -2.0, 2.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) {
            // it is only a hack if you are weird about it
            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(&sponge, &mut params) {
            let scaled = res.gradient * 1.0 * get_frame_time();

            sphere.center += scaled;

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

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

        let speed = 0.3;
        let range = 0.8;

        sponge.alpha = (get_time() as f32 * speed).sin() * range;
        sponge.beta = (get_time() as f32 * speed).cos() * range;

        next_frame().await
    }
}