aboutsummaryrefslogtreecommitdiff
path: root/src/sdf.rs
blob: 3204305b574a54d71114c9a89c51b2e1ec95d80a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
use cgmath::prelude::*;
use cgmath::Vector3;
use std::f32::consts::PI;

type Vector = Vector3<f32>;

pub trait Object: Send + Sync {
    fn sdf(&self, point: Vector, time: f32) -> f32;
}

pub struct Sphere {
    pub center: Vector,
    pub radius: f32,
}

impl Object for Sphere {
    fn sdf(&self, point: Vector, time: f32) -> f32 {
        (point - self.center).magnitude() - self.radius + (time % 5.0) / 50.0
    }
}

pub struct SDBox {
    pub center: Vector,
    pub size: Vector,
}

impl Object for SDBox {
    fn sdf(&self, point: Vector, _time: f32) -> f32 {
        let diff = self.center - point;
        let q = diff.map(|n| n.abs()) - self.size / 2.0;
        q.map(|n| n.max(0.0)).magnitude() + (q.y.max(q.z).max(q.x)).min(0.0)
    }
}

pub struct Gear {
    pub center: Vector,
    pub radius: f32,
    pub thickness: f32,
    pub turn_rate: f32,
}

impl Object for Gear {
    fn sdf(&self, point: Vector, time: f32) -> f32 {
        let point = point - self.center;
        let mut dist: f32;

        let thickness_over_2 = self.thickness / 2.0;
        let thickness_over_4 = self.thickness / 4.0;

        // Ring
        {
            let cylinder_dist =
                (Vector::new(0.0, point.y, point.z)).magnitude() - (self.radius - thickness_over_4);
            dist = cylinder_dist.abs() - thickness_over_2; // Make cylinder hollow
        }
        // Teeth
        {
            let sector_angle: f32 = 2.0 * PI / 12.0;

            // Account for rotation with time
            let angle = sector_angle * time / self.turn_rate;
            let rotated_point = Vector::new(
                point.x,
                point.y * angle.cos() - point.z * angle.sin(),
                point.y * angle.sin() + point.z * angle.cos(),
            );

            // Map all space to the first sector
            let point_angle = (rotated_point.z / rotated_point.y).atan();
            let angle2 = -sector_angle * (point_angle / sector_angle).round();

            let mapped_point = Vector::new(
                rotated_point.x,
                (rotated_point.y * angle2.cos() - rotated_point.z * angle2.sin()).abs(),
                rotated_point.y * angle2.sin() + rotated_point.z * angle2.cos(),
            );

            // Make teeth smooth by subtracting some amount
            dist = dist.min(
                SDBox {
                    center: Vector {
                        x: 0.0,
                        y: self.radius + thickness_over_2,
                        z: 0.0,
                    },
                    size: Vector::new(self.thickness, self.thickness * 2.0, self.thickness),
                }
                .sdf(mapped_point, time)
                    - thickness_over_4,
            );
        }

        // Take a slice
        dist = dist.max(point.x.abs() - thickness_over_2);

        dist
    }
}