1use core::ops::Mul;
9use spirv_std::{
10 image::{sample_with, Cubemap, Image2d, Image2dArray, ImageWithMethods},
11 Image, Sampler,
12};
13
14use glam::*;
15pub use spirv_std::num_traits::{clamp, Float, Zero};
16
17pub trait Fetch<Coords> {
18 type Output;
19
20 fn fetch(&self, coords: Coords) -> Self::Output;
21}
22
23impl Fetch<UVec2> for Image!(2D, type=f32, sampled, depth) {
24 type Output = Vec4;
25
26 fn fetch(&self, coords: UVec2) -> Self::Output {
27 self.fetch_with(coords, sample_with::lod(0))
28 }
29}
30
31impl Fetch<UVec2> for Image!(2D, type=f32, sampled, depth, multisampled=true) {
32 type Output = Vec4;
33
34 fn fetch(&self, coords: UVec2) -> Self::Output {
35 self.fetch_with(coords, sample_with::sample_index(0))
39 }
40}
41
42pub trait IsSampler: Copy + Clone {}
43
44impl IsSampler for () {}
45
46impl IsSampler for Sampler {}
47
48pub trait Sample2d {
49 type Sampler: IsSampler;
50
51 fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec2, lod: f32) -> glam::Vec4;
52}
53
54impl Sample2d for Image2d {
55 type Sampler = Sampler;
56
57 fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec2, lod: f32) -> glam::Vec4 {
58 self.sample_by_lod(sampler, uv, lod)
59 }
60}
61
62impl Sample2d for Image!(2D, type=f32, sampled, depth) {
63 type Sampler = Sampler;
64
65 fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec2, lod: f32) -> glam::Vec4 {
66 self.sample_by_lod(sampler, uv, lod)
67 }
68}
69
70pub trait Sample2dArray {
71 type Sampler: IsSampler;
72
73 fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec3, lod: f32) -> glam::Vec4;
74}
75
76impl Sample2dArray for Image2dArray {
77 type Sampler = Sampler;
78
79 fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec3, lod: f32) -> glam::Vec4 {
80 self.sample_by_lod(sampler, uv, lod)
81 }
82}
83
84impl Sample2dArray for Image!(2D, type=f32, sampled, arrayed, depth) {
85 type Sampler = Sampler;
86
87 fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec3, lod: f32) -> glam::Vec4 {
88 self.sample_by_lod(sampler, uv, lod)
89 }
90}
91
92pub trait SampleCube {
93 type Sampler: IsSampler;
94
95 fn sample_by_lod(&self, sampler: Self::Sampler, uv: Vec3, lod: f32) -> glam::Vec4;
96}
97
98impl SampleCube for Cubemap {
99 type Sampler = Sampler;
100
101 fn sample_by_lod(&self, sampler: Self::Sampler, uv: Vec3, lod: f32) -> glam::Vec4 {
102 self.sample_by_lod(sampler, uv, lod)
103 }
104}
105
106#[cfg(not(target_arch = "spirv"))]
107mod cpu {
108
109 use super::*;
110
111 pub struct ConstTexture(Vec4);
114
115 impl Fetch<UVec2> for ConstTexture {
116 type Output = Vec4;
117
118 fn fetch(&self, _coords: UVec2) -> Self::Output {
119 self.0
120 }
121 }
122
123 impl Sample2d for ConstTexture {
124 type Sampler = ();
125
126 fn sample_by_lod(&self, _sampler: Self::Sampler, _uv: glam::Vec2, _lod: f32) -> Vec4 {
127 self.0
128 }
129 }
130
131 impl Sample2dArray for ConstTexture {
132 type Sampler = ();
133
134 fn sample_by_lod(&self, _sampler: Self::Sampler, _uv: glam::Vec3, _lod: f32) -> glam::Vec4 {
135 self.0
136 }
137 }
138
139 impl SampleCube for ConstTexture {
140 type Sampler = ();
141
142 fn sample_by_lod(&self, _sampler: Self::Sampler, _uv: Vec3, _lod: f32) -> glam::Vec4 {
143 self.0
144 }
145 }
146
147 impl ConstTexture {
148 pub fn new(value: Vec4) -> Self {
149 Self(value)
150 }
151 }
152
153 #[derive(Debug)]
154 pub struct CpuTexture2d<P: image::Pixel, Container> {
155 pub image: image::ImageBuffer<P, Container>,
156 convert_fn: fn(&P) -> Vec4,
157 }
158
159 impl<P: image::Pixel, Container> CpuTexture2d<P, Container> {
160 pub fn from_image(
161 image: image::ImageBuffer<P, Container>,
162 convert_fn: fn(&P) -> Vec4,
163 ) -> Self {
164 Self { image, convert_fn }
165 }
166 }
167
168 impl<P, Container> Fetch<UVec2> for CpuTexture2d<P, Container>
169 where
170 P: image::Pixel,
171 Container: std::ops::Deref<Target = [P::Subpixel]>,
172 {
173 type Output = Vec4;
174
175 fn fetch(&self, coords: UVec2) -> Self::Output {
176 let x = coords.x.clamp(0, self.image.width() - 1);
177 let y = coords.y.clamp(0, self.image.height() - 1);
178 let p = self.image.get_pixel(x, y);
179 (self.convert_fn)(p)
180 }
181 }
182
183 impl<P, Container> Sample2d for CpuTexture2d<P, Container>
184 where
185 P: image::Pixel,
186 Container: std::ops::Deref<Target = [P::Subpixel]>,
187 {
188 type Sampler = ();
189
190 fn sample_by_lod(&self, _sampler: Self::Sampler, uv: glam::Vec2, _lod: f32) -> Vec4 {
191 let px = uv.x.clamp(0.0, 1.0) * (self.image.width() as f32 - 1.0);
194 let py = uv.y.clamp(0.0, 1.0) * (self.image.height() as f32 - 1.0);
195 self.fetch(UVec2::new(px.round() as u32, py.round() as u32))
196 }
197 }
198
199 pub struct CpuTexture2dArray<P: image::Pixel, Container> {
200 pub images: Vec<image::ImageBuffer<P, Container>>,
201 convert_fn: fn(&P) -> Vec4,
202 }
203
204 impl<P: image::Pixel, Container> CpuTexture2dArray<P, Container> {
205 pub fn from_images(
206 images: impl IntoIterator<Item = image::ImageBuffer<P, Container>>,
207 convert_fn: fn(&P) -> Vec4,
208 ) -> Self {
209 let images = images.into_iter().collect();
210 Self { images, convert_fn }
211 }
212 }
213
214 impl<P, Container> Sample2dArray for CpuTexture2dArray<P, Container>
215 where
216 P: image::Pixel,
217 Container: std::ops::Deref<Target = [P::Subpixel]>,
218 {
219 type Sampler = ();
220
221 fn sample_by_lod(&self, _sampler: Self::Sampler, uv: glam::Vec3, _lod: f32) -> Vec4 {
223 let img = &self.images[uv.z as usize];
226 let px = (uv.x.clamp(0.0, 1.0) * (img.width() as f32 - 1.0)).round() as u32;
227 let py = (uv.y.clamp(0.0, 1.0) * (img.height() as f32 - 1.0)).round() as u32;
228 println!("sampling: ({px}, {py})");
229 let p = img.get_pixel(px, py);
230 (self.convert_fn)(p)
231 }
232 }
233
234 #[derive(Default)]
238 pub struct CpuCubemap {
239 pub images: [image::DynamicImage; 6],
240 }
241
242 impl SampleCube for CpuCubemap {
243 type Sampler = ();
244
245 fn sample_by_lod(
246 &self,
247 _sampler: Self::Sampler,
248 direction: glam::Vec3,
249 _lod: f32,
250 ) -> glam::Vec4 {
251 crate::cubemap::cpu_sample_cubemap(&self.images, direction)
252 }
253 }
254
255 pub fn scaled_u8_to_f32(u: u8) -> f32 {
257 u as f32 / 255.0
258 }
259
260 pub fn luma_u8_to_vec4(p: &image::Luma<u8>) -> Vec4 {
261 let shade = scaled_u8_to_f32(p.0[0]);
262 Vec3::splat(shade).extend(1.0)
263 }
264
265 pub fn scaled_f32_to_u8(f: f32) -> u8 {
267 (f * 255.0) as u8
268 }
269
270 pub fn scaled_u32_to_u8(u: u32) -> u8 {
272 ((u as f32 / u32::MAX as f32) * 255.0) as u8
273 }
274}
275#[cfg(not(target_arch = "spirv"))]
276pub use cpu::*;
277
278pub trait IsVector {
287 type OrthogonalVectors;
289
290 fn alt_norm_or_zero(&self) -> Self;
292
293 fn signum_or_zero(&self) -> Self;
295
296 fn dot2(&self) -> f32;
299
300 fn orthonormal_vectors(&self) -> Self::OrthogonalVectors;
302}
303
304impl IsVector for glam::Vec2 {
305 type OrthogonalVectors = Vec2;
306
307 fn alt_norm_or_zero(&self) -> Self {
308 if self.length().is_zero() {
309 glam::Vec2::ZERO
310 } else {
311 self.normalize()
312 }
313 }
314
315 fn signum_or_zero(&self) -> Self {
316 Vec2::new(signum_or_zero(self.x), signum_or_zero(self.y))
317 }
318
319 fn dot2(&self) -> f32 {
320 self.dot(*self)
321 }
322
323 fn orthonormal_vectors(&self) -> Self::OrthogonalVectors {
324 Vec3::new(self.x, self.y, 0.0).cross(Vec3::Z).xy()
325 }
326}
327
328impl IsVector for glam::Vec3 {
329 type OrthogonalVectors = [Vec3; 2];
330
331 fn alt_norm_or_zero(&self) -> Self {
332 if self.length().is_zero() {
333 glam::Vec3::ZERO
334 } else {
335 self.normalize()
336 }
337 }
338
339 fn signum_or_zero(&self) -> Self {
340 Vec3::new(
341 signum_or_zero(self.x),
342 signum_or_zero(self.y),
343 signum_or_zero(self.z),
344 )
345 }
346
347 fn dot2(&self) -> f32 {
348 self.dot(*self)
349 }
350
351 fn orthonormal_vectors(&self) -> Self::OrthogonalVectors {
352 let s = self.alt_norm_or_zero();
354 let sign = signum_or_zero(s.z);
355 let a = -1.0 / (sign + s.z);
356 let b = s.x * s.y * a;
357 [
358 Self::new(1.0 + sign * s.x * s.x * a, sign * b, -sign * s.x),
359 Self::new(b, sign + s.y * s.y * a, -s.y),
360 ]
361 }
362}
363
364pub fn distance_to_line(p: Vec3, a: Vec3, b: Vec3) -> f32 {
368 let ab_distance = a.distance(b);
369 if ab_distance <= f32::EPSILON {
370 p.distance(a)
371 } else {
372 let tri_area = (p - a).cross(p - b).length();
373 tri_area / ab_distance
374 }
375}
376
377pub trait IsMatrix {
386 fn to_scale_rotation_translation_or_id(&self) -> (glam::Vec3, glam::Quat, glam::Vec3);
402}
403
404#[inline]
408fn from_rotation_axes(x_axis: glam::Vec3, y_axis: glam::Vec3, z_axis: glam::Vec3) -> glam::Quat {
409 let (m00, m01, m02) = x_axis.into();
411 let (m10, m11, m12) = y_axis.into();
412 let (m20, m21, m22) = z_axis.into();
413 if m22 <= 0.0 {
414 let dif10 = m11 - m00;
416 let omm22 = 1.0 - m22;
417 if dif10 <= 0.0 {
418 let four_xsq = omm22 - dif10;
420 let inv4x = 0.5 / four_xsq.sqrt();
421 glam::Quat::from_xyzw(
422 four_xsq * inv4x,
423 (m01 + m10) * inv4x,
424 (m02 + m20) * inv4x,
425 (m12 - m21) * inv4x,
426 )
427 } else {
428 let four_ysq = omm22 + dif10;
430 let inv4y = 0.5 / four_ysq.sqrt();
431 glam::Quat::from_xyzw(
432 (m01 + m10) * inv4y,
433 four_ysq * inv4y,
434 (m12 + m21) * inv4y,
435 (m20 - m02) * inv4y,
436 )
437 }
438 } else {
439 let sum10 = m11 + m00;
441 let opm22 = 1.0 + m22;
442 if sum10 <= 0.0 {
443 let four_zsq = opm22 - sum10;
445 let inv4z = 0.5 / four_zsq.sqrt();
446 glam::Quat::from_xyzw(
447 (m02 + m20) * inv4z,
448 (m12 + m21) * inv4z,
449 four_zsq * inv4z,
450 (m01 - m10) * inv4z,
451 )
452 } else {
453 let four_wsq = opm22 + sum10;
455 let inv4w = 0.5 / four_wsq.sqrt();
456 glam::Quat::from_xyzw(
457 (m12 - m21) * inv4w,
458 (m20 - m02) * inv4w,
459 (m01 - m10) * inv4w,
460 four_wsq * inv4w,
461 )
462 }
463 }
464}
465
466const fn srt_id() -> (Vec3, Quat, Vec3) {
467 (Vec3::ONE, Quat::IDENTITY, Vec3::ZERO)
468}
469
470impl IsMatrix for glam::Mat4 {
471 #[inline]
472 fn to_scale_rotation_translation_or_id(&self) -> (glam::Vec3, glam::Quat, glam::Vec3) {
473 let det = self.determinant();
474 if det == 0.0 {
475 crate::println!("det == 0.0, returning identity");
476 return srt_id();
477 }
478
479 let det_sign = if det >= 0.0 { 1.0 } else { -1.0 };
480
481 let scale = glam::Vec3::new(
482 self.x_axis.length() * det_sign,
483 self.y_axis.length(),
484 self.z_axis.length(),
485 );
486
487 if !scale.cmpne(glam::Vec3::ZERO).all() {
488 return srt_id();
489 }
490
491 let inv_scale = scale.recip();
492
493 let rotation = from_rotation_axes(
494 self.x_axis.mul(inv_scale.x).xyz(),
495 self.y_axis.mul(inv_scale.y).xyz(),
496 self.z_axis.mul(inv_scale.z).xyz(),
497 );
498
499 let translation = self.w_axis.xyz();
500
501 (scale, rotation, translation)
502 }
503}
504
505pub fn signum_or_zero(n: f32) -> f32 {
510 ((n >= 0.0) as u32) as f32 - ((n < 0.0) as u32) as f32
511}
512
513#[inline(always)]
516pub fn step_ge(value: f32, edge: f32) -> f32 {
517 ((value >= edge) as u32) as f32
518}
519
520#[inline(always)]
523pub fn step_le(value: f32, edge: f32) -> f32 {
524 ((value <= edge) as u32) as f32
525}
526
527pub fn smoothstep(edge_in: f32, edge_out: f32, mut x: f32) -> f32 {
528 x = clamp((x - edge_in) / (edge_out - edge_in), 0.0, 1.0);
530 x * x * (3.0 - 2.0 * x)
531}
532
533pub fn triangle_face_normal(p1: Vec3, p2: Vec3, p3: Vec3) -> Vec3 {
534 let a = p1 - p2;
535 let b = p1 - p3;
536 let n: Vec3 = a.cross(b).alt_norm_or_zero();
537 #[cfg(cpu)]
538 debug_assert_ne!(
539 Vec3::ZERO,
540 n,
541 "normal is zero - p1: {p1}, p2: {p2}, p3: {p3}"
542 );
543 n
544}
545
546pub fn hex_to_vec4(color: u32) -> Vec4 {
548 let r = ((color >> 24) & 0xFF) as f32 / 255.0;
549 let g = ((color >> 16) & 0xFF) as f32 / 255.0;
550 let b = ((color >> 8) & 0xFF) as f32 / 255.0;
551 let a = (color & 0xFF) as f32 / 255.0;
552
553 Vec4::new(r, g, b, a)
554}
555
556pub const UNIT_QUAD_CCW: [Vec3; 6] = {
557 let tl = Vec3::new(-0.5, 0.5, 0.0);
558 let tr = Vec3::new(0.5, 0.5, 0.0);
559 let bl = Vec3::new(-0.5, -0.5, 0.0);
560 let br = Vec3::new(0.5, -0.5, 0.0);
561 [bl, br, tr, tr, tl, bl]
562};
563
564pub const CLIP_QUAD_CCW: [Vec3; 6] = {
565 let tl = Vec3::new(-1.0, 1.0, 0.0);
566 let tr = Vec3::new(1.0, 1.0, 0.0);
567 let bl = Vec3::new(-1.0, -1.0, 0.0);
568 let br = Vec3::new(1.0, -1.0, 0.0);
569 [bl, br, tr, tr, tl, bl]
570};
571
572pub const CLIP_SPACE_COORD_QUAD_CCW_TL: Vec4 = Vec4::new(-1.0, 1.0, 0.5, 1.0);
573pub const CLIP_SPACE_COORD_QUAD_CCW_BL: Vec4 = Vec4::new(-1.0, -1.0, 0.5, 1.0);
574pub const CLIP_SPACE_COORD_QUAD_CCW_TR: Vec4 = Vec4::new(1.0, 1.0, 0.5, 1.0);
575pub const CLIP_SPACE_COORD_QUAD_CCW_BR: Vec4 = Vec4::new(1.0, -1.0, 0.5, 1.0);
576
577pub const CLIP_SPACE_COORD_QUAD_CCW: [Vec4; 6] = {
578 [
579 CLIP_SPACE_COORD_QUAD_CCW_BL,
580 CLIP_SPACE_COORD_QUAD_CCW_BR,
581 CLIP_SPACE_COORD_QUAD_CCW_TR,
582 CLIP_SPACE_COORD_QUAD_CCW_TR,
583 CLIP_SPACE_COORD_QUAD_CCW_TL,
584 CLIP_SPACE_COORD_QUAD_CCW_BL,
585 ]
586};
587
588pub const UV_COORD_QUAD_CCW: [Vec2; 6] = {
589 let tl = Vec2::new(0.0, 0.0);
590 let tr = Vec2::new(1.0, 0.0);
591 let bl = Vec2::new(0.0, 1.0);
592 let br = Vec2::new(1.0, 1.0);
593 [bl, br, tr, tr, tl, bl]
594};
595
596pub const POINTS_2D_TEX_QUAD: [Vec2; 6] = {
597 let tl = Vec2::new(0.0, 0.0);
598 let tr = Vec2::new(1.0, 0.0);
599 let bl = Vec2::new(0.0, 1.0);
600 let br = Vec2::new(1.0, 1.0);
601 [tl, bl, tr, tr, bl, br]
602};
603
604pub const UNIT_POINTS: [Vec3; 8] = {
612 let p0 = Vec3::new(-0.5, 0.5, 0.5);
613 let p1 = Vec3::new(-0.5, 0.5, -0.5);
614 let p2 = Vec3::new(0.5, 0.5, -0.5);
615 let p3 = Vec3::new(0.5, 0.5, 0.5);
616
617 let p4 = Vec3::new(-0.5, -0.5, 0.5);
618 let p7 = Vec3::new(-0.5, -0.5, -0.5);
619 let p6 = Vec3::new(0.5, -0.5, -0.5);
620 let p5 = Vec3::new(0.5, -0.5, 0.5);
621
622 [p0, p1, p2, p3, p4, p5, p6, p7]
623};
624
625pub const UNIT_INDICES: [usize; 36] = [
627 0, 2, 1, 0, 3, 2, 0, 4, 3, 4, 5, 3, 3, 6, 2, 3, 5, 6, 1, 7, 0, 7, 4, 0, 4, 6, 5, 4, 7, 6, 2, 7, 1, 2, 6, 7, ];
634
635#[cfg(not(target_arch = "spirv"))]
636pub fn unit_cube() -> Vec<(Vec3, Vec3)> {
637 UNIT_INDICES
638 .chunks_exact(3)
639 .flat_map(|chunk| match chunk {
640 [a, b, c] => {
641 let a = UNIT_POINTS[*a];
642 let b = UNIT_POINTS[*b];
643 let c = UNIT_POINTS[*c];
644 let n = triangle_face_normal(a, b, c);
645 [(a, n), (b, n), (c, n)]
646 }
647 _ => unreachable!(),
648 })
649 .collect::<Vec<_>>()
650}
651
652pub const CUBE: [Vec3; 36] = {
659 let p0 = Vec3::new(-0.5, 0.5, 0.5);
660 let p1 = Vec3::new(-0.5, 0.5, -0.5);
661 let p2 = Vec3::new(0.5, 0.5, -0.5);
662 let p3 = Vec3::new(0.5, 0.5, 0.5);
663 let p4 = Vec3::new(-0.5, -0.5, 0.5);
664 let p7 = Vec3::new(-0.5, -0.5, -0.5);
665 let p6 = Vec3::new(0.5, -0.5, -0.5);
666 let p5 = Vec3::new(0.5, -0.5, 0.5);
667 convex_mesh([p0, p1, p2, p3, p4, p5, p6, p7])
668};
669
670pub fn reflect(i: Vec3, n: Vec3) -> Vec3 {
671 let n = n.alt_norm_or_zero();
672 i - 2.0 * n.dot(i) * n
673}
674
675pub fn is_inside_clip_space(p: Vec3) -> bool {
679 p.x.abs() <= 1.0 && p.y.abs() <= 1.0 && p.z >= 0.0 && p.z <= 1.0
680}
681
682pub const fn convex_mesh([p0, p1, p2, p3, p4, p5, p6, p7]: [Vec3; 8]) -> [Vec3; 36] {
683 [
684 p0, p2, p1, p0, p3, p2, p0, p4, p3, p4, p5, p3, p3, p6, p2, p3, p5, p6, p1, p7, p0, p7, p4, p0, p4, p6, p5, p4, p7, p6, p2, p7, p1, p2, p6, p7, ]
691}
692
693pub struct GpuRng(pub u32);
703
704impl GpuRng {
705 pub fn new(state: u32) -> GpuRng {
706 Self(state)
707 }
708
709 pub fn gen(&mut self) -> u32 {
710 let state = self.0;
711 self.0 = if cfg!(gpu) {
712 self.0 * 747796405 + 2891336453
713 } else {
714 self.0.wrapping_sub(747796405).wrapping_add(2891336453)
715 };
716 let word = (state >> ((state >> 28) + 4)) ^ state;
717 let word = if cfg!(gpu) {
718 word * 277803737
719 } else {
720 word.wrapping_mul(277803737)
721 };
722 (word >> 22) ^ word
723 }
724
725 pub fn gen_u32(&mut self, min: u32, max: u32) -> u32 {
726 let range = max - min;
727 let percent = self.gen_f32(0.0, 1.0);
728 min + (range as f32 * percent).round() as u32
729 }
730
731 pub fn gen_f32(&mut self, min: f32, max: f32) -> f32 {
732 let range = max - min;
733 let numerator = self.gen();
734 let percentage = numerator as f32 / u32::MAX as f32;
735 min + range * percentage
736 }
737
738 pub fn gen_vec3(&mut self, min: Vec3, max: Vec3) -> Vec3 {
739 let x = self.gen_f32(min.x, max.x);
740 let y = self.gen_f32(min.y, max.y);
741 let z = self.gen_f32(min.z, max.z);
742 Vec3::new(x, y, z)
743 }
744
745 pub fn gen_vec2(&mut self, min: Vec2, max: Vec2) -> Vec2 {
746 let x = self.gen_f32(min.x, max.x);
747 let y = self.gen_f32(min.y, max.y);
748 Vec2::new(x, y)
749 }
750}
751
752pub fn convert_pixel_to_ndc(pixel_coord: Vec2, viewport_size: UVec2) -> Vec2 {
756 let mut normalized = pixel_coord / viewport_size.as_vec2();
758 normalized.y = 1.0 - normalized.y;
760 (normalized * 2.0) - 1.0
762}
763
764#[cfg(test)]
765mod test {
766 use super::*;
767
768 #[test]
769 fn step_sanity() {
770 assert_eq!(0.0, step_le(0.0, -0.33333));
771 assert_eq!(1.0, step_le(0.0, 0.33333));
772 assert_eq!(1.0, step_le(0.0, 0.0));
773 }
774
775 #[test]
776 #[allow(clippy::bool_comparison)]
777 fn nan_sanity() {
778 let n = f32::NAN;
779 assert!(n.is_nan());
780 assert!((n <= 0.0) == false);
781 assert!((n > 0.0) == false);
782 }
783
784 #[test]
785 fn signum_sanity() {
786 assert_eq!(1.0, signum_or_zero(0.33));
787 assert_eq!(1.0, signum_or_zero(0.0));
788 assert_eq!(1.0, signum_or_zero(-0.0));
789 assert_eq!(-1.0, signum_or_zero(-0.33));
790
791 let nan = f32::NAN;
792 assert_eq!(0.0, signum_or_zero(nan));
793 }
794}