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