diff --git a/Cargo.toml b/Cargo.toml index 24a552a5..c6691e02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,9 @@ # \___\/ ==\______\/\_____\__\/ ==\______/_____,´ /==\_____\___\/==\_______\/ # \_____\,´ +# On nightly only +#cargo-features = ["panic-immediate-abort"] + [package] name = "retrofire" description = "90s style software 3D renderer and graphics tools." @@ -67,12 +70,21 @@ debug = 1 [profile.bench] opt-level = 3 codegen-units = 1 -lto = "thin" +lto = "fat" [profile.dev] opt-level = 1 split-debuginfo = "unpacked" +[profile.min] +inherits = "release" +opt-level = 3 +codegen-units = 1 +lto = "fat" +debug = 0 +strip = true +panic = "abort" +#panic = "immediate-abort" # On nightly only [[bench]] name = "fill" diff --git a/benches/clip.rs b/benches/clip.rs index c2450ff3..d970a778 100644 --- a/benches/clip.rs +++ b/benches/clip.rs @@ -14,12 +14,13 @@ use retrofire_core::{ //#[global_allocator] //static ALLOC: AllocProfiler = AllocProfiler::system(); -#[divan::bench(args = [1, 10, 100, 1000, 10_000])] +#[divan::bench(args = [1, 10, 100, 1000, 10_000], min_time = 1)] fn clip_mixed(b: Bencher, n: usize) { let rng = &mut DefaultRng::default(); let pts = pt3(-10.0, -10.0, -10.0)..pt3(10.0, 10.0, 10.0); let proj = orthographic(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0)); + let mut out = Vec::with_capacity(n); b.with_inputs(|| { repeat_with(|| { let vs = array::from_fn(|_| { @@ -32,18 +33,19 @@ fn clip_mixed(b: Bencher, n: usize) { }) .input_counter(|tris| ItemsCount::of_iter(tris)) .bench_local_values(|tris| { - let mut out = Vec::new(); + out.clear(); view_frustum::clip(tris.as_slice(), &mut out); - out + out.len() }) } -#[divan::bench(args = [1, 10, 100, 1000, 10_000])] +#[divan::bench(args = [1, 10, 100, 1000, 10_000], min_time = 1)] fn clip_all_inside(b: Bencher, n: usize) { let rng = &mut DefaultRng::default(); let pts = pt3(-1.0, -1.0, -1.0)..pt3(1.0, 1.0, 1.0); let proj = orthographic(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0)); + let mut out = Vec::with_capacity(n); b.with_inputs(|| { repeat_with(|| { let vs = array::from_fn(|_| { @@ -56,18 +58,19 @@ fn clip_all_inside(b: Bencher, n: usize) { }) .input_counter(|tris| ItemsCount::of_iter(tris)) .bench_local_values(|tris| { - let mut out = Vec::new(); + out.clear(); view_frustum::clip(tris.as_slice(), &mut out); - out + out.len() }) } -#[divan::bench(args = [1, 10, 100, 1000, 10_000])] +#[divan::bench(args = [1, 10, 100, 1000, 10_000], min_time = 1)] fn clip_all_outside(b: Bencher, n: usize) { let mut rng = DEFAULT_RNG; let pts = pt3(2.0, -10.0, -10.0)..pt3(10.0, 10.0, 10.0); let proj = orthographic(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0)); + let mut out = Vec::with_capacity(n); b.with_inputs(|| { repeat_with(|| { let vs = ([pts.start; 3]..[pts.end; 3]) @@ -80,9 +83,9 @@ fn clip_all_outside(b: Bencher, n: usize) { }) .input_counter(|tris| ItemsCount::of_iter(tris)) .bench_local_values(|tris| { - let mut out = Vec::with_capacity(tris.len()); + out.clear(); view_frustum::clip(tris.as_slice(), &mut out); - out + out.len() }) } diff --git a/benches/fill.rs b/benches/fill.rs index a6f38133..2bee07fa 100644 --- a/benches/fill.rs +++ b/benches/fill.rs @@ -56,10 +56,8 @@ fn gouraud(b: Bencher, sz: f32) { }); }); - let buf = Buf2::new_from( - (1024, 1024), - buf.data().into_iter().map(|c| c.to_color3()), - ); + let buf = + Buf2::new_from((1024, 1024), buf.data().iter().map(|c| c.to_color3())); save_ppm("benches_fill_color.ppm", buf).unwrap(); } diff --git a/clip_arrays.txt b/clip_arrays.txt new file mode 100644 index 00000000..dd35e1e3 --- /dev/null +++ b/clip_arrays.txt @@ -0,0 +1,40 @@ + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +clip fastest │ slowest │ median │ mean │ samples │ iters +├─ clip_all_inside │ │ │ │ │ +│ ├─ 1 121.6 ns │ 6.734 µs │ 124.6 ns │ 192.4 ns │ 100 │ 100 +│ │ 8.217 Mitem/s │ 148.4 Kitem/s │ 8.019 Mitem/s │ 5.196 Mitem/s │ │ +│ ├─ 10 481.9 ns │ 3.401 µs │ 489.4 ns │ 527.6 ns │ 100 │ 400 +│ │ 20.74 Mitem/s │ 2.94 Mitem/s │ 20.43 Mitem/s │ 18.95 Mitem/s │ │ +│ ├─ 100 1.299 µs │ 14.45 µs │ 1.402 µs │ 1.585 µs │ 100 │ 100 +│ │ 76.94 Mitem/s │ 6.92 Mitem/s │ 71.29 Mitem/s │ 63.08 Mitem/s │ │ +│ ├─ 1000 9.797 µs │ 55.06 µs │ 9.962 µs │ 12.42 µs │ 100 │ 100 +│ │ 102 Mitem/s │ 18.16 Mitem/s │ 100.3 Mitem/s │ 80.51 Mitem/s │ │ +│ ╰─ 10000 268.9 µs │ 444.8 µs │ 304.2 µs │ 317.1 µs │ 100 │ 100 +│ 37.17 Mitem/s │ 22.48 Mitem/s │ 32.86 Mitem/s │ 31.53 Mitem/s │ │ +├─ clip_all_outside │ │ │ │ │ +│ ├─ 1 70.45 ns │ 71.41 ns │ 71.04 ns │ 70.97 ns │ 100 │ 6400 +│ │ 14.19 Mitem/s │ 14 Mitem/s │ 14.07 Mitem/s │ 14.08 Mitem/s │ │ +│ ├─ 10 140.4 ns │ 162.3 ns │ 142 ns │ 142.2 ns │ 100 │ 3200 +│ │ 71.21 Mitem/s │ 61.6 Mitem/s │ 70.38 Mitem/s │ 70.27 Mitem/s │ │ +│ ├─ 100 325 ns │ 337.3 ns │ 327.9 ns │ 328.1 ns │ 100 │ 1600 +│ │ 307.6 Mitem/s │ 296.4 Mitem/s │ 304.9 Mitem/s │ 304.7 Mitem/s │ │ +│ ├─ 1000 3.085 µs │ 11.86 µs │ 3.109 µs │ 3.203 µs │ 100 │ 100 +│ │ 324 Mitem/s │ 84.29 Mitem/s │ 321.5 Mitem/s │ 312.1 Mitem/s │ │ +│ ╰─ 10000 25.36 µs │ 38.05 µs │ 25.84 µs │ 26.03 µs │ 100 │ 100 +│ 394.2 Mitem/s │ 262.8 Mitem/s │ 386.8 Mitem/s │ 384.1 Mitem/s │ │ +╰─ clip_mixed │ │ │ │ │ + ├─ 1 124.7 ns │ 281 ns │ 205.6 ns │ 205.5 ns │ 100 │ 3200 + │ 8.017 Mitem/s │ 3.557 Mitem/s │ 4.862 Mitem/s │ 4.865 Mitem/s │ │ + ├─ 10 1.137 µs │ 4.934 µs │ 1.81 µs │ 1.875 µs │ 100 │ 400 + │ 8.787 Mitem/s │ 2.026 Mitem/s │ 5.523 Mitem/s │ 5.33 Mitem/s │ │ + ├─ 100 12.63 µs │ 20.31 µs │ 16.45 µs │ 16.47 µs │ 100 │ 100 + │ 7.911 Mitem/s │ 4.922 Mitem/s │ 6.077 Mitem/s │ 6.068 Mitem/s │ │ + ├─ 1000 147.5 µs │ 313.6 µs │ 159 µs │ 162.7 µs │ 100 │ 100 + │ 6.776 Mitem/s │ 3.188 Mitem/s │ 6.288 Mitem/s │ 6.145 Mitem/s │ │ + ╰─ 10000 1.544 ms │ 2.106 ms │ 1.598 ms │ 1.638 ms │ 100 │ 100 + 6.474 Mitem/s │ 4.746 Mitem/s │ 6.256 Mitem/s │ 6.104 Mitem/s │ │ + diff --git a/clip_no_changes.txt b/clip_no_changes.txt new file mode 100644 index 00000000..df7e41e2 --- /dev/null +++ b/clip_no_changes.txt @@ -0,0 +1,40 @@ + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +clip fastest │ slowest │ median │ mean │ samples │ iters +├─ clip_all_inside │ │ │ │ │ +│ ├─ 1 252.6 ns │ 6.016 µs │ 260.6 ns │ 319.9 ns │ 100 │ 100 +│ │ 3.957 Mitem/s │ 166.2 Kitem/s │ 3.835 Mitem/s │ 3.125 Mitem/s │ │ +│ ├─ 10 625.4 ns │ 1.26 µs │ 641.1 ns │ 647.9 ns │ 100 │ 400 +│ │ 15.98 Mitem/s │ 7.93 Mitem/s │ 15.59 Mitem/s │ 15.43 Mitem/s │ │ +│ ├─ 100 1.376 µs │ 13.15 µs │ 1.479 µs │ 1.664 µs │ 100 │ 100 +│ │ 72.63 Mitem/s │ 7.602 Mitem/s │ 67.6 Mitem/s │ 60.06 Mitem/s │ │ +│ ├─ 1000 9.529 µs │ 47.56 µs │ 9.637 µs │ 10.16 µs │ 100 │ 100 +│ │ 104.9 Mitem/s │ 21.02 Mitem/s │ 103.7 Mitem/s │ 98.36 Mitem/s │ │ +│ ╰─ 10000 125.3 µs │ 500.6 µs │ 131.7 µs │ 164.8 µs │ 100 │ 100 +│ 79.79 Mitem/s │ 19.97 Mitem/s │ 75.91 Mitem/s │ 60.64 Mitem/s │ │ +├─ clip_all_outside │ │ │ │ │ +│ ├─ 1 204.1 ns │ 207 ns │ 205.4 ns │ 205.5 ns │ 100 │ 3200 +│ │ 4.898 Mitem/s │ 4.83 Mitem/s │ 4.866 Mitem/s │ 4.865 Mitem/s │ │ +│ ├─ 10 281.7 ns │ 309.1 ns │ 284.8 ns │ 285 ns │ 100 │ 1600 +│ │ 35.49 Mitem/s │ 32.34 Mitem/s │ 35.1 Mitem/s │ 35.07 Mitem/s │ │ +│ ├─ 100 397.7 ns │ 1.069 µs │ 424.5 ns │ 452.9 ns │ 100 │ 1600 +│ │ 251.4 Mitem/s │ 93.46 Mitem/s │ 235.5 Mitem/s │ 220.7 Mitem/s │ │ +│ ├─ 1000 2.69 µs │ 17.68 µs │ 2.719 µs │ 2.882 µs │ 100 │ 100 +│ │ 371.6 Mitem/s │ 56.53 Mitem/s │ 367.6 Mitem/s │ 346.9 Mitem/s │ │ +│ ╰─ 10000 20.52 µs │ 57.49 µs │ 21.38 µs │ 22.6 µs │ 100 │ 100 +│ 487.2 Mitem/s │ 173.9 Mitem/s │ 467.5 Mitem/s │ 442.4 Mitem/s │ │ +╰─ clip_mixed │ │ │ │ │ + ├─ 1 239.8 ns │ 440.6 ns │ 329.9 ns │ 334.2 ns │ 100 │ 1600 + │ 4.168 Mitem/s │ 2.269 Mitem/s │ 3.03 Mitem/s │ 2.992 Mitem/s │ │ + ├─ 10 1.261 µs │ 3.492 µs │ 1.895 µs │ 1.91 µs │ 100 │ 400 + │ 7.924 Mitem/s │ 2.862 Mitem/s │ 5.276 Mitem/s │ 5.234 Mitem/s │ │ + ├─ 100 13.6 µs │ 83.33 µs │ 17.75 µs │ 19.44 µs │ 100 │ 100 + │ 7.35 Mitem/s │ 1.199 Mitem/s │ 5.631 Mitem/s │ 5.142 Mitem/s │ │ + ├─ 1000 141.2 µs │ 230.3 µs │ 152.2 µs │ 154 µs │ 100 │ 100 + │ 7.081 Mitem/s │ 4.342 Mitem/s │ 6.567 Mitem/s │ 6.492 Mitem/s │ │ + ╰─ 10000 1.475 ms │ 1.835 ms │ 1.532 ms │ 1.553 ms │ 100 │ 100 + 6.777 Mitem/s │ 5.447 Mitem/s │ 6.524 Mitem/s │ 6.435 Mitem/s │ │ + diff --git a/core/Cargo.toml b/core/Cargo.toml index e06566cf..76c0b4e7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -22,7 +22,7 @@ repository.workspace = true documentation.workspace = true [features] -default = ["std"] +default = ["std", "bumpalo"] # Use std fp functions, enable I/O and timing support. std = ["fp"] # Use fp functions from the libm crate. @@ -32,9 +32,13 @@ mm = ["fp", "dep:micromath"] # For internal use only. fp = [] +bumpalo = ["dep:bumpalo"] + [dependencies] libm = { version = "0.2", optional = true } micromath = { version = "2.1", optional = true } +bumpalo = { version = "3.19", features = ["collections"], optional = true } + [lints] workspace = true diff --git a/core/examples/hello_tri.rs b/core/examples/hello_tri.rs index 3e33b97e..8f4ddf17 100644 --- a/core/examples/hello_tri.rs +++ b/core/examples/hello_tri.rs @@ -1,5 +1,5 @@ +use retrofire_core::prelude::*; use retrofire_core::render::{Model, render, shader}; -use retrofire_core::{prelude::*, util::*}; fn main() { let verts = [ @@ -53,6 +53,7 @@ fn main() { } #[cfg(feature = "std")] { + use retrofire_core::util::pnm; pnm::save_ppm("triangle.ppm", framebuf).unwrap(); } } diff --git a/core/src/geom/mesh.rs b/core/src/geom/mesh.rs index f04a50cb..9cba28ba 100644 --- a/core/src/geom/mesh.rs +++ b/core/src/geom/mesh.rs @@ -92,6 +92,7 @@ impl Mesh { } /// Returns a mesh with the faces and vertices of both `self` and `other`. + #[must_use] pub fn merge(mut self, Self { faces, verts }: Self) -> Self { let n = self.verts.len(); self.verts.extend(verts); @@ -107,7 +108,7 @@ fn assert_indices_in_bounds(faces: &[Tri], len: usize) { assert!( vs.iter().all(|&j| j < len), "vertex index out of bounds at faces[{i}]: {vs:?}" - ) + ); } } @@ -164,6 +165,7 @@ impl Builder { /// /// # Panics /// If any of the vertex indices in `faces` ≥ `verts.len()`. + #[must_use] pub fn build(self) -> Mesh { // Sanity checks done by new() Mesh::new(self.mesh.faces, self.mesh.verts) @@ -175,6 +177,7 @@ impl Builder { /// /// This is an eager operation, that is, only vertices *currently* /// added to the builder are transformed. + #[must_use] pub fn transform(self, tf: &Mat4) -> Self { self.warp(|v| vertex(tf.apply(&v.pos), v.attrib)) } @@ -184,6 +187,7 @@ impl Builder { /// This method can be used for various nonlinear transformations such as /// twisting or dilation. This is an eager operation, that is, only vertices /// *currently* added to the builder are transformed. + #[must_use] pub fn warp(mut self, f: impl FnMut(Vertex3) -> Vertex3) -> Self { self.mesh.verts = self.mesh.verts.into_iter().map(f).collect(); self @@ -202,6 +206,7 @@ impl Builder { /// This is an eager operation, that is, only vertices *currently* added /// to the builder are transformed. The attribute type of the result is /// `Normal3`; the vertex type it accepts is changed accordingly. + #[must_use] pub fn with_vertex_normals(self) -> Builder { let Mesh { verts, faces } = self.mesh; diff --git a/core/src/geom/prim.rs b/core/src/geom/prim.rs index 196d5e72..88701990 100644 --- a/core/src/geom/prim.rs +++ b/core/src/geom/prim.rs @@ -467,6 +467,7 @@ impl Plane3 { /// /// assert_eq!(::new(0.0, 0.0, 1.0, 2.0).project(pt), pt3(1.0, 2.0, 2.0)); /// ``` + #[must_use] pub fn project(&self, pt: Point3) -> Point3 { // The vector that projects pt on the plane is parallel with the plane // normal and its length is the distance of pt from the plane. @@ -672,9 +673,9 @@ impl Line2 { /// /// # Panics /// If the vector (a, b) is not unit-length. - pub fn new(a: f32, b: f32, c: f32) -> Self { + pub const fn new(a: f32, b: f32, c: f32) -> Self { // TODO This method can't itself normalize because const - assert!((a * a + b * b - 1.0).abs() < 1e-6, "non-unit normal"); + assert!((a * a + b * b - 1.0).abs() < 1e-5, "non-unit normal",); Self(Vector::new([a, b, -c])) } @@ -720,7 +721,7 @@ impl Line2 { /// Returns the coefficients [a, b, c] of the line equation ax + by = c. pub const fn coeffs(&self) -> [f32; 3] { - return self.0.0; + self.0.0 } } @@ -1071,21 +1072,21 @@ mod tests { let mut l: Line2; l = Line2::new(1.0, 0.0, 0.0); // x = 0 - assert_eq!(format!("{:?}", l), "Line(x = 0)"); + assert_eq!(format!("{l:?}"), "Line(x = 0)"); l = Line2::from_points(pt2(2.0, 0.0), pt2(2.0, -1.0)); assert_eq!(l.coeffs(), [1.0, 0.0, -2.0]); - assert_eq!(format!("{:?}", l), "Line(x = 2)"); + assert_eq!(format!("{l:?}"), "Line(x = 2)"); l = Line2::new(1.0, 0.0, 2.0); // x = 2 - assert_eq!(format!("{:?}", l), "Line(x = 2)"); + assert_eq!(format!("{l:?}"), "Line(x = 2)"); l = Line2::new(0.0, 1.0, 0.0); // y = 0 - assert_eq!(format!("{:?}", l), "Line(y = 0)"); + assert_eq!(format!("{l:?}"), "Line(y = 0)"); l = Line2::from_points(pt2(0.0, -3.0), pt2(1.0, -3.0)); // y = -3 assert_eq!(l.slope_intercept(), Some((0.0, -3.0))); - assert_eq!(format!("{:?}", l), "Line(y = -3)"); + assert_eq!(format!("{l:?}"), "Line(y = -3)"); l = Line2::new(0.0, 1.0, -3.0); // y = -3 assert_eq!(format!("{:?}", l), "Line(y = -3)"); diff --git a/core/src/lib.rs b/core/src/lib.rs index 11d2a072..0a5b3e3c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -41,6 +41,7 @@ //! # Example //! //! ``` +#![allow(clippy::needless_doctest_main)] #![doc = include_str!("../examples/hello_tri.rs")] //! ``` diff --git a/core/src/math.rs b/core/src/math.rs index d90a9aae..3250aead 100644 --- a/core/src/math.rs +++ b/core/src/math.rs @@ -1,7 +1,7 @@ //! Linear algebra and other useful mathematics. //! //! Includes [vectors][self::vec], [matrices][mat], [colors][color], -//! [angles][angle], [Bezier splines][spline] and [pseudo-random numbers][rand], +//! [angles][angle], [splines][spline] and [pseudo-random numbers][rand], //! as well as support for custom [varying][vary] types and utilities such as //! approximate equality comparisons. //! diff --git a/core/src/math/angle.rs b/core/src/math/angle.rs index e3aeaff0..fdf93d0f 100644 --- a/core/src/math/angle.rs +++ b/core/src/math/angle.rs @@ -77,6 +77,7 @@ pub const fn turns(a: f32) -> Angle { /// # Panics /// If `x` is outside the range [-1.0, 1.0]. #[cfg(feature = "fp")] +#[inline] pub fn asin(x: f32) -> Angle { assert!(-1.0 <= x && x <= 1.0); Angle(f32::asin(x)) @@ -96,6 +97,7 @@ pub fn asin(x: f32) -> Angle { /// # Panics /// If `x` is outside the range [-1.0, 1.0]. #[cfg(feature = "fp")] +#[inline] pub fn acos(x: f32) -> Angle { Angle(f32::acos(x)) } @@ -113,11 +115,13 @@ pub fn acos(x: f32) -> Angle { /// assert_eq!(atan2(-3.0, 0.0), degs(-90.0)); /// ``` #[cfg(feature = "fp")] +#[inline] pub fn atan2(y: f32, x: f32) -> Angle { Angle(f32::atan2(y, x)) } /// Returns a polar coordinate vector with azimuth `az` and radius `r`. +#[inline] pub const fn polar(r: f32, az: Angle) -> PolarVec { Vector::new([r, az.to_rads()]) } @@ -126,6 +130,7 @@ pub const fn polar(r: f32, az: Angle) -> PolarVec { /// altitude `alt`, and radius `r`. /// /// An altitude of +90° corresponds to straight up and -90° to straight down. +#[inline] pub const fn spherical(r: f32, az: Angle, alt: Angle) -> SphericalVec { Vector::new([r, az.to_rads(), alt.to_rads()]) } @@ -183,11 +188,13 @@ impl Angle { /// Returns the minimum of `self` and `other`. #[inline] + #[must_use] pub const fn min(self, other: Self) -> Self { Self(self.0.min(other.0)) } /// Returns the maximum of `self` and `other`. #[inline] + #[must_use] pub const fn max(self, other: Self) -> Self { Self(self.0.max(other.0)) } @@ -413,6 +420,7 @@ impl Vec2 { /// // A negative x and zero y maps to straight angle azimuth /// assert_approx_eq!(vec2(-1.0, 0.0).to_polar().az(), degs(180.0)); /// ``` + #[inline] pub fn to_polar(&self) -> PolarVec { polar(self.len(), self.atan()) } @@ -426,8 +434,8 @@ impl Vec3 { /// * `r` equals `self.len()` /// * `az`is the angle between `self` and the xy-plane in the range /// (-180°, 180°] such that positive `z` maps to *negative* `az`, and - /// * `alt` is the angle between `self` and the xz-plane in the - /// range [-90°, 90°] such that positive `y` maps to positive `alt`. + /// * `alt` is the angle between `self` and the xz-plane in the range + /// [-90°, 90°] such that positive `y` maps to positive `alt`. /// /// # Examples /// ``` @@ -631,7 +639,7 @@ impl DivAssign for Angle { impl From> for Vec2 { /// Converts a polar vector into the equivalent Cartesian vector. /// - /// See [PolarVec::to_cart] for more information. + /// See [`PolarVec::to_cart`] for more information. #[inline] fn from(p: PolarVec) -> Self { p.to_cart() @@ -642,7 +650,7 @@ impl From> for Vec2 { impl From> for PolarVec { /// Converts a Cartesian 2-vector into the equivalent polar vector. /// - /// See [Vec2::to_polar] for more information. + /// See [`Vec2::to_polar`] for more information. #[inline] fn from(v: Vec2) -> Self { v.to_polar() @@ -653,7 +661,7 @@ impl From> for PolarVec { impl From> for Vec3 { /// Converts a spherical coordinate vector to a Euclidean 3-vector. /// - /// See [SphericalVec::to_cart] for more information. + /// See [`SphericalVec::to_cart`] for more information. #[inline] fn from(v: SphericalVec) -> Self { v.to_cart() @@ -664,7 +672,7 @@ impl From> for Vec3 { impl From> for SphericalVec { /// Converts a Cartesian 3-vector into the equivalent spherical vector. /// - /// See [Vec3::to_spherical] for more information. + /// See [`Vec3::to_spherical`] for more information. #[inline] fn from(v: Vec3) -> Self { v.to_spherical() diff --git a/core/src/math/color.rs b/core/src/math/color.rs index eab9acab..225c7262 100644 --- a/core/src/math/color.rs +++ b/core/src/math/color.rs @@ -139,6 +139,7 @@ impl Color<[f32; N], Sp> { // A generic clamp for Sc: Ord would conflict with this one. There is // currently no clean way to support both floats and impl Ord types. // However, ColorX should have its own inherent impls. + #[inline] #[must_use] pub fn clamp(&self, min: &Self, max: &Self) -> Self { array::from_fn(|i| self[i].clamp(min[i], max[i])).into() @@ -313,7 +314,7 @@ impl Color3f { (r - g) / d + 4.0 }; let h = h / 6.0; - let l = (max + min) / 2.0; + let l = min.midpoint(max); let s = if l == 0.0 || l == 1.0 { 0.0 } else { @@ -330,6 +331,7 @@ impl Color3f { impl Color4f { /// Returns `self` as RGB, discarding the alpha channel. + #[inline] pub const fn to_rgb(self) -> Color3f { let [r, g, b, _] = self.0; rgb(r, g, b) @@ -421,7 +423,7 @@ impl Color3 { let rgb = hcx_to_rgb(h / _1, c, x, 0); rgb.map(|ch| { let ch = ch + m; - debug_assert!(0 <= ch && ch < _1, "channel oob: {:?}", ch); + debug_assert!(0 <= ch && ch < _1, "channel oob: {ch:?}"); ch as u8 }) .into() @@ -473,11 +475,13 @@ fn hcx_to_rgb(h: i32, c: T, x: T, z: T) -> [T; 3] { impl Color4 { /// Returns `self` as HSL, discarding the alpha channel. + #[inline] pub const fn to_hsl(self) -> Color3 { let [h, s, l, _] = self.0; hsl(h, s, l) } /// Returns the RGBA color equivalent to `self`. + #[inline] pub fn to_rgba(self) -> Color4 { let [r, g, b] = self.to_hsl().to_rgb().0; rgba(r, g, b, self.a()) @@ -485,11 +489,13 @@ impl Color4 { } impl Color4f { /// Returns `self` as HSL, discarding the alpha channel. + #[inline] pub fn to_hsl(self) -> Color3f { let [h, s, l, _] = self.0; hsl(h, s, l) } /// Returns the RGBA color equivalent to `self`. + #[inline] pub fn to_rgba(self) -> Color4f { let [r, g, b] = self.to_hsl().to_rgb().0; rgba(r, g, b, self.a()) @@ -498,14 +504,17 @@ impl Color4f { impl Color<[Ch; N], Rgb> { /// Returns the red component of `self`. + #[inline] pub const fn r(&self) -> Ch { self.0[0] } /// Returns the green component of `self`. + #[inline] pub const fn g(&self) -> Ch { self.0[1] } /// Returns the blue component of `self`. + #[inline] pub const fn b(&self) -> Ch { self.0[2] } @@ -513,18 +522,22 @@ impl Color<[Ch; N], Rgb> { impl Color<[Ch; N], Rgba> { /// Returns the red component of `self`. + #[inline] pub const fn r(&self) -> Ch { self.0[0] } /// Returns the green component of `self`. + #[inline] pub const fn g(&self) -> Ch { self.0[1] } /// Returns the blue component of `self`. + #[inline] pub const fn b(&self) -> Ch { self.0[2] } /// Returns the alpha component of `self`. + #[inline] pub const fn a(&self) -> Ch { self.0[3] } @@ -532,14 +545,17 @@ impl Color<[Ch; N], Rgba> { impl Color<[Ch; N], Hsl> { /// Returns the hue component of `self`. + #[inline] pub const fn h(&self) -> Ch { self.0[0] } /// Returns the saturation component of `self`. + #[inline] pub const fn s(&self) -> Ch { self.0[1] } /// Returns the luminance component of `self`. + #[inline] pub const fn l(&self) -> Ch { self.0[2] } @@ -550,18 +566,22 @@ where Ch: Copy, { /// Returns the hue component of `self`. + #[inline] pub const fn h(&self) -> Ch { self.0[0] } /// Returns the saturation component of `self`. + #[inline] pub const fn s(&self) -> Ch { self.0[1] } /// Returns the luminance component of `self`. + #[inline] pub const fn l(&self) -> Ch { self.0[2] } /// Returns the alpha component of `self`. + #[inline] pub const fn a(&self) -> Ch { self.0[3] } @@ -578,13 +598,15 @@ impl Affine for Color<[u8; DIM], Sp> { const DIM: usize = DIM; + #[inline] fn add(&self, other: &Self::Diff) -> Self { array::from_fn(|i| { let sum = i32::from(self.0[i]) + other.0[i]; - sum.clamp(0, u8::MAX as i32) as u8 + sum.clamp(0, u8::MAX.into()) as u8 }) .into() } + #[inline] fn sub(&self, other: &Self) -> Self::Diff { array::from_fn(|i| i32::from(self.0[i]) - i32::from(other.0[i])).into() } @@ -616,6 +638,7 @@ where type Scalar = f32; /// Returns the all-zeroes color (black). + #[inline] fn zero() -> Self { [0.0; DIM].into() } @@ -636,6 +659,7 @@ impl ZDiv for Color<[Sc; N], Sp> where Sc: ZDiv + Copy { impl Copy for Color {} impl Clone for Color { + #[inline] fn clone(&self) -> Self { self.0.clone().into() } @@ -648,6 +672,7 @@ impl Debug for Color { } impl Default for Color { + #[inline] fn default() -> Self { R::default().into() } @@ -656,6 +681,7 @@ impl Default for Color { impl Eq for Color {} impl PartialEq for Color { + #[inline] fn eq(&self, other: &Self) -> bool { self.0 == other.0 } @@ -705,7 +731,7 @@ where { #[inline] fn sub_assign(&mut self, rhs: D) { - *self += rhs.neg(); + self.add_assign(rhs.neg()); } } @@ -728,7 +754,7 @@ where #[inline] fn mul_assign(&mut self, rhs: Self) { for (a, b) in zip(&mut self.0, rhs.0) { - *a = (&*a).mul(b) + *a = Linear::mul(a, b); } } } diff --git a/core/src/math/grad.rs b/core/src/math/grad.rs index 2df68f17..a2b73cf7 100644 --- a/core/src/math/grad.rs +++ b/core/src/math/grad.rs @@ -63,9 +63,9 @@ impl Gradient2 { Kind::Radial(p0, r) => p.distance(&p0) / r, #[cfg(feature = "fp")] Kind::Conical(p0) => { + use super::float::f32; let angle = (p - p0).atan(); // map negative angles to positive - use super::float::f32; f32::rem_euclid(angle.to_turns(), 1.0) } }; diff --git a/core/src/math/mat.rs b/core/src/math/mat.rs index 48426425..837daa8c 100644 --- a/core/src/math/mat.rs +++ b/core/src/math/mat.rs @@ -210,9 +210,9 @@ const fn transpose(a: &mut [[Sc; N]; N]) { let tmp = a[i][j]; a[i][j] = a[j][i]; a[j][i] = tmp; - j += 1 + j += 1; } - i += 1 + i += 1; } } @@ -279,7 +279,7 @@ where array::from_fn(|j| array::from_fn(|i| dot(&lhs[j], &rhs[i]))) } - let mut other = other.0.clone(); + let mut other = other.0; transpose(&mut other); do_compose(&self.0, &other).into() } @@ -519,6 +519,11 @@ impl Mat3 { Some(Mat3::new(res)) } + /// TODO + /// + /// # Panics + /// If the matrix is singular or near-singular. + #[must_use] pub fn inverse(&self) -> Mat3 { self.checked_inverse() .expect("matrix cannot be singular or near-singular") @@ -660,14 +665,6 @@ impl Mat4 { #[must_use] pub fn inverse(&self) -> Mat4 { use super::float::f32; - if cfg!(debug_assertions) { - let det = self.determinant(); - assert!( - !det.approx_eq(&0.0), - "a singular, near-singular, or non-finite matrix does not \ - have a well-defined inverse (determinant = {det})" - ); - } // Elementary row operation subtracting one row, // multiplied by a scalar, from another @@ -685,6 +682,15 @@ impl Mat4 { m.0.swap(r, s); } + if cfg!(debug_assertions) { + let det = self.determinant(); + assert!( + !det.approx_eq(&0.0), + "a singular, near-singular, or non-finite matrix does not \ + have a well-defined inverse (determinant = {det})" + ); + } + // This algorithm attempts to reduce `this` to the identity matrix // by simultaneously applying elementary row operations to it and // another matrix `inv` which starts as the identity matrix. Once @@ -1484,11 +1490,11 @@ mod tests { fn matrix_debug() { assert_eq!( alloc::format!("{MAT:?}"), - r#"Matrix[ + r"Matrix[ [ 0.0, 1.0, 2.0] [10.0, 11.0, 12.0] [20.0, 21.0, 22.0] -]"# +]" ); } } @@ -1756,12 +1762,12 @@ mod tests { fn matrix_debug() { assert_eq!( alloc::format!("{MAT:?}"), - r#"Matrix[ + r"Matrix[ [ 0.0, 1.0, 2.0, 3.0] [10.0, 11.0, 12.0, 13.0] [20.0, 21.0, 22.0, 23.0] [30.0, 31.0, 32.0, 33.0] -]"# +]" ); } } diff --git a/core/src/math/point.rs b/core/src/math/point.rs index 0cc6572d..ed2c4eca 100644 --- a/core/src/math/point.rs +++ b/core/src/math/point.rs @@ -281,9 +281,9 @@ where Sc: ZDiv + Copy, { #[inline] - fn z_div(mut self, z: f32) -> Self { + fn z_div_recip(mut self, recip_z: f32) -> Self { for c in &mut self.0 { - *c = c.z_div(z); + *c = c.z_div_recip(recip_z); } self } @@ -308,12 +308,14 @@ impl ApproxEq for Point<[Sc; N], Sp> { impl Copy for Point {} impl Clone for Point { + #[inline] fn clone(&self) -> Self { Self(self.0.clone(), Pd) } } impl Default for Point { + #[inline] fn default() -> Self { Self(R::default(), Pd) } @@ -329,6 +331,7 @@ impl Debug for Point { impl Eq for Point {} impl PartialEq for Point { + #[inline] fn eq(&self, other: &Self) -> bool { self.0 == other.0 } @@ -479,7 +482,7 @@ where #[inline] fn div(self, rhs: f32) -> Self { - self * rhs.recip() + self.mul(1.0 / rhs) } } impl DivAssign for Point @@ -505,13 +508,14 @@ mod tests { const pt2: fn(f32, f32) -> Point2 = super::pt2; const pt3: fn(f32, f32, f32) -> Point3 = super::pt3; + #[test] fn vector_addition() { assert_eq!(pt2(1.0, 2.0) + vec2(-2.0, 3.0), pt2(-1.0, 5.0)); assert_eq!( pt3(1.0, 2.0, 3.0) + vec3(-2.0, 3.0, 1.0), pt3(-1.0, 5.0, 4.0) - ) + ); } #[test] fn vector_subtraction() { @@ -519,7 +523,7 @@ mod tests { assert_eq!( pt3(1.0, 2.0, 3.0) - vec3(-2.0, 3.0, 1.0), pt3(3.0, -1.0, 2.0) - ) + ); } #[test] fn point_subtraction() { @@ -527,7 +531,7 @@ mod tests { assert_eq!( pt3(1.0, 2.0, 3.0) - pt3(-2.0, 3.0, 1.0), vec3(3.0, -1.0, 2.0) - ) + ); } #[test] fn scalar_multiplication() { diff --git a/core/src/math/rand.rs b/core/src/math/rand.rs index cb37df7c..decb7b45 100644 --- a/core/src/math/rand.rs +++ b/core/src/math/rand.rs @@ -118,7 +118,7 @@ struct Samples(D, R); impl Xorshift64 { /// A random 64-bit prime, used to initialize the generator returned by /// [`Xorshift64::default()`]. - pub const DEFAULT_SEED: u64 = 378682147834061; + pub const DEFAULT_SEED: u64 = 378_682_147_834_061; /// Returns a new `Xorshift64` seeded by the given number. /// @@ -163,10 +163,11 @@ impl Xorshift64 { /// ``` #[cfg(feature = "std")] pub fn from_time() -> Self { - let t = std::time::SystemTime::UNIX_EPOCH + let d = std::time::SystemTime::UNIX_EPOCH .elapsed() - .unwrap(); - Self(t.as_micros() as u64) + // If for some strange reason the system time < epoch... + .unwrap_or_else(|e| e.duration()); + Self(d.as_micros() as u64) } /// Returns 64 bits of pseudo-randomness. @@ -174,6 +175,7 @@ impl Xorshift64 { /// Successive calls to this function (with the same `self`) will yield /// every value in the interval [1, 264) exactly once before /// starting to repeat the sequence. + #[inline] pub const fn next_bits(&mut self) -> u64 { let Self(x) = self; *x ^= *x << 13; @@ -193,6 +195,7 @@ impl Iterator for Samples { /// Returns the next pseudorandom sample from this iterator. /// /// This method never returns `None`. + #[inline] fn next(&mut self) -> Option { Some(self.0.sample(self.1)) } @@ -220,6 +223,7 @@ impl Default for Xorshift64 { impl Distrib for &D { type Sample = D::Sample; + #[inline] fn sample(&self, rng: &mut DefaultRng) -> Self::Sample { (*self).sample(rng) } @@ -357,6 +361,7 @@ where { type Sample = S; + #[inline] fn sample(&self, rng: &mut DefaultRng) -> Self::Sample { Uniform(self.clone()).sample(rng) } @@ -406,6 +411,7 @@ where /// assert_eq!(int_pairs.next(), Some([1, 0])); /// assert_eq!(int_pairs.next(), Some([3, 1])); /// ``` + #[inline] fn sample(&self, rng: &mut DefaultRng) -> [T; N] { let Range { start, end } = self.0; array::from_fn(|i| Uniform(start[i]..end[i]).sample(rng)) @@ -592,6 +598,7 @@ impl Distrib for Bernoulli { /// let bools = array::from_fn(|_| bern.sample(rng)); /// assert_eq!(bools, [true, true, false, true, false, true]); /// ``` + #[inline] fn sample(&self, rng: &mut DefaultRng) -> bool { Uniform(0.0f32..1.0).sample(rng) < self.0 } @@ -601,6 +608,7 @@ impl Distrib for (D, E) { type Sample = (D::Sample, E::Sample); /// Returns a pair of samples, sampled from two separate distributions. + #[inline] fn sample(&self, rng: &mut DefaultRng) -> Self::Sample { (self.0.sample(rng), self.1.sample(rng)) } @@ -616,7 +624,7 @@ mod tests { const COUNT: usize = 1000; fn rng() -> DefaultRng { - Default::default() + DefaultRng::default() } #[test] diff --git a/core/src/math/spline.rs b/core/src/math/spline.rs index 54f6873b..1546c95c 100644 --- a/core/src/math/spline.rs +++ b/core/src/math/spline.rs @@ -1,4 +1,7 @@ //! Bézier curves and splines. + +#![allow(clippy::just_underscores_and_digits)] + use alloc::vec::Vec; use core::{array::from_fn, fmt::Debug, marker::PhantomData}; @@ -243,6 +246,7 @@ impl CubicBezier { /// the curve beyond the control points. /// /// [1]: https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm + #[inline] pub fn eval(&self, t: f32) -> T { let [p0, p1, p2, p3] = &self.0; let p01 = p0.lerp(p1, t); @@ -262,6 +266,7 @@ where /// numerically stable than [`Self::eval`]. Values of *t* outside the /// interval [0, 1] are accepted and extrapolate the curve beyond the /// control points. + #[inline] pub fn fast_eval(&self, t: f32) -> T { // Add a linear combination of the three coefficients // to `p0` to get the result @@ -346,6 +351,7 @@ where /// /// Values of *t* outside the interval [0, 1] are accepted and extrapolate /// the curve beyond the control points. + #[inline] pub fn eval(&self, t: f32) -> P { let Self([p0, p1], [d0, d1]) = self; let [_0, t1, t2, t3] = [1.0, t, t * t, t * t * t]; @@ -366,7 +372,7 @@ where // = (1 - b2) * p0 + b2 * p1 // = p0 + b2 * (p1 - p0) - p0.add(&p1.sub(&p0).mul(b2)) // Affine part + p0.add(&p1.sub(p0).mul(b2)) // Affine part .add(&d0.mul(b1).add(&d1.mul(b3))) // Linear part } @@ -398,7 +404,7 @@ where // Only vectors as expected: // b2·(p1 - p0) + b1·d0 + b3·d1 - p1.sub(&p0) + p1.sub(p0) .mul(b2) .add(&d0.mul(b1).add(&d1.mul(b3))) } @@ -442,13 +448,14 @@ where .into_iter() .flat_map(|Ray(p, d)| [p.add(&d.neg()), p.clone(), p.add(&d)]) .collect(); - Self::new(pts[1..pts.len() - 1].into_iter().cloned()) + Self::new(pts[1..pts.len() - 1].iter().cloned()) } /// Returns the point of `self` at the given *t* value. /// /// Values of *t* outside the interval [0, 1] are accepted and extrapolate /// the curve beyond the control points. + #[inline] pub fn eval(&self, t: f32) -> T { let (u, seg) = self.segment(t); seg.fast_eval(u) @@ -458,6 +465,7 @@ where /// /// Values of *t* outside the interval [0, 1] are accepted and extrapolate /// the curve beyond the control points. + #[inline] pub fn velocity(&self, t: f32) -> T::Diff { let (u, seg) = self.segment(t); seg.velocity(u) @@ -482,7 +490,6 @@ where let num_segs = (self.0.len() - 1) / 3; // Rescale from [0, 1] to [0, num_segs] let t = t * num_segs as f32; - use super::float::f32; // Calculate the segment index. let seg_i = (t as usize).min(num_segs - 1); // The leftover part is the local t value. This is the fractional part @@ -542,6 +549,7 @@ where /// /// Values of *t* outside the interval [0, 1] are accepted and extrapolate /// the curve beyond the control points. + #[inline] pub fn eval(&self, t: f32) -> T { let (u, seg) = self.segment(t); seg.eval(u) @@ -552,6 +560,7 @@ where /// /// Values of *t* outside the interval [0, 1] are accepted and extrapolate /// the curve beyond the control points. + #[inline] pub fn velocity(&self, t: f32) -> T::Diff { let (u, seg) = self.segment(t); seg.velocity(u) @@ -569,6 +578,10 @@ where -0.5, 1.5, -1.5, 0.5; ]; + /// TODO + /// + /// # Panics + /// If `pts` has fewer than four points. pub fn new(pts: impl IntoIterator) -> Self { let pts: Vec<_> = pts.into_iter().collect(); assert!( @@ -582,6 +595,7 @@ where /// /// Values of *t* outside the interval [0, 1] are accepted and extrapolate /// the curve beyond the control points. + #[inline] pub fn eval(&self, t: f32) -> T { let (t, [p0, p1, p2, p3]) = crb_segment(&self.0, t); let [_0, t1, t2, t3] = [1.0, t, t * t, t * t * t]; @@ -599,9 +613,9 @@ where // = P0 - b1·P0 - b2·P0 - b3·P0 + b1·P1 + b2·P2 + b3·P3 // = P0 + b1·(P1 - P0) + b2·(P2 - P0) + b3·(P3 - P0) - let v01 = p1.sub(&p0).mul(b1); - let v02 = &p2.sub(&p0).mul(b2); - let v03 = p3.sub(&p0).mul(b3); + let v01 = p1.sub(p0).mul(b1); + let v02 = p2.sub(p0).mul(b2); + let v03 = p3.sub(p0).mul(b3); p0.add(&v01.add(&v02).add(&v03).mul(1.0 / 2.0)) } @@ -629,9 +643,9 @@ where // = b1·P1 + b2·P2 + b3·P3 - b1·P0 - b2·P0 - b3·P0 // = b1·(P1 - P0) + b2·(P2 - P0) + b3·(P3 - P0) - let v01 = p1.sub(&p0).mul(b1); - let v02 = p2.sub(&p0).mul(b2); - let v03 = p3.sub(&p0).mul(b3); + let v01 = p1.sub(p0).mul(b1); + let v02 = p2.sub(p0).mul(b2); + let v03 = p3.sub(p0).mul(b3); v01.add(&v02).add(&v03).mul(1.0 / 2.0) } } @@ -651,6 +665,10 @@ where ] }; + /// TODO + /// + /// # Panics + /// If `pts` has fewer than four points. pub fn new(pts: impl IntoIterator) -> Self { let pts: Vec<_> = pts.into_iter().collect(); assert!(pts.len() >= 4, "a B-spline requires at least four points"); @@ -670,9 +688,9 @@ where let b2 = 1.0 + 3.0 * t1 + 3.0 * t2 - 3.0 * t3; let b3 = t3; - let v01 = p1.sub(&p0).mul(b1); - let v02 = p2.sub(&p0).mul(b2); - let v03 = p3.sub(&p0).mul(b3); + let v01 = p1.sub(p0).mul(b1); + let v02 = p2.sub(p0).mul(b2); + let v03 = p3.sub(p0).mul(b3); p0.add(&v01.add(&v02).add(&v03).mul(1.0 / 6.0)) } @@ -700,15 +718,16 @@ where // = b1·P1 + b2·P2 + b3·P3 - b1·P0 - b2·P0 - b3·P0 // = b1·(P1 - P0) + b2·(P2 - P0) + b3·(P3 - P0) - let v01 = p1.sub(&p0).mul(b1); - let v02 = p2.sub(&p0).mul(b2); - let v03 = p3.sub(&p0).mul(b3); + let v01 = p1.sub(p0).mul(b1); + let v02 = p2.sub(p0).mul(b2); + let v03 = p3.sub(p0).mul(b3); v01.add(&v02).add(&v03).mul(1.0 / 6.0) } } /// Returns the curve segment and local *t* value corresponding to /// the global *t* value of a Catmull–Rom or B-spline. +#[inline] fn crb_segment(pts: &[T], t: f32) -> (f32, &[T; 4]) { let t = 1.0 + t * (pts.len() as f32 - 3.0); @@ -742,6 +761,7 @@ impl Euclidean { /// Returns the point of `self` at distance *s* from the start, /// as measured along the curve. + #[inline] pub fn eval(&self, s: f32) -> T where Spl: Parametric, @@ -780,6 +800,7 @@ impl Parametric for CubicBezier where T: Affine + Clone> + Clone, { + #[inline] fn eval(&self, t: f32) -> T { self.fast_eval(t) } @@ -789,6 +810,7 @@ impl Parametric for CubicHermite where T: Affine + Clone> + Clone, { + #[inline] fn eval(&self, t: f32) -> T { self.eval(t) } @@ -798,6 +820,7 @@ impl Parametric for BezierSpline where T: Affine + Clone> + Clone, { + #[inline] fn eval(&self, t: f32) -> T { self.eval(t) } @@ -807,6 +830,7 @@ impl Parametric for HermiteSpline where T: Affine + Clone> + Clone, { + #[inline] fn eval(&self, t: f32) -> T { self.eval(t) } @@ -816,6 +840,7 @@ impl Parametric for CatmullRomSpline where T: Affine + Clone> + Clone, { + #[inline] fn eval(&self, t: f32) -> T { self.eval(t) } @@ -825,6 +850,7 @@ impl Parametric for BSpline where T: Affine + Clone> + Clone, { + #[inline] fn eval(&self, t: f32) -> T { self.eval(t) } @@ -834,6 +860,7 @@ impl Parametric for Euclidean where Spl: Parametric, { + #[inline] fn eval(&self, s: f32) -> T { self.eval(s) } diff --git a/core/src/math/vary.rs b/core/src/math/vary.rs index ec9fe73a..9a57b8b3 100644 --- a/core/src/math/vary.rs +++ b/core/src/math/vary.rs @@ -8,8 +8,22 @@ use core::mem; use super::Lerp; pub trait ZDiv: Sized { + /// Performs a z-division. + /// + /// The default implementation calls [`z_div_recip`][Self::z_div_recip] + /// with the reciprocal of `z`. + #[must_use] + fn z_div(self, z: f32) -> Self { + self.z_div_recip(z.recip()) + } + /// Performs a z-division by multiplying by the reciprocal of z. + /// + /// This method should be preferred to [`z_div`][Self::z_div] when the same + /// (reciprocal) z can be reused for several z-divisions. + /// + /// The default implementation is a no-op and simply returns `self`. #[must_use] - fn z_div(self, _z: f32) -> Self { + fn z_div_recip(self, _recip_z: f32) -> Self { self } } @@ -127,15 +141,15 @@ impl Vary for (T, U) { } impl ZDiv for (T, U) { #[inline] - fn z_div(self, z: f32) -> Self { - (self.0.z_div(z), self.1.z_div(z)) + fn z_div_recip(self, recip_z: f32) -> Self { + (self.0.z_div_recip(recip_z), self.1.z_div_recip(recip_z)) } } impl ZDiv for f32 { #[inline] - fn z_div(self, z: f32) -> Self { - self / z + fn z_div_recip(self, recip_z: f32) -> Self { + self * recip_z } } diff --git a/core/src/math/vec.rs b/core/src/math/vec.rs index b0067e21..36ca1694 100644 --- a/core/src/math/vec.rs +++ b/core/src/math/vec.rs @@ -1,13 +1,9 @@ //! Real and projective vectors. //! //! TODO +//! +//! -use super::{ - Affine, ApproxEq, Linear, Point, - space::{Proj3, Real}, - vary::ZDiv, -}; -use crate::math::space::Hom; use core::{ array, fmt::{Debug, Formatter}, @@ -17,15 +13,21 @@ use core::{ ops::{AddAssign, DivAssign, MulAssign, SubAssign}, }; +use super::{ + Affine, ApproxEq, Linear, Point, + space::{Hom, Proj3, Real}, + vary::ZDiv, +}; + // // Types // /// A generic vector type. Represents an element of a vector space. /// -/// or a module, -/// a generalization of a vector space where the scalars can be integers -/// (technically, the scalar type can be any *ring*-like type). +// or a module, +// a generalization of a vector space where the scalars can be integers +// (technically, the scalar type can be any *ring*-like type). /// /// # Type parameters /// * `Repr`: Representation of the scalar components of the vector, @@ -313,6 +315,7 @@ where /// /// assert_eq!(v.reflect(axis), vec3(2.0, 3.0, 1.0)); /// ``` + #[must_use] pub fn reflect(self, axis: Self) -> Self where Sc: Div, @@ -356,7 +359,7 @@ where if !a.mul(y).approx_eq(&b.mul(x)) { return false; } - (x, y) = (a, b) + (x, y) = (a, b); } true } @@ -668,9 +671,9 @@ where Sc: ZDiv + Copy, { #[inline] - fn z_div(mut self, z: f32) -> Self { + fn z_div_recip(mut self, recip_z: f32) -> Self { for c in &mut self.0 { - *c = c.z_div(z); + *c = c.z_div_recip(recip_z); } self } @@ -695,6 +698,7 @@ impl ApproxEq for Vector<[Sc; N], Sp> { impl Copy for Vector {} impl Clone for Vector { + #[inline] fn clone(&self) -> Self { Self::new(self.0.clone()) } @@ -702,14 +706,22 @@ impl Clone for Vector { // Limited to Cartesian vectors because Spherical/PolarVec have own impl impl Default for Vector> { + #[inline] fn default() -> Self { Self::new(R::default()) } } +impl Default for ProjVec3 { + #[inline] + fn default() -> Self { + Self::new(Default::default()) + } +} impl Eq for Vector {} impl PartialEq for Vector { + #[inline] fn eq(&self, other: &Self) -> bool { self.0 == other.0 } diff --git a/core/src/render.rs b/core/src/render.rs index f6d8c6f0..806a0342 100644 --- a/core/src/render.rs +++ b/core/src/render.rs @@ -52,7 +52,7 @@ pub mod text; /// Renderable geometric primitive. pub trait Render { - /// The type of this primitive in clip space + /// The type of this primitive in clip space. type Clip; /// The type for which `Clip` is implemented. diff --git a/core/src/render/batch.rs b/core/src/render/batch.rs index bf917455..10da4af9 100644 --- a/core/src/render/batch.rs +++ b/core/src/render/batch.rs @@ -153,7 +153,7 @@ impl Batch, Vtx, Uni, Shd, Tgt, Ctx> { let prims = prims.into_iter().map(|e| Edge(e.0 + n, e.1 + n)); self.verts.extend(verts); - self.prims.extend(prims) + self.prims.extend(prims); } } diff --git a/core/src/render/cam.rs b/core/src/render/cam.rs index c3fc9aa7..2c1c7c81 100644 --- a/core/src/render/cam.rs +++ b/core/src/render/cam.rs @@ -154,6 +154,7 @@ impl Camera<()> { } /// Sets the world-to-view transform of this camera. + #[must_use] pub fn transform(self, tf: T) -> Camera { let Self { dims, project, viewport, .. } = self; Camera { @@ -167,6 +168,7 @@ impl Camera<()> { impl Camera { /// Sets the viewport bounds of this camera. + #[must_use] pub fn viewport(self, bounds: impl Into>) -> Self { let (w, h) = self.dims; @@ -196,6 +198,7 @@ impl Camera { /// # Panics /// * If any parameter value is non-positive. /// * If `near_far` is an empty range. + #[must_use] pub fn perspective(mut self, fov: Fov, near_far: Range) -> Self { let aspect = self.dims.0 as f32 / self.dims.1 as f32; @@ -204,6 +207,7 @@ impl Camera { } /// Sets up orthographic projection. + #[must_use] pub fn orthographic(mut self, bounds: Range) -> Self { self.project = orthographic(bounds.start, bounds.end); self @@ -226,6 +230,7 @@ impl Camera { } /// Renders the given geometry from the viewpoint of this camera. + #[allow(clippy::too_many_arguments)] pub fn render( &self, prims: impl AsRef<[Prim]>, @@ -371,9 +376,8 @@ impl PitchYawRoll { /// Adjusts the orientation of the camera by the given delta angles. pub fn rotate(&mut self, pitch: Angle, yaw: Angle, roll: Angle) { - self.orient = self - .orient - .compose(&rotate_pyr(pitch, yaw, roll).to()) + let rot = rotate_pyr(pitch, yaw, roll); + self.orient = self.orient.compose(&rot.to()); } /// Sets the orientation of the camera to the given **world-space** angles. diff --git a/core/src/render/clip.rs b/core/src/render/clip.rs index 2b64f3ff..9b3fbe22 100644 --- a/core/src/render/clip.rs +++ b/core/src/render/clip.rs @@ -14,7 +14,12 @@ //! use alloc::vec::Vec; -use core::{iter::zip, mem::swap}; +use core::{ + iter::zip, + marker::PhantomData, + mem::swap, + ops::{Deref, DerefMut}, +}; use crate::geom::{Edge, Tri, Vertex, vertex}; use crate::math::{Lerp, ProjVec3}; @@ -53,7 +58,7 @@ pub trait Clip { pub type ClipVec = ProjVec3; /// A vertex in clip space. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct ClipVert { pub pos: ClipVec, pub attrib: A, @@ -74,8 +79,75 @@ pub enum Status { #[derive(Debug, Copy, Clone)] pub struct ClipPlane(ClipVec, u8); +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)] +pub struct FixedVec(D, usize, PhantomData); + +pub type ArrayVec = FixedVec; +pub type SliceVec<'a, T> = FixedVec; + +impl> FixedVec { + #[inline] + pub fn new() -> Self + where + T: Default, + D: Default, + { + Self::default() + } + #[inline] + pub fn from(data: D) -> Self { + Self(data, 0, PhantomData) + } + #[inline] + pub fn capacity(&self) -> usize { + self.0.as_ref().len() + } + #[inline] + pub fn clear(&mut self) { + self.1 = 0; + } +} +impl + AsMut<[T]>> FixedVec { + #[inline] + pub fn push(&mut self, item: T) { + self.try_push(item).unwrap_or_else(|_| { + panic!("at maximum capacity ({})", self.capacity()); + }); + } + #[inline] + pub fn try_push(&mut self, item: T) -> Result<(), T> { + if self.1 < self.capacity() { + self.0.as_mut()[self.1] = item; + self.1 += 1; + Ok(()) + } else { + Err(item) + } + } +} + +impl> Deref for FixedVec { + type Target = [T]; + #[inline] + fn deref(&self) -> &[T] { + &self.0.as_ref()[..self.1] + } +} + +impl + AsMut<[T]>> DerefMut for FixedVec { + #[inline] + fn deref_mut(&mut self) -> &mut [T] { + &mut self.0.as_mut()[..self.1] + } +} + +// +// Inherent impls +// + impl ClipPlane { /// Creates a clip plane given a normal, offset, and outcode bit. + #[inline] const fn new(x: f32, y: f32, z: f32, off: f32, bit: u8) -> Self { Self(ClipVec::new([x, y, z, -off]), bit) } @@ -167,7 +239,7 @@ impl ClipPlane { pub fn clip_simple_polygon( &self, verts_in: &[ClipVert], - verts_out: &mut Vec>, + verts_out: &mut SliceVec>, ) { let mut verts = verts_in.iter().chain(&verts_in[..1]); @@ -282,20 +354,20 @@ pub mod view_frustum { /// Communications of the ACM, vol. 17, pp. 32–42, 1974 pub fn clip_simple_polygon<'a, A: Lerp>( planes: &[ClipPlane], - verts_in: &'a mut Vec>, - verts_out: &'a mut Vec>, + mut verts_in: SliceVec<'a, ClipVert>, + mut verts_out: &mut SliceVec<'a, ClipVert>, ) { debug_assert!(verts_out.is_empty()); for (p, i) in zip(planes, 0..) { - p.clip_simple_polygon(verts_in, verts_out); + p.clip_simple_polygon(&verts_in, &mut verts_out); verts_in.clear(); - if verts_out.is_empty() { + if verts_out.len() == 0 { // Nothing left to clip; the polygon was fully outside break; } else if i < planes.len() - 1 { // Use the result of this iteration as the input of the next - swap(verts_in, verts_out); + swap(&mut verts_in, &mut verts_out); } } } @@ -347,30 +419,36 @@ impl Clip for [Edge>] { } } -impl Clip for [Tri>] { +impl Clip for [Tri>] { type Item = Tri>; fn clip(&self, planes: &[ClipPlane], out: &mut Vec) { debug_assert!(out.is_empty()); // Avoid unnecessary allocations by reusing these - let mut verts_in = Vec::with_capacity(10); - let mut verts_out = Vec::with_capacity(10); + let mut verts_in_a: [_; 10] = Default::default(); + let mut verts_out_a: [_; 10] = Default::default(); - for tri @ Tri(vs) in self { - match status(vs) { + for tri in self.iter().cloned() { + match status(&tri.0) { Status::Visible => { - out.push(tri.clone()); + out.push(tri); continue; } Status::Hidden => continue, Status::Clipped => { /* go on and clip */ } + }; + + let mut verts_in = FixedVec::from(verts_in_a.as_mut()); + let mut verts_out = FixedVec::from(verts_out_a.as_mut()); + + for v in tri.0 { + verts_in.push(v); } - verts_in.extend(vs.clone()); - clip_simple_polygon(planes, &mut verts_in, &mut verts_out); + clip_simple_polygon(planes, verts_in, &mut verts_out); - if let [p, rest @ ..] = &verts_out[..] { + if let [p, rest @ ..] = verts_out.deref() { // Clipping a triangle results in an n-gon, where n depends on // how many planes the triangle intersects. For example, here // clipping triangle ABC generated three new vertices, resulting @@ -399,6 +477,7 @@ impl Clip for [Tri>] { } } +#[cfg(false)] #[cfg(test)] mod tests { use alloc::vec; @@ -443,7 +522,7 @@ mod tests { // 32 16 8 4 2 1 // Outside near == 1 - assert_eq!(outcode(&vec(0.0, 0.0, -1.5)), 0b00_0_01); + assert_eq!(outcode(&vec(0.0, 0.0, -1.5)), 0b00_00_01); // Outside right == 8 assert_eq!(outcode(&vec(2.0, 0.0, 0.0)), 0b00_10_00); // Outside bottom == 16 @@ -701,7 +780,7 @@ mod tests { in_degen += is_degenerate(&tr) as u32; out_tris[res.len()] += 1; out_total += res.len(); - out_degen += res.iter().filter(|t| is_degenerate(t)).count() + out_degen += res.iter().filter(|t| is_degenerate(t)).count(); } #[cfg(feature = "std")] { diff --git a/core/src/render/ctx.rs b/core/src/render/ctx.rs index f3ed5fcb..49f3894b 100644 --- a/core/src/render/ctx.rs +++ b/core/src/render/ctx.rs @@ -94,8 +94,8 @@ impl Context { #[inline] pub fn face_cull(&self, is_backface: bool) -> bool { match self.face_cull { - Some(FaceCull::Back) if is_backface => true, - Some(FaceCull::Front) if !is_backface => true, + Some(FaceCull::Back) => is_backface, + Some(FaceCull::Front) => !is_backface, _ => false, } } diff --git a/core/src/render/debug.rs b/core/src/render/debug.rs index b1b6646a..fea2f437 100644 --- a/core/src/render/debug.rs +++ b/core/src/render/debug.rs @@ -60,7 +60,7 @@ pub fn dir_to_rgb(v: Vec3) -> Color4f { } /// Draws an illustration of a ray. -pub fn ray<'a, B>(o: Point3, dir: Vec3) -> DbgBatch { +pub fn ray(o: Point3, dir: Vec3) -> DbgBatch { let mut b = dir.cross(&Vec3::Y); if b.len_sqr() < 1e-6 { b = dir.cross(&Vec3::X); @@ -88,7 +88,7 @@ pub fn ray<'a, B>(o: Point3, dir: Vec3) -> DbgBatch { /// /// The ray originates from the triangle's centroid. pub fn face_normal( - tri: Tri>, + tri: &Tri>, ) -> DbgBatch { ray(tri.centroid(), tri.normal().to()) } diff --git a/core/src/render/prim.rs b/core/src/render/prim.rs index a510be9a..e540b924 100644 --- a/core/src/render/prim.rs +++ b/core/src/render/prim.rs @@ -9,30 +9,31 @@ use super::{ raster::{Scanline, ScreenPt, line, tri_fill}, }; -impl Render for Tri { +impl Render for Tri { type Clip = Tri>; type Clips = [Tri>]; type Screen = Tri>; + #[inline] fn inline(tri: Self, vs: &[ClipVert]) -> Tri> { tri.map(|i| vs[i].clone()) } - + #[inline] fn depth(Tri([a, b, c]): &Self::Clip) -> f32 { (a.pos.z() + b.pos.z() + c.pos.z()) / 3.0 } - + #[inline] fn is_backface(tri: &Self::Screen) -> bool { tri.winding() == Winding::Cw } - + #[inline] fn to_screen( clip: Tri>, tf: &Mat4, ) -> Self::Screen { Tri(to_screen(clip.0, tf)) } - + #[inline] fn rasterize)>(scr: Self::Screen, scanline_fn: F) { tri_fill(scr.0, scanline_fn); } @@ -45,26 +46,29 @@ impl Render for Edge { type Screen = Edge>; + #[inline] fn inline(Edge(i, j): Edge, vs: &[ClipVert]) -> Self::Clip { Edge(vs[i].clone(), vs[j].clone()) } - + #[inline] fn to_screen(e: Self::Clip, tf: &Mat4) -> Self::Screen { let [a, b] = to_screen([e.0, e.1], tf); Edge(a, b) } - + #[inline] fn rasterize)>(e: Self::Screen, scanline_fn: F) { line([e.0, e.1], scanline_fn); } } +/// A helper function to perform z-division and viewport transform for a list +/// of clip-space vertices. +#[inline] pub fn to_screen( vs: [ClipVert; N], tf: &Mat4, ) -> [Vertex; N] { vs.map(|v| { - let [x, y, _, w] = v.pos.0; // Perspective division (projection to the real plane) // // We use the screen-space z coordinate to store the reciprocal @@ -77,12 +81,14 @@ pub fn to_screen( // Vec3 was used here, which only worked because apply used to use // w=1 for vectors. Fixing it made viewport transform incorrect. // The z-div concept and trait likely need clarification. - let pos = pt3(x, y, 1.0).z_div(w); + let [x, y, _, w] = v.pos.0; + let recip_w = w.recip(); + let pos = pt3(x, y, 1.0).z_div_recip(recip_w); Vertex { // Viewport transform pos: tf.apply(&pos), // Perspective correction - attrib: v.attrib.z_div(w), + attrib: v.attrib.z_div_recip(recip_w), } }) } diff --git a/core/src/render/scene.rs b/core/src/render/scene.rs index 85edb5da..261e2251 100644 --- a/core/src/render/scene.rs +++ b/core/src/render/scene.rs @@ -26,6 +26,8 @@ impl Obj { pub fn new(geom: Mesh) -> Self { Self::with_transform(geom, Mat4::identity()) } + + #[must_use] pub fn with_transform(geom: Mesh, tf: Mat4) -> Self { let bbox = BBox::of(&geom); Self { geom, bbox, tf } diff --git a/core/src/render/shader.rs b/core/src/render/shader.rs index e67e3f99..b2560654 100644 --- a/core/src/render/shader.rs +++ b/core/src/render/shader.rs @@ -60,6 +60,7 @@ where { type Output = Out; + #[inline] fn shade_vertex(&self, vertex: In, uniform: Uni) -> Out { self(vertex, uniform) } @@ -70,6 +71,7 @@ where F: Fn(Frag) -> Out, Out: Into>, { + #[inline] fn shade_fragment(&self, frag: Frag) -> Option { self(frag).into() } @@ -111,6 +113,7 @@ where { type Output = Vs::Output; + #[inline] fn shade_vertex(&self, vertex: In, uniform: Uni) -> Self::Output { self.vertex_shader.shade_vertex(vertex, uniform) } @@ -120,6 +123,7 @@ impl FragmentShader for Shader where Fs: FragmentShader, { + #[inline] fn shade_fragment(&self, frag: Frag) -> Option { self.fragment_shader.shade_fragment(frag) } diff --git a/core/src/render/stats.rs b/core/src/render/stats.rs index 46f66b8f..598c6124 100644 --- a/core/src/render/stats.rs +++ b/core/src/render/stats.rs @@ -66,13 +66,11 @@ impl Stats { /// /// No-op if the timer was not running. This method is also no-op unless /// the `std` feature is enabled. + #[must_use] pub fn finish(self) -> Self { Self { #[cfg(feature = "std")] - time: self - .start - .map(|st| st.elapsed()) - .unwrap_or(self.time), + time: self.start.map_or(self.time, |st| st.elapsed()), ..self } } diff --git a/core/src/render/target.rs b/core/src/render/target.rs index bbd8397c..257edf03 100644 --- a/core/src/render/target.rs +++ b/core/src/render/target.rs @@ -58,6 +58,7 @@ impl AsMutSlice2 for Colorbuf { } impl Target for &mut T { + #[inline] fn rasterize>( &mut self, sl: Scanline, @@ -68,6 +69,7 @@ impl Target for &mut T { } } impl Target for &RefCell { + #[inline] fn rasterize>( &mut self, sl: Scanline, @@ -85,6 +87,7 @@ where Color4: IntoPixel, { /// Rasterizes `scanline` into this framebuffer. + #[inline] fn rasterize>( &mut self, sl: Scanline, @@ -103,6 +106,7 @@ where { /// Rasterizes `scanline` into this `u32` color buffer. /// Does no z-buffering. + #[inline] fn rasterize>( &mut self, sl: Scanline, @@ -114,6 +118,7 @@ where } impl Target for Buf2 { + #[inline] fn rasterize>( &mut self, sl: Scanline, @@ -125,6 +130,7 @@ impl Target for Buf2 { } impl Target for Buf2 { + #[inline] fn rasterize>( &mut self, sl: Scanline, diff --git a/core/src/render/tex.rs b/core/src/render/tex.rs index de668e40..762b0726 100644 --- a/core/src/render/tex.rs +++ b/core/src/render/tex.rs @@ -112,10 +112,12 @@ pub fn cube_map(pos: Vec3, dir: Normal3) -> TexCoord { impl TexCoord { /// Returns the u (horizontal) component of `self`. + #[inline] pub const fn u(&self) -> f32 { self.0[0] } /// Returns the v (vertical) component of `self`. + #[inline] pub const fn v(&self) -> f32 { self.0[1] } @@ -162,6 +164,7 @@ impl Atlas { /// # Panics /// If `i` is out of bounds. // TODO Improve error reporting + #[inline] pub fn get(&self, i: u32) -> Texture> { let [p0, p1] = self.rect(i); self.texture.data.slice(p0..p1).into() diff --git a/core/src/util/buf.rs b/core/src/util/buf.rs index 90e5fcaa..421308e0 100644 --- a/core/src/util/buf.rs +++ b/core/src/util/buf.rs @@ -152,6 +152,7 @@ impl Buf2 { /// /// # Panics /// If `w * h > isize::MAX`. + #[inline] pub fn new((w, h): Dims) -> Self where T: Default + Clone, @@ -200,11 +201,13 @@ impl Buf2 { } /// Returns a view of the backing data of `self`. + #[inline] pub fn data(&self) -> &[T] { self.0.data() } /// Returns a mutable view of the backing data of `self`. + #[inline] pub fn data_mut(&mut self) -> &mut [T] { self.0.data_mut() } @@ -249,6 +252,7 @@ impl<'a, T> Slice2<'a, T> { /// # Panics /// if `stride < width` or if the slice would overflow `data`. /// + #[inline] pub fn new(dims: Dims, stride: u32, data: &'a [T]) -> Self { Self(Inner::new(dims, stride, data)) } @@ -259,6 +263,7 @@ impl<'a, T> MutSlice2<'a, T> { /// and stride `stride`. /// /// See [`Slice2::new`] for more information. + #[inline] pub fn new(dims: Dims, stride: u32, data: &'a mut [T]) -> Self { Self(Inner::new(dims, stride, data)) } @@ -363,7 +368,7 @@ impl DerefMut for Buf2 { &mut self.0 } } -impl<'a, T> DerefMut for MutSlice2<'a, T> { +impl DerefMut for MutSlice2<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -422,11 +427,13 @@ pub mod inner { /// `Buf2` instances are always contiguous. A `Slice2` or `MutSlice2` /// instance is contiguous if its width equals its stride, if its /// height is 1, or if it is empty. + #[inline] pub fn is_contiguous(&self) -> bool { let (w, h) = self.dims; self.stride == w || h <= 1 || w == 0 } /// Returns whether `self` contains no elements. + #[inline] pub fn is_empty(&self) -> bool { self.dims.0 == 0 || self.dims.1 == 0 } @@ -457,6 +464,7 @@ pub mod inner { } /// Returns the dimensions and linear range corresponding to the rect. + #[inline(never)] fn resolve_bounds(&self, rect: &Rect) -> (Dims, Range) { let (w, h) = self.dims; @@ -487,6 +495,7 @@ pub mod inner { } /// A helper for implementing `Debug`. + #[inline(never)] pub(super) fn debug_fmt( &self, f: &mut Formatter, @@ -515,6 +524,7 @@ pub mod inner { impl> Inner { /// # Panics /// if `stride < w` or if the slice would overflow `data`. + #[inline] #[rustfmt::skip] pub(super) fn new(dims: Dims, stride: u32, data: D) -> Self { check_preconditions(dims, stride, data.len()); @@ -522,6 +532,7 @@ pub mod inner { } /// Returns the data of `self` as a linear slice. + #[inline] pub(super) fn data(&self) -> &[T] { &self.data } @@ -537,6 +548,7 @@ pub mod inner { /// /// # Panics /// If any part of `rect` is outside the bounds of `self`. + #[inline] pub fn slice(&self, rect: impl Into) -> Slice2<'_, T> { let (dims, rg) = self.resolve_bounds(&rect.into()); Slice2::new(dims, self.stride, &self.data[rg]) @@ -544,6 +556,7 @@ pub mod inner { /// Returns a reference to the element at `pos`, /// or `None` if `pos` is out of bounds. + #[inline] pub fn get(&self, pos: impl Into) -> Option<&T> { let [x, y] = pos.into().0; self.to_index_checked(x, y).map(|i| &self.data[i]) @@ -593,6 +606,7 @@ pub mod inner { } /// Returns the data of `self` as a single mutable slice. + #[inline] pub(super) fn data_mut(&mut self) -> &mut [T] { &mut self.data } @@ -664,6 +678,7 @@ pub mod inner { /// Returns a mutable reference to the element at `pos`, /// or `None` if `pos` is out of bounds. + #[inline] pub fn get_mut(&mut self, pos: impl Into) -> Option<&mut T> { let [x, y] = pos.into().0; self.to_index_checked(x, y) @@ -674,6 +689,7 @@ pub mod inner { /// /// # Panics /// If any part of `rect` is outside the bounds of `self`. + #[inline] pub fn slice_mut(&mut self, rect: impl Into) -> MutSlice2<'_, T> { let (dims, rg) = self.resolve_bounds(&rect.into()); MutSlice2(Inner::new(dims, self.stride, &mut self.data[rg])) diff --git a/core/src/util/pixfmt.rs b/core/src/util/pixfmt.rs index a6899303..5ebf1680 100644 --- a/core/src/util/pixfmt.rs +++ b/core/src/util/pixfmt.rs @@ -43,6 +43,7 @@ pub struct Rgba4444; // Impls for Color3 impl IntoPixel for Color3 { + #[inline] fn into_pixel(self) -> u32 { let [r, g, b] = self.0; // [0x00, 0xRR, 0xGG, 0xBB] -> 0x00_RR_GG_BB @@ -50,12 +51,14 @@ impl IntoPixel for Color3 { } } impl IntoPixel<[u8; 3], Rgb888> for Color3 { + #[inline] fn into_pixel(self) -> [u8; 3] { self.0 } } impl IntoPixel for Color3 { + #[inline] fn into_pixel(self) -> u16 { let [r, g, b] = self.0; (r as u16 >> 3 & 0x1F) << 11 @@ -65,6 +68,7 @@ impl IntoPixel for Color3 { } impl IntoPixel<[u8; 2], Rgb565> for Color3 { + #[inline] fn into_pixel(self) -> [u8; 2] { let c: u16 = self.into_pixel(); c.to_ne_bytes() @@ -77,6 +81,7 @@ impl IntoPixel for Color4 where Self: IntoPixel<[u8; 4], F>, { + #[inline] fn into_pixel(self) -> u32 { // From [0xAA, 0xBB, 0xCC, 0xDD] to 0xAA_BB_CC_DD -> big-endian! u32::from_be_bytes(self.into_pixel()) @@ -84,6 +89,7 @@ where } impl IntoPixel for Color4 { + #[inline] fn into_pixel(self) -> u32 { let [r, g, b, _] = self.0; // From [0x00, 0xRR, 0xGG, 0xBB] to 0x00_RR_GG_BB -> big-endian! @@ -91,34 +97,40 @@ impl IntoPixel for Color4 { } } impl IntoPixel<[u8; 4], Rgba8888> for Color4 { + #[inline] fn into_pixel(self) -> [u8; 4] { self.0 } } impl IntoPixel<[u8; 4], Argb8888> for Color4 { + #[inline] fn into_pixel(self) -> [u8; 4] { let [r, g, b, a] = self.0; [a, r, g, b] } } impl IntoPixel<[u8; 4], Bgra8888> for Color4 { + #[inline] fn into_pixel(self) -> [u8; 4] { let [r, g, b, a] = self.0; [b, g, r, a] } } impl IntoPixel<[u8; 3], Rgb888> for Color4 { + #[inline] fn into_pixel(self) -> [u8; 3] { [self.r(), self.g(), self.b()] } } impl IntoPixel<[u8; 2], Rgba4444> for Color4 { + #[inline] fn into_pixel(self) -> [u8; 2] { let c: u16 = self.into_pixel_fmt(Rgba4444); c.to_ne_bytes() } } impl IntoPixel for Color4 { + #[inline] fn into_pixel(self) -> u16 { let [r, g, b, a] = self.0; @@ -130,11 +142,13 @@ impl IntoPixel for Color4 { } } impl IntoPixel for Color4 { + #[inline] fn into_pixel(self) -> u16 { self.to_rgb().into_pixel() } } impl IntoPixel<[u8; 2], Rgb565> for Color4 { + #[inline] fn into_pixel(self) -> [u8; 2] { let c: u16 = self.into_pixel_fmt(Rgb565); c.to_ne_bytes() @@ -142,6 +156,7 @@ impl IntoPixel<[u8; 2], Rgb565> for Color4 { } #[cfg(test)] +#[allow(clippy::unusual_byte_groupings)] mod tests { use crate::math::{rgb, rgba}; diff --git a/core/src/util/pnm.rs b/core/src/util/pnm.rs index 346ca6fb..d7af3380 100644 --- a/core/src/util/pnm.rs +++ b/core/src/util/pnm.rs @@ -259,7 +259,7 @@ pub fn parse_pnm(input: impl IntoIterator) -> Result> { TextGraymap => (0..count) .map(|_| Ok(gray(parse_u16(&mut it)?.try_into()?))) .collect::>>()?, - _ => return Err(Unsupported((h.format as u16).to_be_bytes())), + TextBitmap => return Err(Unsupported((h.format as u16).to_be_bytes())), }; if data.len() < count as usize { diff --git a/core/src/util/rect.rs b/core/src/util/rect.rs index 0061452c..a5d856b4 100644 --- a/core/src/util/rect.rs +++ b/core/src/util/rect.rs @@ -71,10 +71,10 @@ impl Rect { /// Returns the horizontal and vertical extents of `self`. pub fn bounds(&self) -> [impl RangeBounds; 2] { - let left = self.left.map(Included).unwrap_or(Unbounded); - let top = self.top.map(Included).unwrap_or(Unbounded); - let right = self.right.map(Excluded).unwrap_or(Unbounded); - let bottom = self.bottom.map(Excluded).unwrap_or(Unbounded); + let left = self.left.map_or(Unbounded, Included); + let top = self.top.map_or(Unbounded, Included); + let right = self.right.map_or(Unbounded, Excluded); + let bottom = self.bottom.map_or(Unbounded, Excluded); [(left, right), (top, bottom)] } diff --git a/demos/src/bin/crates.rs b/demos/src/bin/crates.rs index 555659b3..b3801117 100644 --- a/demos/src/bin/crates.rs +++ b/demos/src/bin/crates.rs @@ -98,7 +98,7 @@ fn main() { // Floor { let Obj { bbox, tf, geom } = &floor; - let model_to_project = tf.then(&world_to_project); + let model_to_project = tf.then(world_to_project); if bbox.visibility(&model_to_project) != Hidden { let mut b = batch .clone() @@ -114,7 +114,7 @@ fn main() { for Obj { geom, bbox, tf } in &crates { frame.ctx.stats.borrow_mut().objs.i += 1; - let model_to_project = tf.then(&world_to_project); + let model_to_project = tf.then(world_to_project); // TODO Also if `Visible`, no further clipping or culling needed if bbox.visibility(&model_to_project) == Hidden { @@ -183,7 +183,7 @@ fn floor() -> Obj { bld.push_face(a, d, b); } else { bld.push_face(b, c, d); - bld.push_face(b, a, c) + bld.push_face(b, a, c); } } } diff --git a/demos/src/bin/curses.rs b/demos/src/bin/curses.rs index 05112783..eb1720e1 100644 --- a/demos/src/bin/curses.rs +++ b/demos/src/bin/curses.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unusual_byte_groupings)] + use std::time::Instant; use pancurses::*; @@ -126,8 +128,8 @@ impl Target for Win { let [r, g, b, _] = col.0.map(|c| c as u32); let col = (r & 0b111_000_00) - | (g / 9 & 0b000_111_00) - | (b / 85 & 0b000_000_11); + | ((g / 9) & 0b000_111_00) + | ((b / 85) & 0b000_000_11); // Avoid the eight standard colors self.0.addch(COLOR_PAIR(col.max(8) as chtype)); diff --git a/demos/src/bin/solids.rs b/demos/src/bin/solids.rs index 59d63504..8dace5fe 100644 --- a/demos/src/bin/solids.rs +++ b/demos/src/bin/solids.rs @@ -1,4 +1,5 @@ use core::ops::ControlFlow::Continue; +use std::env::args; use minifb::{Key, KeyRepeat}; @@ -22,6 +23,9 @@ struct Carousel { } impl Carousel { + fn new(idx: usize) -> Self { + Self { idx, ..Self::default() } + } fn start(&mut self) { if self.t.is_none() { self.t = Some(0.0); @@ -48,8 +52,6 @@ impl Carousel { } fn main() { - eprintln!("Press Space to cycle between objects..."); - let mut win = Window::builder() .title("retrofire//solids") .build() @@ -87,8 +89,14 @@ fn main() { let objects = objects_n(8); let translate = translate(-3.0 * Vec3::Z); - let mut carousel = Carousel::default(); + let idx = args().nth(1).map(|idx| idx.parse()); + let Ok(idx) = idx.unwrap_or(Ok(0)) else { + panic!("expected integer in 0..=13"); + }; + let mut carousel = Carousel::new(idx); + + eprintln!("Press Space to cycle between objects..."); win.run(|frame| { let Frame { t, dt, win, .. } = frame; diff --git a/front/src/minifb.rs b/front/src/minifb.rs index 9c8184b8..0ec0c42c 100644 --- a/front/src/minifb.rs +++ b/front/src/minifb.rs @@ -52,22 +52,26 @@ impl Default for Builder<'_> { impl<'t> Builder<'t> { /// Sets the width and height of the window. + #[must_use] pub fn dims(mut self, dims: Dims) -> Self { self.dims = dims; self } /// Sets the title of the window. + #[must_use] pub fn title(mut self, title: &'t str) -> Self { self.title = title; self } /// Sets the frame rate cap of the window. `None` means unlimited /// frame rate (the main loop runs as fast as possible). + #[must_use] pub fn target_fps(mut self, fps: Option) -> Self { self.target_fps = fps; self } /// Sets other `minifb` options. + #[must_use] pub fn options(mut self, opts: WindowOptions) -> Self { self.opts = opts; self diff --git a/front/src/sdl2.rs b/front/src/sdl2.rs index 165b5d5b..9c57d50e 100644 --- a/front/src/sdl2.rs +++ b/front/src/sdl2.rs @@ -26,6 +26,7 @@ pub trait PixelFmt: Copy + Default { type Pixel: AsRef<[u8]> + Copy + Sized; const SDL_FMT: PixelFormatEnum; + #[inline] fn encode>(self, color: C) -> Self::Pixel { color.into_pixel_fmt(self) } @@ -69,11 +70,13 @@ pub type Framebuf<'a, Pix, Fmt> = impl<'t, PF: PixelFmt> Builder<'t, PF> { /// Sets the width and height of the window, in pixels. + #[must_use] pub fn dims(mut self, dims: Dims) -> Self { self.dims = dims; self } /// Sets the title of the window. + #[must_use] pub fn title(mut self, title: &'t str) -> Self { self.title = title; self @@ -81,18 +84,21 @@ impl<'t, PF: PixelFmt> Builder<'t, PF> { /// Sets whether vertical sync is enabled. /// /// If true, the frame rate is synced to the monitor's refresh rate. + #[must_use] pub fn vsync(mut self, enabled: bool) -> Self { self.vsync = enabled; self } - /// Sets whether high-dpi + /// Sets whether high-dpi TODO /// /// If true, the physical resolution may be higher than the logical resolution. + #[must_use] pub fn high_dpi(mut self, enabled: bool) -> Self { self.hidpi = enabled; self } /// Sets the fullscreen state of the window. + #[must_use] pub fn fullscreen(mut self, fs: FullscreenType) -> Self { self.fs = fs; self @@ -100,6 +106,7 @@ impl<'t, PF: PixelFmt> Builder<'t, PF> { /// Sets the framebuffer pixel format. /// /// Supported formats are [`Rgba8888`], [`Rgb565`], and [`Rgba4444`]. + #[must_use] pub fn pixel_fmt(mut self, fmt: PF) -> Self { self.pixfmt = fmt; self diff --git a/geom/src/isect.rs b/geom/src/isect.rs index 12eb7c1a..19ab2ad7 100644 --- a/geom/src/isect.rs +++ b/geom/src/isect.rs @@ -1,15 +1,14 @@ use core::fmt::{Debug, Formatter}; +#[cfg(feature = "std")] +use retrofire_core::geom::Sphere; use retrofire_core::{ - geom::{Edge, Line2, Plane3, Ray, Ray2, Ray3}, + geom::{Edge, Line2, Plane3, Polygon, Ray, Ray2, Ray3}, mat, math::{ApproxEq, Mat2, Point2, Point3, pt2, vec3}, render::scene::BBox, }; -#[cfg(feature = "std")] -use retrofire_core::geom::Sphere; - /// Trait for finding intersection points of geometric objects. pub trait Intersect { /// The result of an intersection test. @@ -55,7 +54,7 @@ impl LineIntersect { impl Debug for LineIntersect { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { - Self::Point(p) => write!(f, "Point({:?})", p), + Self::Point(p) => write!(f, "Point({p:?})"), Self::Coincident => f.write_str("Coincident"), } } @@ -173,8 +172,8 @@ impl Intersect> for Ray3 { let low = (low - orig) * r_d; let upp = (upp - orig) * r_d; - let near = low.zip_map(upp, |l, u| l.min(u)); - let far = low.zip_map(upp, |l, u| l.max(u)); + let near = low.zip_map(upp, f32::min); + let far = low.zip_map(upp, f32::max); let near_t = near[0].max(near[1]).max(near[2]); let far_t = far[0].min(far[1]).min(far[2]); @@ -497,12 +496,40 @@ impl Intersect for Edge> { } } +impl Intersect>> for Ray2 { + type Result = RayIntersect2; + + fn intersect(&self, poly: &Polygon>) -> Self::Result { + let mut res = None; + for Edge(a, b) in poly.edges() { + let ip = self.intersect(&Edge(*a, *b)); + match (res, ip) { + (None, Some(ip)) => res = Some(ip), + (Some(curr), Some(new)) if curr.0 > new.0 => res = Some(new), + _ => continue, + } + } + res + } +} + #[cfg(test)] mod tests { - use retrofire_core::math::{Linear, Vec3, pt3}; + use retrofire_core::math::{Linear, Vec3, pt3, vec2}; use super::*; + #[test] + fn ray_polygon() { + let ray: Ray2 = Ray(pt2(-1.0, 0.0), vec2(1.0, 0.0)); + + let poly = Polygon::new([pt2(0.0, -1.0), pt2(2.0, 0.0), pt2(2.0, 1.0)]); + + let ip = ray.intersect(&poly); + + assert_eq!(ip, Some((1.0, pt2(-0.0, 0.0)))); + } + mod ray_plane { use super::*; diff --git a/geom/src/solids/lathe.rs b/geom/src/solids/lathe.rs index 534488c5..a681ae9b 100644 --- a/geom/src/solids/lathe.rs +++ b/geom/src/solids/lathe.rs @@ -78,6 +78,10 @@ pub struct Capsule { // impl>> Lathe

{ + /// TODO + /// + /// # Panics + /// If `sectors` < 3. pub fn new(points: P, sectors: u32, segments: u32) -> Self { assert!(sectors >= 3, "sectors must be at least 3, was {sectors}"); Self { @@ -89,12 +93,14 @@ impl>> Lathe

{ } } + #[must_use] pub fn capped(self, capped: bool) -> Self { Self { capped, ..self } } /// Builds the lathe mesh. #[inline(never)] + #[must_use] pub fn build_with( self, f: &mut dyn FnMut(Point3, Normal3, TexCoord) -> Vertex3,