obvhs/
test_util.rs

1//! Meshes, generators, sampling functions, etc.. for basic testing & examples.
2
3pub mod sampling {
4    use std::f32::consts::TAU;
5
6    use glam::*;
7
8    #[inline(always)]
9    pub fn uhash(x: u32) -> u32 {
10        // from https://nullprogram.com/blog/2018/07/31/
11        let mut x = x ^ (x >> 16);
12        x = x.overflowing_mul(0x7feb352d).0;
13        x = x ^ (x >> 15);
14        x = x.overflowing_mul(0x846ca68b).0;
15        x = x ^ (x >> 16);
16        x
17    }
18
19    pub fn hash_f32_vec(v: &[f32]) -> u32 {
20        v.iter()
21            .map(|&f| uhash(f.to_bits()))
22            .fold(0, |acc, h| acc ^ h)
23    }
24
25    pub fn hash_vec3a_vec(v: &[Vec3A]) -> u32 {
26        v.iter()
27            .flat_map(|v| [v.x, v.y, v.z])
28            .map(|f| uhash(f.to_bits()))
29            .fold(0, |acc, h| acc ^ h)
30    }
31
32    #[inline(always)]
33    pub fn uhash2(a: u32, b: u32) -> u32 {
34        uhash((a.overflowing_mul(1597334673).0) ^ (b.overflowing_mul(3812015801).0))
35    }
36
37    #[inline(always)]
38    pub fn unormf(n: u32) -> f32 {
39        n as f32 * (1.0 / 0xffffffffu32 as f32)
40    }
41
42    #[inline(always)]
43    pub fn hash_noise(coord: UVec2, frame: u32) -> f32 {
44        let urnd = uhash2(coord.x, (coord.y << 11) + frame);
45        unormf(urnd)
46    }
47
48    // https://jcgt.org/published/0006/01/01/paper.pdf
49    #[inline(always)]
50    pub fn build_orthonormal_basis(n: Vec3A) -> Mat3 {
51        let sign = n.z.signum();
52        let a = -1.0 / (sign + n.z);
53        let b = n.x * n.y * a;
54
55        mat3(
56            vec3(1.0 + sign * n.x * n.x * a, sign * b, -sign * n.x),
57            vec3(b, sign + n.y * n.y * a, -n.y),
58            n.into(),
59        )
60    }
61
62    #[inline(always)]
63    pub fn cosine_sample_hemisphere(urand: Vec2) -> Vec3A {
64        let r = urand.x.sqrt();
65        let theta = urand.y * TAU;
66        vec3a(
67            r * theta.cos(),
68            r * theta.sin(),
69            0.0f32.max(1.0 - urand.x).sqrt(),
70        )
71    }
72
73    #[inline(always)]
74    pub fn uniform_sample_sphere(urand: Vec2) -> Vec3A {
75        let z = 1.0 - 2.0 * urand.x;
76        let r = (1.0 - z * z).sqrt();
77        let theta = urand.y * TAU;
78        vec3a(r * theta.cos(), r * theta.sin(), z)
79    }
80
81    #[inline(always)]
82    pub fn uniform_sample_cone(urand: Vec2, cos_theta_max: f32) -> Vec3A {
83        let cos_theta = (1.0 - urand.x) + urand.x * cos_theta_max;
84        let sin_theta = (1.0 - cos_theta * cos_theta).clamp(0.0, 1.0).sqrt();
85        let phi: f32 = urand.y * TAU;
86        vec3a(sin_theta * phi.cos(), sin_theta * phi.sin(), cos_theta)
87    }
88
89    #[inline(always)]
90    pub fn smoothstep(e0: f32, e1: f32, x: f32) -> f32 {
91        let t = ((x - e0) / (e1 - e0)).clamp(0.0, 1.0);
92        t * t * (3.0 - 2.0 * t)
93    }
94
95    #[inline(always)]
96    fn cubic(v0: f32, v1: f32, v2: f32, v3: f32, x: f32) -> f32 {
97        let p = (v3 - v2) - (v0 - v1);
98        let q = (v0 - v1) - p;
99        let r = v2 - v0;
100        let s = v1;
101        p * x.powi(3) + q * x.powi(2) + r * x + s
102    }
103
104    #[inline(always)]
105    pub fn bicubic_noise(coord: Vec2, seed: u32) -> f32 {
106        let ix = coord.x.floor() as u32;
107        let iy = coord.y.floor() as u32;
108        let fx = coord.x - ix as f32;
109        let fy = coord.y - iy as f32;
110        fn cubic_col(ix: u32, iy: u32, j: u32, seed: u32, fx: f32) -> f32 {
111            cubic(
112                hash_noise(uvec2(ix, iy + j), seed),
113                hash_noise(uvec2(ix + 1, iy + j), seed),
114                hash_noise(uvec2(ix + 2, iy + j), seed),
115                hash_noise(uvec2(ix + 3, iy + j), seed),
116                fx,
117            )
118        }
119        cubic(
120            cubic_col(ix, iy, 0, seed, fx),
121            cubic_col(ix, iy, 1, seed, fx),
122            cubic_col(ix, iy, 2, seed, fx),
123            cubic_col(ix, iy, 3, seed, fx),
124            fy,
125        )
126    }
127
128    // By Tomasz Stachowiak
129    pub fn somewhat_boring_display_transform(col: Vec3A) -> Vec3A {
130        fn rgb_to_ycbcr(col: Vec3A) -> Vec3A {
131            Mat3A {
132                x_axis: vec3a(0.2126, -0.1146, 0.5),
133                y_axis: vec3a(0.7152, -0.3854, -0.4542),
134                z_axis: vec3a(0.0722, 0.5, -0.0458),
135            } * col
136        }
137
138        fn tonemap_curve(v: f32) -> f32 {
139            1.0 - (-v).exp()
140        }
141
142        fn tonemap_curve3(v: Vec3A) -> Vec3A {
143            1.0 - (-v).exp()
144        }
145
146        fn tonemapping_luminance(col: Vec3A) -> f32 {
147            col.dot(vec3a(0.2126, 0.7152, 0.0722))
148        }
149
150        let mut col = col;
151        let ycbcr = rgb_to_ycbcr(col);
152
153        let bt = tonemap_curve(ycbcr.yz().length() * 2.4);
154        let mut desat = (bt - 0.7) * 0.8;
155        desat *= desat;
156
157        let desat_col = col.lerp(ycbcr.xxx(), desat);
158
159        let tm_luma = tonemap_curve(ycbcr.x);
160        let tm0 = col * tm_luma / tonemapping_luminance(col).max(1e-5);
161        let final_mult = 0.97;
162        let tm1 = tonemap_curve3(desat_col);
163
164        col = tm0.lerp(tm1, bt * bt);
165
166        col * final_mult
167    }
168}
169
170pub mod geometry {
171    use crate::{Triangle, test_util::sampling::bicubic_noise};
172    use glam::*;
173
174    #[inline(always)]
175    const fn vec(a: f32, b: f32, c: f32) -> Vec3A {
176        Vec3A::new(a, b, c)
177    }
178    #[inline(always)]
179    const fn tri(v0: Vec3A, v1: Vec3A, v2: Vec3A) -> Triangle {
180        Triangle { v0, v1, v2 }
181    }
182
183    /// Cube triangle mesh with side length of 2 centered at 0,0,0
184    pub const CUBE: [Triangle; 12] = [
185        tri(vec(-1., 1., -1.), vec(1., 1., 1.), vec(1., 1., -1.)),
186        tri(vec(1., 1., 1.), vec(-1., -1., 1.), vec(1., -1., 1.)),
187        tri(vec(-1., 1., 1.), vec(-1., -1., -1.), vec(-1., -1., 1.)),
188        tri(vec(1., -1., -1.), vec(-1., -1., 1.), vec(-1., -1., -1.)),
189        tri(vec(1., 1., -1.), vec(1., -1., 1.), vec(1., -1., -1.)),
190        tri(vec(-1., 1., -1.), vec(1., -1., -1.), vec(-1., -1., -1.)),
191        tri(vec(-1., 1., -1.), vec(-1., 1., 1.), vec(1., 1., 1.)),
192        tri(vec(1., 1., 1.), vec(-1., 1., 1.), vec(-1., -1., 1.)),
193        tri(vec(-1., 1., 1.), vec(-1., 1., -1.), vec(-1., -1., -1.)),
194        tri(vec(1., -1., -1.), vec(1., -1., 1.), vec(-1., -1., 1.)),
195        tri(vec(1., 1., -1.), vec(1., 1., 1.), vec(1., -1., 1.)),
196        tri(vec(-1., 1., -1.), vec(1., 1., -1.), vec(1., -1., -1.)),
197    ];
198
199    /// Plane triangle mesh with side length of 2 centered at 0,0,0
200    pub const PLANE: [Triangle; 2] = [
201        tri(vec(1., 0., 1.), vec(-1., 0., -1.), vec(-1., 0., 1.)),
202        tri(vec(1., 0., 1.), vec(1., 0., -1.), vec(-1., 0., -1.)),
203    ];
204
205    /// Generate icosphere mesh with radius of 2
206    pub fn icosphere(subdivisions: u32) -> Vec<Triangle> {
207        let phi = (1.0 + 5.0_f32.sqrt()) / 2.0; // golden ratio
208        let (a, b, c, d, e) = (1.0, -1.0, 0.0, phi, -phi);
209
210        #[rustfmt::skip]
211        let mut p = [vec(b,d,c),vec(a,d,c),vec(b,e,c),vec(a,e,c),vec(c,b,d),vec(c,a,d),vec(c,b,e),vec(c,a,e),vec(d,c,b),vec(d,c,a),vec(e,c,b),vec(e,c,a)];
212        p.iter_mut().for_each(|v| *v = v.normalize());
213
214        let mut tris = vec![
215            tri(p[0], p[11], p[5]),
216            tri(p[0], p[5], p[1]),
217            tri(p[0], p[1], p[7]),
218            tri(p[0], p[7], p[10]),
219            tri(p[0], p[10], p[11]),
220            tri(p[1], p[5], p[9]),
221            tri(p[5], p[11], p[4]),
222            tri(p[11], p[10], p[2]),
223            tri(p[10], p[7], p[6]),
224            tri(p[7], p[1], p[8]),
225            tri(p[3], p[9], p[4]),
226            tri(p[3], p[4], p[2]),
227            tri(p[3], p[2], p[6]),
228            tri(p[3], p[6], p[8]),
229            tri(p[3], p[8], p[9]),
230            tri(p[4], p[9], p[5]),
231            tri(p[2], p[4], p[11]),
232            tri(p[6], p[2], p[10]),
233            tri(p[8], p[6], p[7]),
234            tri(p[9], p[8], p[1]),
235        ];
236
237        (0..subdivisions).for_each(|_| {
238            let mut new_tris = Vec::new();
239            tris.iter().for_each(|t| {
240                let mid01 = ((t.v0 + t.v1) * 0.5).normalize();
241                let mid12 = ((t.v1 + t.v2) * 0.5).normalize();
242                let mid20 = ((t.v2 + t.v0) * 0.5).normalize();
243                new_tris.push(tri(t.v0, mid01, mid20));
244                new_tris.push(tri(t.v1, mid12, mid01));
245                new_tris.push(tri(t.v2, mid20, mid12));
246                new_tris.push(tri(mid01, mid12, mid20));
247            });
248            tris = new_tris;
249        });
250
251        tris
252    }
253
254    /// Convert height map to triangles with 2x2x2 size given -1.0..=1.0 output from height_map: F
255    pub fn height_to_triangles<F>(
256        height_map: F,
257        x_resolution: usize,
258        z_resolution: usize,
259    ) -> Vec<Triangle>
260    where
261        F: Fn(usize, usize) -> f32,
262    {
263        let mut triangles = Vec::new();
264
265        // Iterate over each cell in the grid
266        for z in 0..z_resolution {
267            for x in 0..x_resolution {
268                // Calculate normalized positions
269                let fx = (x as f32 / x_resolution as f32) * 2.0 - 1.0;
270                let fz = (z as f32 / z_resolution as f32) * 2.0 - 1.0;
271                let fx2 = ((x + 1) as f32 / x_resolution as f32) * 2.0 - 1.0;
272                let fz2 = ((z + 1) as f32 / z_resolution as f32) * 2.0 - 1.0;
273
274                // Create vertices for each corner of the cell
275                let v00 = vec(fx, height_map(x, z), fz);
276                let v10 = vec(fx2, height_map(x + 1, z), fz);
277                let v01 = vec(fx, height_map(x, z + 1), fz2);
278                let v11 = vec(fx2, height_map(x + 1, z + 1), fz2);
279
280                // Create two triangles for this cell
281                triangles.push(tri(v00, v01, v10));
282                triangles.push(tri(v10, v01, v11));
283            }
284        }
285
286        triangles
287    }
288
289    /// terrain_res 1024 or greater recommended
290    pub fn demoscene(terrain_res: usize, seed: u32) -> Vec<Triangle> {
291        let height_map = |x: usize, y: usize| -> f32 {
292            let coord = vec2(x as f32, y as f32) / terrain_res as f32;
293            let (mut cs, mut ns) = (1.579, 0.579);
294            (1..17)
295                .map(|i| {
296                    (cs, ns) = (cs * 1.579, ns * -0.579);
297                    bicubic_noise(coord * cs, seed + i) * ns
298                })
299                .sum::<f32>()
300                * (1.0 - coord.y).powf(0.579)
301                + (1.0 - coord.y).powf(1.579) * 0.579
302        };
303        height_to_triangles(height_map, terrain_res, terrain_res)
304    }
305}