ultraviolet-0.10.0/.cargo_vcs_info.json0000644000000001360000000000100134640ustar { "git": { "sha1": "da30e84ecf312308a431d35fa2ae147f82f528a4" }, "path_in_vcs": "" }ultraviolet-0.10.0/.github/FUNDING.yml000064400000000000000000000001121046102023000154230ustar 00000000000000# These are supported funding model platforms github: termhn ko_fi: gray ultraviolet-0.10.0/.github/workflows/ci.yaml000064400000000000000000000030511046102023000171270ustar 00000000000000name: CI on: push: branches: - main tags: - "*" pull_request: concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: lint: name: Lint runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: "clippy, rustfmt" - uses: Swatinem/rust-cache@v2 # make sure all code has been formatted with rustfmt and linted with clippy - name: rustfmt run: cargo fmt -- --check --color always # run clippy to verify we have no warnings - run: cargo fetch - name: cargo clippy run: cargo clippy --all-targets --all-features -- -D warnings test: name: Test strategy: matrix: os: [ubuntu-22.04, macos-14] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 with: submodules: true - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo fetch - name: cargo test build run: cargo build --tests - run: cargo test publish-check: name: Publish Check runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo fetch - name: cargo publish run: cargo publish --dry-run test_success: runs-on: ubuntu-22.04 needs: [lint, test, publish-check] steps: - run: echo "All test jobs passed"ultraviolet-0.10.0/.gitignore000064400000000000000000000000471046102023000142450ustar 00000000000000/target **/*.rs.bk Cargo.lock /.vscode ultraviolet-0.10.0/CHANGELOG.md000064400000000000000000000075651046102023000141020ustar 00000000000000# Changelog ## Unreleased ## 0.10.0 ## 0.10.0 - Update `projection` module's and submodules' documentation - Remove `rh_ydown` projection module as it was very unlikely to be useful and caused confusion. - Add `mint` type conversions for integer vectors - Implement Serialize and Deserialize for `Similarity` - Implement Serialize and Deserialize for f64 types: `DBivec`, `DRotor`, `DIsometry`, `DSimilarity` - Add type conversion between `mint` quaternion and `Rotor3` ## 0.9.2 - Add `num_traits::identities` support behind a `num-traits` feature flag - Fix inverse for similarities - Fix missing `mut` in `Mat4::as_mut_array` result ## 0.9.1 - Fix `from_euler_angles` on `Mat4` - Update documentation on matrix rotation constructors - Update some documentation and improve implementation on `as_array` methods on various types ## 0.9.0 - Add methods to scale `Rotor3` - Implement `Neg` for all `IVec` types - Relax bound in `map` and `apply` from `Fn` to `FnMut` - Make matrix serialization and deserialization implementations match - Update `wide` to `0.7.x` ## 0.8.1 - Implement Serialize and Deserialize for all `UVec`,`IVec`, `DVec`, `DMat` types (under `serde`, `int` and `f64` feature flags) - Implement conversions between integer and float vectors. - Add `#[must_use]` attributes on `.normalized` methods to help prevent silent logic bugs when `.normalize()` would have been more appropriate. - Build docs.rs docs with all features. ## 0.8.0 - Update `wide` to `0.6.x` - Add `Rotor3::into_angle_plane()` - Add `Rotor3::into_quaternion_array` and `Rotor3::from_quaternion_array` - Implement Serialize and Deserialize for `Isometry2` and `Isometry3` (under `serde` feature flag) - Added `const` to `new` functions for integer vectors. - Add `Mat4::truncate()` - Add `Mul` and `Add` for isometries and similarities ## 0.7.5 - Add `Mat4::extract_translation`, `Mat4::extract_rotation` and `Mat4::into_isometry`. - Add missing `PartialEq` implementations for all matrices, transformations, vectors, bivectors and rotors, including SIMD and `f64` variants - Fix `Rotor2::rotate_vec` and corresponding derivation. ## 0.7.4 - Add optional bytemuck support ## 0.7.3 - Fix integer types not compiling properly. ## 0.7.2 - Implement Serialize and Deserialize for bivectors and rotors (under `serde` feature flag) ## 0.7.1 - Fix typo in `Mat3::inverse` implementation which made it transpose instead ## 0.7.0 - Add Mat3 into Rotor3 conversion for rotation matrices - Remove heavy reliance on `mul_add` due to negligible performance benefit and in many cases performance detriment. - Slightly optimize Vector `normalize`. - `Rotor2::from_angle_plane()` now takes plane and angle as separate arguments. - Add `MatN::adjugate()`. - `Mat3::from_nonuniform_scale_homogeneous()` now takes a `Vec2` instead of a `Vec3`. ## 0.6.1 - Add scalar multiplication and componentwise addition for `MatN` ## 0.6 - Significantly improve performance of Rotors and transform types (Isometry, Similarity) - Add `Rotor3::rotate_vecs()` for improved performance on rotating multiple vecs with the same rotor - Add support for f64/double precision floats under `f64` feature. Naming convention is `D[TypeName]` for the f64 versions. - Rename `W[TypeName]` to `[TypeName]x4`, allowing room for `[TypeName]x8`. - Add support for 256 bit AVX vectors. - Add support for `mint` for scalar types - Add `wgpu`-specfic notes to `projection` module (adds `_wgpu` to some function names) - Add spherical linear interpolation and better docs around interpolation - Rename `[WideType]::merge()` to `[WideType]::blend()` - Add `Into for Vec2xN` implementations - Fix some doc comments not appearing properly on Vec and Mat types. - Make most initializers `const` - Various performance improvements, especially for Rotor-transform-vector and some matrix operations - Add `MatN::determinant()` - Add `Mat2::inverse()` ultraviolet-0.10.0/Cargo.lock0000644000000035770000000000100114530ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bytemuck" version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" [[package]] name = "mint" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "safe_arch" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529" dependencies = [ "bytemuck", ] [[package]] name = "serde" version = "1.0.157" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707de5fcf5df2b5788fca98dd7eab490bc2fd9b7ef1404defc462833b83f25ca" [[package]] name = "serde_test" version = "1.0.157" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4231c6fab29d02b3cc705db3422aa36f7d23f1e1c096c947c8b8816d7c43dd45" dependencies = [ "serde", ] [[package]] name = "ultraviolet" version = "0.10.0" dependencies = [ "bytemuck", "mint", "num-traits", "serde", "serde_test", "wide", ] [[package]] name = "wide" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b689b6c49d6549434bf944e6b0f39238cf63693cb7a147e9d887507fffa3b223" dependencies = [ "bytemuck", "safe_arch", ] ultraviolet-0.10.0/Cargo.toml0000644000000026620000000000100114700ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "ultraviolet" version = "0.10.0" authors = ["Gray Olson "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A crate to do linear algebra, fast." readme = "README.md" keywords = [ "simd", "wide", "graphics", "math", "linear-algebra", ] license = "MIT OR Apache-2.0 OR Zlib" repository = "https://github.com/termhn/ultraviolet" [package.metadata.docs.rs] features = [ "f64", "int", "serde", "mint", "bytemuck", ] [features] default = [] f64 = [] int = [] [lib] name = "ultraviolet" path = "src/lib.rs" [dependencies.bytemuck] version = "1.22" optional = true [dependencies.mint] version = "0.5" optional = true [dependencies.num-traits] version = "0.2" optional = true [dependencies.serde] version = "1.0" features = [] optional = true [dependencies.wide] version = "0.7" [dev-dependencies.serde_test] version = "1.0" ultraviolet-0.10.0/Cargo.toml.orig000064400000000000000000000014411046102023000151430ustar 00000000000000[package] name = "ultraviolet" version = "0.10.0" authors = ["Gray Olson "] edition = "2018" description = "A crate to do linear algebra, fast." repository = "https://github.com/termhn/ultraviolet" readme = "README.md" keywords = ["simd", "wide", "graphics", "math", "linear-algebra"] license = "MIT OR Apache-2.0 OR Zlib" [package.metadata.docs.rs] features = ["f64", "int", "serde", "mint", "bytemuck"] [dependencies] # wide = { path = "../wide", optional = true } wide = { version = "0.7" } serde = { version = "1.0", features = [], optional = true } mint = { version = "0.5", optional = true } bytemuck = { version = "1.22", optional = true } num-traits = { version = "0.2", optional = true } [features] default = [] f64 = [] int = [] [dev-dependencies] serde_test = "1.0" ultraviolet-0.10.0/README.md000064400000000000000000000430301046102023000135330ustar 00000000000000[![crates.io](https://img.shields.io/crates/v/ultraviolet.svg)](https://crates.io/crates/ultraviolet) [![docs.rs](https://docs.rs/ultraviolet/badge.svg)](https://docs.rs/ultraviolet) # `ultraviolet` This is a crate to computer-graphics and games-related linear and geometric algebra, but *fast*, both in terms of productivity and in terms of runtime performance. In terms of productivity, ultraviolet uses no generics and is designed to be as straightforward of an interface as possible, resulting in fast compilation times and clear code. In addition, the lack of generics and Rust type-system "hacks" result in clear and concise errors that are easy to parse and fix for the user. In terms of runtime performance, ultraviolet was designed from the start with performance in mind. To do so, we provide two separate kinds of each type, each with nearly identical functionality, one with usual scalar f32 values, and the other a 'wide' type which uses SIMD f32x4 vectors for each value. This design is clear and explicit in intent, and it also allows code to take full advantage of SIMD. The 'wide' types use an "SoA" (Structure of Arrays) architecture such that each wide data structure actually contains the data for 4 or 8 of its associated data type and will do any operation on all of the simd 'lanes' at the same time. For example, a `Vec3x8` is equivalent to 8 `Vec3`s all bundled together into one data structure. Doing this is potentially *much* (factor of 10) faster than an standard "AoS" (Array of Structs) layout, though it does depend on your workload and algorithm requirements. Algorithms must be carefully architected to take full advantage of this, and doing so can be easier said than done, especially if your algorithm involves significant branching. `ultraviolet` was the first Rust math library to be designed in this "AoSoA" manner, though `nalgebra` now supports it for several of their data structures as well. ## Benchmarks See [`mathbench-rs`](https://github.com/bitshifter/mathbench-rs) for latest benchmarks (may not be fully up-to-date with git master). ## Cargo Features To help further improve build times, `ultraviolet` puts various functionality under feature flags. For example, the 2d and 3d projective geometric algebras as well as f64 and integer types are disabled by default. In order to enable them, enable the corresponding crate feature flags in your `Cargo.toml`. For example: ```toml [dependencies] ultraviolet = { version = "0.9", features = [ "f64", "int" ] } ``` Will enable the `f64` and `int` features. Here's a list of the available features: * `f64` – Enable `f64` bit wide floating point support. Naming convention is `D[Type]`, such as `DVec3x4` would be a collection of 4 3d vectors with `f64` precision each. * `int` – Enable integer vector types. * `bytemuck` – Enable casting of many types to byte arrays, for use with graphics APIs. * `mint` – Enable interoperation with other math crates through the `mint` interface. * `num-traits` – Enable [identity traits](https://docs.rs/num-traits/latest/num_traits/identities/index.html) for interoperation with other math crates. * `serde` – Enable `Serialize` and `Deserialize` implementations for many scalar types. ## Crate Features This crate is currently being dogfooded in my ray tracer [`rayn`](https://github.com/termhn/rayn), and is being used by various independent Rust game developers for various projects. It does what those users have currently needed it to do. There are a couple relatively unique/novel features in this library, the most important being the use of the Geometric Algebra. Instead of implementing complex number algebra (for 2d rotations) and Quaternion algebra (for 3d rotations), we use Rotors, a concept taken from Geometric Algebra, to represent 2d and 3d rotations. What this means for the programmer is that you will be using the `Rotor3` type in place of a Quaternion, though you can expect it to do basically all the same things that a Quaternion does. In fact, Quaternions are directly isomorphic to Rotors (meaning they are in essense the same thing, just formulated differently). The reason this decision was made was twofold: first, the derivation of the math is actually quite simple to understand. All the derivations for the code implemented in the Rotor structs in this library are written out in the `derivations` folder of the GitHub repo; I derived them manually as part of the implementation. On the other hand, Quaternions are often basically just seen as black boxes that we programmers use to do rotations because they have some nice properties, but that we don't really understand. You can use Rotors this same way, but you can also easily understand them. Second is that in some sense they can be seen as 'more correct' than Quaternions. Specifically, they facilitate a more proper understanding of rotation as being something that occurs *within a plane* rather than something that occurs *around an axis*, as it is generally thought. Finally, Rotors also generalize to 4 and even higher dimensions, and if someone wants to they could implement a Rotor4 which retains all the properties of a Rotor3/Quaternion but does rotation in 4 dimensions instead, something which simply is not possible to do with Quaternions. If it's missing something you need it to do, bug me on the [GitHub issue tracker](https://github.com/termhn/ultraviolet/issues) and/or Rust community discord server (I'm Fusha there) and I'll try to add it for you, if I believe it fits with the vision of the lib :) ## Examples ### Euler Integration [Euler Integration](https://en.wikipedia.org/wiki/Euler_method) is a method for numerically solving ordinary differential equations. If that sounds complicated, don't worry! The details of the method don't matter if you're not looking to implement any kind of physics simulation but this method is common in games. Keep reading for the code below! The point is that if you are doing the same basic math operations on multiple floating point values with no conditionals (no `if`s), porting to wide data types and parallel processing is quite simple. Here is the scalar example of Euler Integration: ```rust fn integrate( pos: &mut [uv::Vec3], vel: &mut [uv::Vec3], acc: &[uv::Vec3], dt: f32, ) { for ((position, velocity), acceleration) in pos.iter_mut().zip(vel).zip(acc) { *velocity = *velocity + *acceleration * dt; *position = *position + *velocity * dt; } } ``` The code loops over each set of corresponding position, velocity, and acceleration vectors. It first adjusts the velocity by the acceleration scaled by the amount of time that has passed and then adjusts the position by the velocity scaled by the amount of time that has passed. These are all multiplication, addition, and assignment operators that need to be applied in the same way to all of the variables in question. To port this function to wide data types and parallel processing, all we have to do is change the data types and we're done! The new function looks like this: ```rust fn integrate_x8( pos: &mut [uv::Vec3x8], vel: &mut [uv::Vec3x8], acc: &[uv::Vec3x8], dt: f32x8, ) { for ((position, velocity), acceleration) in pos.iter_mut().zip(vel).zip(acc) { *velocity = *velocity + *acceleration * dt; *position = *position + *velocity * dt; } } ``` This function now processes 8 sets of vectors in parallel and brings significant speed gains! The only caveat is that the calling code that creates the slices of vectors needs to be modified to populate these wide data types with 8 sets of values instead of just one. The scalar code for that looks like this: ```rust let mut pos: Vec = Vec::with_capacity(100); let mut vel: Vec = Vec::with_capacity(100); let mut acc: Vec = Vec::with_capacity(100); // You would probably write these constant values in-line but // they are here for illustrative purposes let pos_x = 1.0f32; let pos_y = 2.0f32; let pos_z = 3.0f32; let vel_x = 4.0f32; let vel_y = 5.0f32; let vel_z = 6.0f32; let acc_x = 7.0f32; let acc_y = 8.0f32; let acc_z = 9.0f32; for ((position, velocity), acceleration) in pos.iter_mut().zip(vel).zip(acc) { pos.push(uv::Vec3::new(pos_x, pos_y, pos_z)); vel.push(uv::Vec3::new(vel_x, vel_y, vel_z)); acc.push(uv::Vec3::new(acc_x, acc_y, acc_z)); } ``` Whereas to populate the same for the 8-lane wide `Vec3x8` data type, the code could look like this: ```rust let mut pos: Vec = Vec::with_capacity(100 / 8 + 1); let mut vel: Vec = Vec::with_capacity(100 / 8 + 1); let mut acc: Vec = Vec::with_capacity(100 / 8 + 1); let pos_x = uv::f32x8::splat(1.0f32); let pos_y = uv::f32x8::splat(2.0f32); let pos_z = uv::f32x8::splat(3.0f32); let vel_x = uv::f32x8::splat(4.0f32); let vel_y = uv::f32x8::splat(5.0f32); let vel_z = uv::f32x8::splat(6.0f32); let acc_x = uv::f32x8::splat(7.0f32); let acc_y = uv::f32x8::splat(8.0f32); let acc_z = uv::f32x8::splat(9.0f32); for ((position, velocity), acceleration) in pos.iter_mut().zip(vel).zip(acc) { pos.push(uv::Vec3x8::new(pos_x, pos_y, pos_z)); vel.push(uv::Vec3x8::new(vel_x, vel_y, vel_z)); acc.push(uv::Vec3x8::new(acc_x, acc_y, acc_z)); } ``` Note that `100 / 8` in maths terms would be `12.5`, but we can't conveniently have a half-sized `Vec3x8`. There are various ways to handle these 'remainder' vectors. You could fall back to scalar code, or progressively fall back to narrower wide types, such as `Vec3x4`, or you can just consider whether the cost of calculating a few additional vectors that you won't use is worth adding complexity to your code. ### Ray-Sphere Intersection Scalar code that operates on a single value at a time needs some restructuring to take advantage of SIMD and the 4-/8-wide data types. Below is an example of scalar ray-sphere instersection code using `Vec3` for points and vectors: ```rust fn ray_sphere_intersect( ray_o: uv::Vec3, ray_d: uv::Vec3, sphere_o: uv::Vec3, sphere_r_sq: f32, ) -> f32 { let oc = ray_o - sphere_o; let b = oc.dot(ray_d); let c = oc.mag_sq() - sphere_r_sq; let descrim = b * b - c; if descrim > 0.0 { let desc_sqrt = descrim.sqrt(); let t1 = -b - desc_sqrt; if t1 > 0.0 { t1 } else { let t2 = -b + desc_sqrt; if t2 > 0.0 { t2 } else { f32::MAX } } } else { f32::MAX } } ``` This porting guide will not discuss the details of the algorithm, but will focus on how to convert the code to apply parallel SIMD operations on wide data types. The first thing to do is to convert the parameter and return types from scalar `Vec3` to wide `Vec3x8` and `f32x8`: ```rust fn ray_sphere_intersect_x8( ray_o: uv::Vec3x8, ray_d: uv::Vec3x8, sphere_o: uv::Vec3x8, sphere_r_sq: uv::f32x8, ) -> uv::f32x8 { ``` Each call to the function will process 8 ray-sphere intersections in parallel. The first four lines of the function remain the same: ```rust let oc = ray_o - sphere_o; let b = oc.dot(ray_d); let c = oc.mag_sq() - sphere_r_sq; let descrim = b * b - c; ``` Despite this code being the same, the calculations for 8 rays and spheres will be carried out at the same time! The next line of the scalar code tests the value of `descrim` to see if it is greater than `0.0`. When operating on 8 values at a time, the code cannot branch along two separate paths because the value of `descrim` for each of the 8 values may cause branching to different sets of operations. To support this we would need to convert back to scalar code and then we lose all the performance benefits of parallel processing. So, how do we convert this? We have a tradeoff to consider depending on the frequency of divergence, that is depending on how often the branch will follow one or the other path. If it is very likely for the given data and algorithm that the majority of branches will take one path, we can check whether all lanes take that path and then branch based on that. Such a bias toward one branch path is relatively rare, and in the case of this algorithm it is common to branch either way so this approach would produce slower code. Another approach is to calculate the results for both branches for all 8 lanes, and then filter the results with masks that select the correct values from the possibilities at the end. To create the mask for 8 lanes of `descrim` values with `0.0`: ```rust let desc_pos = descrim.cmp_gt(uv::f32x8::splat(0.0)); ``` In the true case of the original scalar version, we then have more arithmetic operations that end up looking the exact same when we do them on the vectorized version: ```rust let desc_sqrt = descrim.sqrt(); let t1 = -b - desc_sqrt; ``` And now in the scalar code we have another branch based on `t1 > 0.0`, so we apply the same technique, with a little bit extra: ```rust let t1_valid = t1.cmp_gt(uv::f32x8::splat(0.0)) & desc_pos; ``` The `& desc_pos` at the end does a bitwise and operation to combine the masks that say whether each of the lanes of `t1 > 0.0` are true or false, with those of whether each of the lanes of `descrim > 0.0` were true or false, and if both are true for a lane, then the mask value will be true for that lane in `t1_mask`, otherwise the value for the lane will be `false`. This is combining the nested logic. The true case of the `t1 > 0.0` condition just returns `t1`, but the false case has some more calculation and branching that can be ported in a similar way: ```rust let t2 = -b + desc_sqrt; let t2_valid = t2.cmp_gt(uv::f32x8::splat(0.0)) & desc_pos; ``` This may sound like it could be slower than scalar code because this algorithm being applied to wide data types is doing all the calculations for both branches regardless of which is true, and you would be right! This approach is indeed a tradeoff and depends on the likelihood of branching one way or the other, and the cost of calculation of the branches. However, even with an algorithm that is particularly branch-heavy like the ray-sphere intersection we're analyzing here, in practice, the benefits of being able to calculate multiple pieces of data simultaneously often results in a net win! As with all optimization, measurement tells the truth. At this point, we have ported almost the entire algorithm. We have values for `t1` and `t2` for each of the 8 lanes. We have mask values in `t1_valid` that indicate whether both `descrim > 0.0 && t1 > 0.0` for each lane. And we have `t2_valid` with values indicating exactly `descrim > 0.0 && t2 > 0.0`. When the scalar code does not return `t1` or `t2`, it returns `f32::MAX`. How do we now select the correct return value for each of the lanes? `ultraviolet` has a `blend` function on the mask types that uses the true or false values for each of the lanes to select from the calculated values for the true and false cases. So if `a` were a wide vector of values that would be calculated in the true case of a branch, and `b` were for the false case, with a mask `m` we could select from `a` and `b` based on `m` by calling `m.blend(a, b)` and the result would be the desired output values! Let's try to apply that to the scalar code by looking just at its logical control flow: ```rust if descrim > 0.0 { if t1 > 0.0 { t1 } else { if t2 > 0.0 { t2 } else { f32::MAX } } } else { f32::MAX } ``` So if we take the outer-most if condition.. ```rust let t = t1_valid.blend(t1, ???); ``` What is the value for false case of the `descrim > 0.0 && t1 > 0.0` test? There are two possibilities - either `descrim <= 0.0`, which is the false case of the `descrim > 0.0` condition, or `descrim > 0.0 && t1 <= 0.0` which is the else case where we handle `t2`. This looks complicated. Let's try looking at the `descrim > 0.0 && t2 > 0.0` case in the scalar code and try `blend`ing that: ```rust let t = t2_valid.blend(t2, uv::f32x8::splat(std::f32::MAX)); ``` So `descrim > 0.0 && t2 > 0.0` has two false cases, either `descrim <= 0.0` and we want to return `f32::MAX`, or `descrim > 0.0 && t2 <= 0.0` and we want to return `f32::MAX`, so we can `blend` to select the correct values here to cover the false case of the scalar `descrim > 0.0` condition, and the false case of the `t1 > 0.0` condition, that leaves only the true case of the `t1 > 0.0` condition left to resolve... And that is exactly what `t1_valid.blend(t1, ???)` would select! So we can combine the two blends like this: ```rust let t = t2_valid.blend(t2, uv::f32x8::splat(std::f32::MAX)); let t = t1_valid.blend(t1, t); ``` `t` now contains `t1`, `t2` or `f32::MAX` as appropriate for each of the lanes! We have completed the port of the scalar algorithm code to leverage SIMD operations on 8-lane wide data types to calculate 8 ray-sphere intersections in parallel! Below is the full example of the same ray-sphere intersection algorithm implemented using the wide `Vec3x8` type: ```rust fn ray_sphere_intersect_x8( sphere_o: uv::Vec3x8, sphere_r_sq: uv::f32x8, ray_o: uv::Vec3x8, ray_d: uv::Vec3x8, ) -> uv::f32x8 { let oc = ray_o - sphere_o; let b = oc.dot(ray_d); let c = oc.mag_sq() - sphere_r_sq; let descrim = b * b - c; let desc_pos = descrim.cmp_gt(uv::f32x8::splat(0.0)); let desc_sqrt = descrim.sqrt(); let t1 = -b - desc_sqrt; let t1_valid = t1.cmp_gt(uv::f32x8::splat(0.0)) & desc_pos; let t2 = -b + desc_sqrt; let t2_valid = t2.cmp_gt(uv::f32x8::splat(0.0)) & desc_pos; let t = t2_valid.blend(t2, uv::f32x8::splat(std::f32::MAX)); let t = t1_valid.blend(t1, t); t } ``` ultraviolet-0.10.0/derivations/rotor2_rotate_rotor_derivation.txt000064400000000000000000000016341046102023000236260ustar 00000000000000E1, E2 = basis vectors, geometric multiplication rules: given u and v are basis vectors, uu = 1 uv = -vu want to compute: a b (rotor multiplication, i.e. composition) and a b a* (rotor rotation) where a, b are rotors, a* is reverse of a a = Sa + BaxyE1E2 b = Sb + BbxyE1E2 a* = Sa - BaxyE1E2 ------------- a b = (Sa + BaxyE1E2)(Sb + BbxyE1E2) = SaSb + SaBbxyE1E2 + SbBaxyE1E2 + BaxyBbxyE1E2E1E2 = SaSb - BaxyBbxy + (SaBbxy + SbBaxy)E1E2 ------------- a b a* = (a b)(Sa - BaxyE1E2) = (SaSb - BaxyBbxy + SaBbxyE1E2 + SbBaxyE1E2)(Sa - BaxyE1E2) = SaSaSb - SaSbBaxyE1E2 - SaBaxyBbxy + BaxyBaxyBbxyE1E2 + SaSaBbxyE1E2 - SaBaxyBbxyE1E2E1E2 + SaSbBaxyE1E2 - SbBaxyBaxyE1E2E1E2 = Sa^2Sb - SaBaxyBbxy + SbBaxyBbxy + SbBaxy^2 + (Baxy^2Bbxy - SaSbBaxy + Sa^2Bbxy + SaSbBaxy)E1E2 = (SaSb - BaxyBbxy)Sa + (BaxyBbxy + Baxy^2)Sb + (Baxy^2Bbxy + Sa^2Bbxy)E1E2 = (Sb - Sa)BaxyBbxy + (Sa^2 + Baxy^2)Sb + ((Baxy^2 + Sa^2)Bbxy)E1E1ultraviolet-0.10.0/derivations/rotor2_rotate_vec_derivation.txt000064400000000000000000000024521046102023000232350ustar 00000000000000E1, E2 = basis vectors, geometric multiplication rules: given u and v are basis vectors, uu = 1 uv = -vu want to compute: ab v ba (geometric product) where ab is a rotor ab = s + BxyE1E2 v = VxE1 + VyE2 ba = s - BxyE1E2 ------------- ab v = (s + BxyE1E2)(VxE1 + VyE2) = SVxE1 + SVyE2 + BxyVxE1E2E1 + BxyVyE1E2E2 = SVxE1 + SVyE2 - BxyVxE2 + BxyVyE1 = (SVx + BxyVy)E1 + (SVy - BxyVx)E2 Fx Fy factored version (for rotor rotate vec) ------------- ab v ba = (ab v)(S - BxyE1E2) =(FxE1 + FyE2)(S - BxyE1E2) = SFxE1 + SFyE2 - BxyFxE1E1E2 - BxyFyE2E1E2 = (SFx + BxyFy)E1 + (SFy - BxyFx)E2 non-factored version (for conversion from rotor to mat3) ------------- ab v ba = (ab v)(S - BxyE1E2) = (SVxE1 + SVyE2 - BxyVxE2 + BxyVyE1)(S - BxyE1E2) = SSVxE1 - SBxyVxE1E1E2 + SSVyE2 - SBxyVyE2E1E2 - SBxyVxVE2 + BxyBxyVxE2E1E2 + SBxyVyE1 - BxyBxyVyE1E1E2 = SSVxE1 - SBxyVxE2 + SSVyE2 + SBxyVyE1 - SBxyVxE2 - BxyBxyVxE1 + SBxyVyE1 - BxyBxyVyE2 = (SSVx + SBxyVy - BxyBxyVx + SBxyVy)E1 + (-SBxyVx + SSVy - SBxyVx - BxyBxyVy)E2 = ((S^2 - Bxy^2)Vx + 2SBxyVy)E1 + (-2SBxyVx + (S^2 - Bxy^2)Vy)E2 ------------------ convert rotor3 into mat3 rotate basis vectors by the rotor. first, x basis vector, i.e. (Vx = 1, Vy = 0) = (S^2 - Bxy^2)E1 + (-2SBxy)E2 y basis vector, i.e. (Vx = 0, Vy = 1) = (2SBxy)E1 + (S^2 - Bxy^2)E2ultraviolet-0.10.0/derivations/rotor3_rotate_rotor_derivation.txt000064400000000000000000000124631046102023000236310ustar 00000000000000E1, E2, E3 = basis vectors, geometric multiplication rules: given u and v are basis vectors, uu = 1 uv = -vu want to compute: a b (rotor composition) and a b a* (rotor rotation) where a, b are rotors, a* = reverse(a) a = Sa + BaxyE1E2 + BaxzE1E3 + BayzE2E3 b = Sb + BbxyE1E2 + BbxzE1E3 + BbyzE2E3 a* = Sa - BaxyE1E2 - BaxzE1E3 - BayzE2E3 ------------- a b = (Sa + BaxyE1E2 + BaxzE1E3 + BayzE2E3)(Sb + BbxyE1E2 + BbxzE1E3 + BbyzE2E3) = SaSb + SaBbxyE1E2 + SaBbxzE1E3 + SaBbyzE2E3 + SbBaxyE1E2 + BaxyBbxyE1E2E1E2 + BaxyBbxzE1E2E1E3 + BaxyBbyzE1E2E2E3 + SbBaxzE1E3 + BaxzBbxyE1E3E1E2 + BaxzBbxzE1E3E1E3 + BaxzBbyzE1E3E2E3 + SbBayzE2E3 + BayzBbxyE2E3E1E2 + BayzBbxzE2E3E1E3 + BayzBbyzE2E3E2E3 = SaSb + SaBbxyE1E2 + SaBbxzE1E3 + SaBbyzE2E3 + SbBaxyE1E2 - BaxyBbxy - BaxyBbxzE2E3 + BaxyBbyzE1E3 + SbBaxzE1E3 + BaxzBbxyE2E3 - BaxzBbxz - BaxzBbyzE1E2 + SbBayzE2E3 - BayzBbxyE1E3 + BayzBbxzE1E2 - BayzBbyz = SaSb - BaxyBbxy - BaxzBbxz - BayzBbyz + SaBbxyE1E2 + SbBaxyE1E2 - BaxzBbyzE1E2 + BayzBbxzE1E2 + SaBbxzE1E3 + SbBaxzE1E3 + BaxyBbyzE1E3 - BayzBbxyE1E3 + SaBbyzE2E3 + SbBayzE2E3 - BaxyBbxzE2E3 + BaxzBbxyE2E3 ------------- a b a* = (a b)(Sa - BaxyE1E2 - BaxzE1E3 - BayzE2E3) = (SaSb - BaxyBbxy - BaxzBbxz - BayzBbyz + SaBbxyE1E2 + SbBaxyE1E2 - BaxzBbyzE1E2 + BayzBbxzE1E2 + SaBbxzE1E3 + SbBaxzE1E3 + BaxyBbyzE1E3 - BayzBbxyE1E3 + SaBbyzE2E3 + SbBayzE2E3 - BaxyBbxzE2E3 + BaxzBbxyE2E3) * (Sa - BaxyE1E2 - BaxzE1E3 - BayzE2E3) = SaSaSb - SaBaxyBbxy - SaBaxzBbxz - SaBayzBbyz + SaSaBbxyE1E2 + SaSbBaxyE1E2 - SaBaxzBbyzE1E2 + SaBayzBbxzE1E2 + SaSaBbxzE1E3 + SaSbBaxzE1E3 + SaBaxyBbyzE1E3 - SaBayzBbxyE1E3 + SaSaBbyzE2E3 + SaSbBayzE2E3 - SaBaxyBbxzE2E3 + SaBaxzBbxyE2E3 - SaSbBaxyE1E2 + BaxyBaxyBbxyE1E2 + BaxyBaxzBbxzE1E2 + BaxyBayzBbyzE1E2 - SaBaxyBbxyE1E2E1E2 - SbBaxyBaxyE1E2E1E2 + BaxyBaxzBbyzE1E2E1E2 - BaxyBayzBbxzE1E2E1E2 - SaBaxyBbxzE1E3E1E2 - SbBaxyBaxzE1E3E1E2 - BaxyBaxyBbyzE1E3E1E2 + BaxyBayzBbxyE1E3E1E2 - SaBaxyBbyzE2E3E1E2 - SbBaxyBayzE2E3E1E2 + BaxyBaxyBbxzE2E3E1E2 - BaxyBaxzBbxyE2E3E1E2 - SaSbBaxzE1E3 + BaxyBaxzBbxyE1E3 + BaxzBaxzBbxzE1E3 + BaxzBayzBbyzE1E3 - SaBaxzBbxyE1E2E1E3 - SbBaxyBaxzE1E2E1E3 + BaxzBaxzBbyzE1E2E1E3 - BaxzBayzBbxzE1E2E1E3 - SaBaxzBbxzE1E3E1E3 - SbBaxzBaxzE1E3E1E3 - BaxyBaxzBbyzE1E3E1E3 + BaxzBayzBbxyE1E3E1E3 - SaBaxzBbyzE2E3E1E3 - SbBaxzBayzE2E3E1E3 + BaxyBaxzBbxzE2E3E1E3 - BaxzBaxzBbxyE2E3E1E3 - SaSbBayzE2E3 + BaxyBayzBbxyE2E3 + BaxzBayzBbxzE2E3 + BayzBayzBbyzE2E3 - SaBayzBbxyE1E2E2E3 - SbBaxyBayzE1E2E2E3 + BaxzBayzBbyzE1E2E2E3 - BayzBayzBbxzE1E2E2E3 - SaBayzBbxzE1E3E2E3 - SbBaxzBayzE1E3E2E3 - BaxyBayzBbyzE1E3E2E3 + BayzBayzBbxyE1E3E2E3 - SaBayzBbyzE2E3E2E3 - SbBayzBayzE2E3E2E3 + BaxyBayzBbxzE2E3E2E3 - BaxzBayzBbxyE2E3E2E3 = SaSaSb - SaBaxyBbxy - SaBaxzBbxz - SaBayzBbyz + SaSaBbxyE1E2 + SaSbBaxyE1E2 - SaBaxzBbyzE1E2 + SaBayzBbxzE1E2 + SaSaBbxzE1E3 + SaSbBaxzE1E3 + SaBaxyBbyzE1E3 - SaBayzBbxyE1E3 + SaSaBbyzE2E3 + SaSbBayzE2E3 - SaBaxyBbxzE2E3 + SaBaxzBbxyE2E3 - SaSbBaxyE1E2 + BaxyBaxyBbxyE1E2 + BaxyBaxzBbxzE1E2 + BaxyBayzBbyzE1E2 + SaBaxyBbxy + SbBaxyBaxy - BaxyBaxzBbyz + BaxyBayzBbxz - SaBaxyBbxzE2E3 - SbBaxyBaxzE2E3 - BaxyBaxyBbyzE2E3 + BaxyBayzBbxyE2E3 + SaBaxyBbyzE1E3 + SbBaxyBayzE1E3 - BaxyBaxyBbxzE1E3 + BaxyBaxzBbxyE1E3 - SaSbBaxzE1E3 + BaxyBaxzBbxyE1E3 + BaxzBaxzBbxzE1E3 + BaxzBayzBbyzE1E3 + SaBaxzBbxyE2E3 + SbBaxyBaxzE2E3 - BaxzBaxzBbyzE2E3 + BaxzBayzBbxzE2E3 + SaBaxzBbxz + SbBaxzBaxz + BaxyBaxzBbyz - BaxzBayzBbxy - SaBaxzBbyzE1E2 - SbBaxzBayzE1E2 + BaxyBaxzBbxzE1E2 - BaxzBaxzBbxyE1E2 - SaSbBayzE2E3 + BaxyBayzBbxyE2E3 + BaxzBayzBbxzE2E3 + BayzBayzBbyzE2E3 - SaBayzBbxyE1E3 - SbBaxyBayzE1E3 + BaxzBayzBbyzE1E3 - BayzBayzBbxzE1E3 + SaBayzBbxzE1E2 + SbBaxzBayzE1E2 + BaxyBayzBbyzE1E2 - BayzBayzBbxyE1E2 + SaBayzBbyz + SbBayzBayz - BaxyBayzBbxz + BaxzBayzBbxy = SaSaSb - SaBaxyBbxy - SaBaxzBbxz - SaBayzBbyz + SaBaxyBbxy + SbBaxyBaxy - BaxyBaxzBbyz + BaxyBayzBbxz + SaBaxzBbxz + SbBaxzBaxz + BaxyBaxzBbyz - BaxzBayzBbxy + SaBayzBbyz + SbBayzBayz - BaxyBayzBbxz + BaxzBayzBbxy +(SaSaBbxy + SaSbBaxy - SaBaxzBbyz + SaBayzBbxz - SaSbBaxy + BaxyBaxyBbxy + BaxyBaxzBbxz + BaxyBayzBbyz + SaBayzBbxz + SbBaxzBayz + BaxyBayzBbyz - BayzBayzBbxy - SaBaxzBbyz - SbBaxzBayz + BaxyBaxzBbxz - BaxzBaxzBbxy)E1E2 +(SaSaBbxz + SaSbBaxz + SaBaxyBbyz - SaBayzBbxy - SaSbBaxz + BaxyBaxzBbxy + BaxzBaxzBbxz + BaxzBayzBbyz + SaBaxyBbyz + SbBaxyBayzE1E3 - BaxyBaxyBbxz + BaxyBaxzBbxy - SaBayzBbxy - SbBaxyBayzE1E3 + BaxzBayzBbyz - BayzBayzBbxz)E1E3 +(SaSaBbyz + SaSbBayz - SaBaxyBbxz + SaBaxzBbxy - SaSbBayz + BaxyBayzBbxy + BaxzBayzBbxz + BayzBayzBbyz - SaBaxyBbxz - SbBaxyBaxz - BaxyBaxyBbyz + BaxyBayzBbxy + SaBaxzBbxy + SbBaxyBaxz - BaxzBaxzBbyz + BaxzBayzBbxz)E2E3 = SaSaSb + SbBaxyBaxy + SbBaxzBaxz + SbBayzBayz +(SaSaBbxy + 2SaBayzBbxz + BaxyBaxyBbxy + 2BaxyBaxzBbxz + 2BaxyBayzBbyz - BayzBayzBbxy - 2SaBaxzBbyz - BaxzBaxzBbxy)E1E2 +(SaSaBbxz + 2SaBaxyBbyz - 2SaBayzBbxy + 2BaxyBaxzBbxy + BaxyBaxzBbxz + 2BaxzBayzBbyz - BaxyBaxyBbxz - BayzBayzBbxz)E1E3 +(SaSaBbyz - 2SaBaxyBbxz + 2SaBaxzBbxy + 2BaxyBayzBbxy + 2BaxzBayzBbxz + BayzBayzBbyz - BaxyBaxyBbyz - BaxzBaxzBbyz)E2E3 = (Sa^2 + Baxy^2 + Baxz^2 + Bayz^2) * Sb +((Sa^2 + Baxy^2 - Baxz^2 - Bayz^2) * Bbxy + (BaxyBaxz + SaBayz) * 2Bbxz + (BaxyBayz - SaBaxz) * 2Bbyz )E1E2 +((Sa^2 - Baxy^2 + Baxz^2 - Bayz^2) * Bbxz + (BaxyBaxz - SaBayz) * 2Bbxy + (BaxzBayz + SaBaxy) * 2Bbyz )E1E3 +((Sa^2 - Baxy^2 - Baxz^2 + Bayz^2) * Bbyz + (BaxyBayz + SaBaxz) * 2Bbxy + (BaxzBayz - SaBaxy) * 2Bbxz )E2E3 ultraviolet-0.10.0/derivations/rotor3_rotate_vec_derivation.txt000064400000000000000000000102411046102023000232310ustar 00000000000000E1, E2, E3 = basis vectors, geometric multiplication rules: given u and v are basis vectors, uu = 1 uv = -vu want to compute: ab v ba (geometric product) where ab is a rotor ab = s + BxyE1E2 + BxzE1E3 + ByzE2E3 v = VxE1 + VyE2 + VzE3 ba = s - BxyE1E2 - BxzE1E3 - ByzE2E3 ------------- ab v = (s + BxyE1E2 + BxzE1E3 + ByzE2E3)(VxE1 + VyE2 + VzE3) = SVxE1 + SVyE2 + SVzE3 + BxyVxE1E2E1 + BxyVyE1E2E2 + BxyVzE1E2E3 + BxzVxE1E3E1 + BxzVyE1E3E2 + BxzVzE1E3E3 + ByzVxE2E3E1 + ByzVyE2E3E2 + ByzVzE2E3E3 = SVxE1 + SVyE2 + SVzE3 - BxyVxE2 + BxyVyE1 + BxyVzE1E2E3 - BxzVxE3 - BxzVyE1E2E3 + BxzVzE1 + ByzVxE1E2E3 - ByzVyE3 + ByzVzE2 = (SVx + BxyVy + BxzVz)E1 + (SVy - BxyVx + ByzVz)E2 + (SVz - BxzVx - ByzVy)E3 + (BxyVz - BxzVy + ByzVx)E1E2E3 FxE1 FyE2 FzE3 FwE1E2E3 factored version (for rotor rotate_by) ------------- ab v ba = (ab v)(S - BxyE1E2 - BxzE1E3 - ByzE2E3) = (FxE1 + FyE2 + FzE3 + FwE1E2E3)(S - BxyE1E2 - BxzE1E3 - ByzE2E3) = SFxE1 + SFyE2 + SFzE3 + SFwE1E2E3 - BxyFxE1E1E2 - BxyFyE2E1E2 - BxyFzE3E1E2 - BxyFwE1E2E3E1E2 - BxzFxE1E1E3 - BxzFyE2E1E3 - BxzFzE3E1E3 - BxzFwE1E2E3E1E3 - ByzFxE1E2E3 - ByzFyE2E2E3 - ByzFzE3E2E3 - ByzFwE1E2E3E2E3 = SFxE1 + SFyE2 + SFzE3 + SFwE1E2E3 - BxyFxE2 + BxyFyE1 - BxyFzE1E2E3 + BxyFwE3 - BxzFxE3 - BxzFyE1E2E3 + BxzFzE1 - BxzFwE2 - ByzFxE1E2E3 - ByzFyE3 + ByzFzE2 + ByzFwE1 = (SFx + BxyFy + BxzFz + ByzFw)E1 + (SFy - BxyFx - BxzFw + ByzFz)E2 + (SFz + BxyFw - BxzFx - ByzFy)E3 + (SFw - BxyFz - BxzFy - ByzFx)E1E2E3 unfactored version (used below for conversion to matrix) ------------ ab v ba = (ab v)(S - BxyE1E2 - BxzE1E3 - ByzE2E3) = S * (SVxE1 + SVyE2 + SVzE3 - BxyVxE2 + BxyVyE1 + BxyVzE1E2E3 - BxzVxE3 - BxzVyE1E2E3 + BxzVzE1 + ByzVxE1E2E3 - ByzVyE3 + ByzVzE2) - SVxBxyE1E1E2 - SVyBxyE2E1E2 - SVzBxyE3E1E2 + BxyBxyVxE2E1E2 - BxyBxyVyE1E1E2 - BxyBxyVzE1E2E3E1E2 + BxzBxyVxE3E1E2 + BxzBxyVyE1E2E3E1E2 - BxzBxyVzE1E1E2 - ByzBxyVxE1E2E3E1E2 + ByzBxzVyE3E1E2 - ByzBxyVzE2E1E2 - SVxBxzE1E1E3 - SVyBxzE2E1E3 - SVzBxzE3E1E3 + BxyBxzVxE2E1E3 - BxyBxzVyE1E1E3 - BxyBxzVzE1E2E3E1E3 + BxzBxzVxE3E1E3 + BxzBxzVyE1E2E3E1E3 - BxzBxzVzE1E1E3 - ByzBxzVxE1E2E3E1E3 + ByzBxzVyE3E1E3 - ByzBxzVzE2E1E3 - SVxByzE1E2E3 - SVyByzE2E2E3 - SVzByzE3E2E3 + BxyByzVxE2E2E3 - BxyByzVyE1E2E3 - BxyByzVzE1E2E3E2E3 + BxzByzVxE3E2E3 + BxzByzVyE1E2E3E2E3 - BxzByzVzE1E2E3 - ByzByzVxE1E2E3E2E3 + ByzByzVyE3E2E3 - ByzByzVzE2E2E3 = SSVxE1 + SSVyE2 + SSVzE3 - SBxyVxE2 + SBxyVyE1 + SBxyVzE1E2E3 - SBxzVxE3 - SBxzVyE1E2E3 + SBxzVzE1 + SByzVxE1E2E3 - SByzVyE3 + SByzVzE2 - SVxBxyE2 + SVyBxyE1 - SVzBxyE1E2E3 - BxyBxyVxE1 - BxyBxyVyE2 + BxyBxyVzE3 + BxzBxyVxE1E2E3 - BxzBxyVyE3 - BxzBxyVzE2 + ByzBxyVxE3 + ByzBxzVyE1E2E3 + ByzBxyVzE1 - SVxBxzE3 + SVyBxzE1E2E3 + SVzBxzE1 - BxyBxzVxE1E2E3 - BxyBxzVyE3 - BxyBxzVzE2 - BxzBxzVxE1 + BxzBxzVyE2 - BxzBxzVzE3 - ByzBxzVxE2 - ByzBxzVyE1 + ByzBxzVzE1E2E3 - SVxByzE1E2E3 - SVyByzE3 + SVzByzE2 + BxyByzVxE3 - BxyByzVy1E2E3 + BxyByzVzE1 - BxzByzVxE2 - BxzByzVyE1 - BxzByzVzE1E2E3 + ByzByzVxE1 - ByzByzVyE2 - ByzByzVzE3 = (SSVx + SBxyVy + SBxzVz + SVyBxy - BxyBxyVx + ByzBxyVz + SVzBxz - BxzBxzVx - ByzBxzVy + BxyByzVz - BxzByzVy + ByzByzVx)E1 + (SSVy - SBxyVx + SByzVz - SVxBxy - BxyBxyVy - BxzBxyVz - BxyBxzVz + BxzBxzVy - ByzBxzVx + SVzByz - BxzByzVx - ByzByzVy)E2 + (SSVz - SBxzVx - SByzVy + BxyBxyVz - BxzBxyVy + ByzBxyVx - SVxBxz - BxyBxzVy - BxzBxzVz - SVyByz + BxyByzVx - ByzByzVz)E3 + (SBxyVz + SByzVx - SVzBxy + BxzBxyVx + ByzBxzVy + SVyBxz - BxyBxzVx + ByzBxzVz - SVxByz - BxyByzVy - BxzByzVz)E1E2E3 = ((S^2 - Bxy^2 - Bxz^2 + Byz^2)Vx + (SBxy - ByzBxz)2Vy + (SBxz + ByzBxy)2Vz)E1 + (-(ByzBxz + SBxy)2Vx + (S^2 - Bxy^2 + Bxz^2 - Byz^2)Vy + (SByz - BxzBxy)2Vz)E2 + ((ByzBxy - SBxz)2Vx - (SByz - BxzBxy)2Vy + (S^2 + Bxy^2 - Bxz^2 - Byz^2)Vz)E3 + ((BxzBxy - BxyBxz)Vx + (ByzBxz - BxyByz)Vy + (ByzBxz - BxzByz)Vz)E1E2E3 convert rotor3 into mat3 ----------------- rotate basis vectors by the rotor. first, x basis vector, i.e. (Vx = 1, Vy = 0, Vz = 0) = (S^2 - Bxy^2 - Bxz^2 + Byz^2)E1 + (-2(ByzBxz + SBxy))E2 + (2(ByzBxy - SBxz))E3 y basis vector, i.e. (Vx = 0, Vy = 1, Vz = 0) = (2(SBxy - ByzBxz))E1 + (S^2 - Bxy^2 + Bxz^2 - Byz^2)E2 - (2(SByz + BxzBxy))E3 z basis vector, i.e. (Vz = 0, Vy = 0, Vz = 1) = (2(SBxz + ByzBxy))E1 + (2(SByz - BxzBxy))E2 + (S^2 + Bxy^2 - Bxz^2 - Byz^2)E3ultraviolet-0.10.0/docs/rotor3_mul_vec3.txt000064400000000000000000000000001046102023000167600ustar 00000000000000ultraviolet-0.10.0/release.toml000064400000000000000000000003201046102023000145640ustar 00000000000000pre-release-replacements = [ {file="CHANGELOG.md", search="Unreleased", replace="{{version}}"}, {file="CHANGELOG.md", search="", replace="\n## Unreleased\n"}, ]ultraviolet-0.10.0/src/bivec.rs000064400000000000000000000451261046102023000145110ustar 00000000000000//! Bivectors, i.e. oriented areas. //! //! A bivector is an *oriented area*, and is equivalent //! to the result of the exterior (wedge) product of two vectors, i.e. //! `u ∧ v`. This means it is the *oriented area* of the parallelogram //! created by attaching two vectors and then extending them into a parallelogram. //! //! This may be hard to visualize at first, but bivectors are as fundamental as vectors. If vectors //! are a representation of *lines*, then bivectors are a representation of *planes*. //! //! A normalized bivector can be thought of as representing a plane of rotation and the *direction of rotation* //! inside that plane such that a *positive* rotation follows the orientation of the bivector. When //! you obtain a bivector by taking the exterior product of two vectors, the positive direction of rotation //! is defined as the one that *brings the first vector closer to the second*. For example, a bivector //! created by taking the exterior product `x ∧ y` of the x and y basis vectors will create a unit //! bivector that represents the xy plane, with orientation such that a positive rotation of `x` inside //! the plane would bring `x` closer to `y`. This is why positive rotation is generally defined as //! "counter clockwise" in 2d, since such a rotation brings `x` to `y`. //! //! Much like vectors can be represented as a linear combination of *basis vectors*, i.e. //! a vector "component representation," bivectors can be represented as a linear combination //! of *basis bivectors*. If the basis vectors are the unit vectors in the direction of each //! canonical axis of a space, then the basis bivectors are the *unit area planes* in each of the //! canonical planes. //! //! In 2d, there is only one basis plane, the xy plane, which represents all of 2d space. As such, in 2d //! there is only *one* basis bivector, while there are *two* basis vectors. This means that a 2d bivector //! has only one component. //! //! In 3d, there are three basis planes, the xy plane, the xz plane, and the yz plane, which are respectively //! the planes parallel to those combinations of the x, y, and z basis vectors. Therefore, a 3d bivector has //! three components, each of which represents the *projected area* of that bivector onto one of the three //! basis bivectors. This is analogous to how vector components represent the *projected length* of that vector //! onto each unit vector. use crate::*; use crate::util::*; use std::ops::*; macro_rules! bivec2s { ($(($bn:ident) => $t:ident),+) => { $( /// A bivector in 2d space. /// /// Since in 2d there is only one plane in the whole of 2d space, a 2d bivector /// has only one component. /// /// Please see the module level documentation for more information on bivectors generally! #[derive(Clone, Copy, Debug, Default, PartialEq)] #[repr(C)] pub struct $bn { pub xy: $t } impl $bn { #[inline] pub const fn new(xy: $t) -> Self { Self { xy } } #[inline] pub fn zero() -> Self { Self::new($t::splat(0.0)) } #[inline] pub fn unit_xy() -> Self { Self::new($t::splat(1.0)) } #[inline] pub fn mag_sq(&self) -> $t { self.xy * self.xy } #[inline] pub fn mag(&self) -> $t { self.mag_sq().sqrt() } #[inline] pub fn normalize(&mut self) { let mag = self.mag(); self.xy /= mag; } #[inline] #[must_use = "Did you mean to use `.normalize()` to normalize `self` in place?"] pub fn normalized(&self) -> Self { let mut r = self.clone(); r.normalize(); r } #[inline] pub fn dot(&self, rhs: Self) -> $t { self.xy * rhs.xy } #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $bn as *const $t, 1) } } #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $bn as *const u8, std::mem::size_of::<$t>()) } } #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $bn as *mut $t, 1) } } #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $bn as *mut u8, std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub const fn as_ptr(&self) -> *const $t { self as *const $bn as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $bn as *mut $t } } impl EqualsEps for $bn { fn eq_eps(self, other: Self) -> bool { self.xy.eq_eps(other.xy) } } impl Add for $bn { type Output = Self; #[inline] fn add(mut self, rhs: $bn) -> Self { self += rhs; self } } impl AddAssign for $bn { #[inline] fn add_assign(&mut self, rhs: $bn) { self.xy += rhs.xy; } } impl Sub for $bn { type Output = Self; #[inline] fn sub(mut self, rhs: $bn) -> Self { self -= rhs; self } } impl SubAssign for $bn { #[inline] fn sub_assign(&mut self, rhs: $bn) { self.xy -= rhs.xy; } } impl Mul for $bn { type Output = Self; #[inline] fn mul(mut self, rhs: $bn) -> Self { self *= rhs; self } } impl Mul<$bn> for $t { type Output = $bn; #[inline] fn mul(self, mut rhs: $bn) -> $bn { rhs *= self; rhs } } impl Mul<$t> for $bn { type Output = Self; #[inline] fn mul(mut self, rhs: $t) -> Self { self *= rhs; self } } impl MulAssign for $bn { #[inline] fn mul_assign(&mut self, rhs: Self) { self.xy *= rhs.xy; } } impl MulAssign<$t> for $bn { #[inline] fn mul_assign(&mut self, rhs: $t) { self.xy *= rhs; } } impl Div for $bn { type Output = Self; #[inline] fn div(mut self, rhs: $bn) -> Self { self /= rhs; self } } impl Div<$t> for $bn { type Output = $bn; #[inline] fn div(mut self, rhs: $t) -> $bn { self.xy /= rhs; self } } impl DivAssign for $bn { #[inline] fn div_assign(&mut self, rhs: $bn) { self.xy /= rhs.xy; } } impl DivAssign<$t> for $bn { #[inline] fn div_assign(&mut self, rhs: $t) { self.xy /= rhs; } } impl Neg for $bn { type Output = Self; #[inline] fn neg(mut self) -> Self { self.xy = -self.xy; self } } )+ } } macro_rules! bivec3s { ($($bn:ident => ($vt:ident, $t:ident)),+) => { $( /// A bivector in 3d space. /// /// In 3d, a bivector has 3 components, each one representing the signed *projected area* of the bivector /// onto one of the 3 *basis bivectors*, which can be thought of as corresponding to each of the /// three basis planes. This is analogous to the components of a 3d vector, which correspond to the /// *projected length* of the vector onto the three basis *vectors. Since in 3d, there are three /// components for both vectors and bivectors, 3d bivectors have been historically confused with /// 3d vectors quite a lot. /// /// Please see the module level documentation for more information on bivectors generally! #[derive(Clone, Copy, Debug, Default, PartialEq)] #[repr(C)] pub struct $bn { pub xy: $t, pub xz: $t, pub yz: $t, } impl EqualsEps for $bn { fn eq_eps(self, other: Self) -> bool { self.xy.eq_eps(other.xy) && self.xz.eq_eps(other.xz) && self.yz.eq_eps(other.yz) } } impl $bn { #[inline] pub const fn new(xy: $t, xz: $t, yz: $t) -> Self { Self { xy, xz, yz } } #[inline] pub fn zero() -> Self { Self::new($t::splat(0.0), $t::splat(0.0), $t::splat(0.0)) } /// Create the bivector which represents the same plane of rotation as a given /// normalized 'axis vector' #[inline] pub fn from_normalized_axis(v: $vt) -> Self { Self::new(v.z, -v.y, v.x) } #[inline] pub fn unit_xy() -> Self { Self::new($t::splat(1.0), $t::splat(0.0), $t::splat(0.0)) } #[inline] pub fn unit_xz() -> Self { Self::new($t::splat(0.0), $t::splat(1.0), $t::splat(0.0)) } #[inline] pub fn unit_yz() -> Self { Self::new($t::splat(0.0), $t::splat(0.0), $t::splat(1.0)) } #[inline] pub fn mag_sq(&self) -> $t { (self.xy * self.xy) + (self.xz * self.xz) + (self.yz * self.yz) } #[inline] pub fn mag(&self) -> $t { self.mag_sq().sqrt() } #[inline] pub fn normalize(&mut self) { let mag = self.mag(); self.xy /= mag; self.xz /= mag; self.yz /= mag; } #[inline] #[must_use = "Did you mean to use `.normalize()` to normalize `self` in place?"] pub fn normalized(&self) -> Self { let mut r = self.clone(); r.normalize(); r } #[inline] pub fn dot(&self, rhs: Self) -> $t { (self.xy * rhs.xy) + (self.xz * rhs.xz) + (self.yz * rhs.yz) } #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $bn as *const $t, 3) } } #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $bn as *const u8, 3 * std::mem::size_of::<$t>()) } } #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $bn as *mut $t, 3) } } #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $bn as *mut u8, 3 * std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub const fn as_ptr(&self) -> *const $t { self as *const $bn as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $bn as *mut $t } } impl Add for $bn { type Output = Self; #[inline] fn add(mut self, rhs: $bn) -> Self { self += rhs; self } } impl AddAssign for $bn { #[inline] fn add_assign(&mut self, rhs: $bn) { self.xy += rhs.xy; self.xz += rhs.xz; self.yz += rhs.yz; } } impl Sub for $bn { type Output = Self; #[inline] fn sub(mut self, rhs: $bn) -> Self { self -= rhs; self } } impl SubAssign for $bn { #[inline] fn sub_assign(&mut self, rhs: $bn) { self.xy -= rhs.xy; self.xz -= rhs.xz; self.yz -= rhs.yz; } } impl Mul for $bn { type Output = Self; #[inline] fn mul(mut self, rhs: $bn) -> Self { self *= rhs; self } } impl Mul<$bn> for $t { type Output = $bn; #[inline] fn mul(self, mut rhs: $bn) -> $bn { rhs *= self; rhs } } impl Mul<$t> for $bn { type Output = Self; #[inline] fn mul(mut self, rhs: $t) -> Self { self *= rhs; self } } impl MulAssign for $bn { #[inline] fn mul_assign(&mut self, rhs: Self) { self.xy *= rhs.xy; self.xz *= rhs.xz; self.yz *= rhs.yz; } } impl MulAssign<$t> for $bn { #[inline] fn mul_assign(&mut self, rhs: $t) { self.xy *= rhs; self.xz *= rhs; self.yz *= rhs; } } impl Div for $bn { type Output = Self; #[inline] fn div(mut self, rhs: $bn) -> Self { self /= rhs; self } } impl Div<$t> for $bn { type Output = $bn; #[inline] fn div(mut self, rhs: $t) -> $bn { self.xy /= rhs; self } } impl DivAssign for $bn { #[inline] fn div_assign(&mut self, rhs: $bn) { self.xy /= rhs.xy; self.xz /= rhs.xz; self.yz /= rhs.yz; } } impl DivAssign<$t> for $bn { #[inline] fn div_assign(&mut self, rhs: $t) { self.xy /= rhs; self.xz /= rhs; self.yz /= rhs; } } impl Neg for $bn { type Output = Self; #[inline] fn neg(mut self) -> Self { self.xy = -self.xy; self.xz = -self.xz; self.yz = -self.yz; self } } )+ } } bivec2s!( (Bivec2) => f32, (Bivec2x4) => f32x4, (Bivec2x8) => f32x8 ); #[cfg(feature = "f64")] bivec2s!( (DBivec2) => f64, (DBivec2x2) => f64x2, (DBivec2x4) => f64x4 ); bivec3s!( Bivec3 => (Vec3, f32), Bivec3x4 => (Vec3x4, f32x4), Bivec3x8 => (Vec3x8, f32x8) ); #[cfg(feature = "f64")] bivec3s!( DBivec3 => (DVec3, f64), DBivec3x2 => (DVec3x2, f64x2), DBivec3x4 => (DVec3x4, f64x4) ); ultraviolet-0.10.0/src/conversion.rs000064400000000000000000000166011046102023000156020ustar 00000000000000//! Contains implementations to convert between `UVec`/`IVec` and `Vec`/`DVec`. //! //! To realize such conversions we make use of crate-private traits `TryFromExt` and `TryIntoExt` to //! simulate the behaviour of the official [From] and [Into]. use crate::*; use core::convert::TryFrom; use std::error::Error; use std::fmt; /// A simple trait extension to simulate `TryFrom` for types that are not from this crate. trait TryFromExt: Sized { type Error; fn try_from(source: Source) -> Result; } /// A simple trait extension to simulate `TryInto` for types that are not from this crate. trait TryIntoExt { type Error; fn try_into(self) -> Result; } impl TryIntoExt for Source where Target: TryFromExt, { type Error = E; fn try_into(self) -> Result { Target::try_from(self) } } /// The error type that may happen when converting a `f32` or `f64` to any other numerical /// representation. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum FloatConversionError { NaN, Infinite, PosOverflow, NegOverflow, } impl fmt::Display for FloatConversionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FloatConversionError::NaN => f.write_str("NaN"), FloatConversionError::Infinite => f.write_str("Infinite"), FloatConversionError::PosOverflow => f.write_str("PosOverflow"), FloatConversionError::NegOverflow => f.write_str("NegOverflow"), } } } impl Error for FloatConversionError {} macro_rules! impl_try_from_float { ($source:ty => $($target:ident),*) => {$( impl TryFromExt<$source> for $target { type Error = FloatConversionError; /// Tries to convert the source to Self in a lossy way, flooring any float value. /// /// # Errors /// * `NaN` - If the float value is `NaN`. /// * `Infinite` - If the float value is infinity or negative infinity. /// * `PosOverflow` - If the float value would be greater than the target max value. /// * `NegOverflow` - If the float value would be less than the target min value. #[inline] fn try_from(source: $source) -> Result { if source.is_nan() { return Err(FloatConversionError::NaN) } if source.is_infinite() { return Err(FloatConversionError::Infinite) } let min = Self::MIN as $source; if source < min { return Err(FloatConversionError::NegOverflow) } let max = Self::MAX as $source; if source > max { return Err(FloatConversionError::PosOverflow) } Ok(source as Self) } } )*} } impl_try_from_float!(f32 => i32, u32); impl_try_from_float!(f64 => i32, u32); macro_rules! impl_try_from_float_vec { ($(($name:ident => $target:ident, [$($var:ident),*])),+) => { $( impl TryFrom<$name> for $target { type Error = FloatConversionError; /// Tries to convert the source to Self in a lossy way, flooring any float value. /// /// # Errors /// * `NaN` - If a float value is `NaN`. /// * `NotFinite` - If a float value is infinity or negative infinity. /// * `PosOverflow` - If a float value would be greater than the the self.component max value. /// * `NegOverflow` - If a float value would be less than the self.component min value. #[inline] fn try_from(v: $name) -> Result { Ok(Self::new($(v.$var.try_into()?,)* )) } } )+ } } macro_rules! impl_from_int_vec { ($(($name:ident => $target:ident, $target_type:ident, [$($var:ident),*])),+) => { $( impl From<$name> for $target { #[inline] fn from(v: $name) -> Self { Self::new($(v.$var as $target_type,)*) } } )+ }; } impl_try_from_float_vec!( (Vec2 => IVec2, [x, y]), (Vec3 => IVec3, [x, y, z]), (Vec4 => IVec4, [x, y, z, w]), (Vec2 => UVec2, [x, y]), (Vec3 => UVec3, [x, y, z]), (Vec4 => UVec4, [x, y, z, w]) ); #[cfg(feature = "f64")] impl_try_from_float_vec!( (DVec2 => IVec2, [x, y]), (DVec3 => IVec3, [x, y, z]), (DVec4 => IVec4, [x, y, z, w]), (DVec2 => UVec2, [x, y]), (DVec3 => UVec3, [x, y, z]), (DVec4 => UVec4, [x, y, z, w]) ); impl_from_int_vec!( (IVec2 => Vec2, f32, [x, y]), (IVec3 => Vec3, f32, [x, y, z]), (IVec4 => Vec4, f32, [x, y, z, w]), (UVec2 => Vec2, f32, [x, y]), (UVec3 => Vec3, f32, [x, y, z]), (UVec4 => Vec4, f32, [x, y, z, w]) ); #[cfg(feature = "f64")] impl_from_int_vec!( (IVec2 => DVec2, f64, [x, y]), (IVec3 => DVec3, f64, [x, y, z]), (IVec4 => DVec4, f64, [x, y, z, w]), (UVec2 => DVec2, f64, [x, y]), (UVec3 => DVec3, f64, [x, y, z]), (UVec4 => DVec4, f64, [x, y, z, w]) ); // tests only for Vec2 #[cfg(test)] mod tests { use crate::*; use core::convert::TryFrom; #[test] #[cfg(feature = "int")] fn vec2_to_ivec2_exact() { let vec2 = Vec2::new(1.0, 2.0); let ivec2 = IVec2::try_from(vec2); assert_eq!(ivec2.ok().unwrap(), IVec2::new(1, 2)); } #[test] #[cfg(feature = "int")] fn vec2_to_ivec2_floored() { let vec2 = Vec2::new(1.99, 2.99); let ivec2 = IVec2::try_from(vec2); assert_eq!(ivec2.ok().unwrap(), IVec2::new(1, 2)); } #[test] #[cfg(feature = "int")] fn vec2_to_ivec2_nan() { let vec2 = Vec2::new(f32::NAN, 0.0); let ivec2 = IVec2::try_from(vec2); assert_eq!(ivec2.err().unwrap(), FloatConversionError::NaN); } #[test] #[cfg(feature = "int")] fn vec2_to_ivec2_infinity() { let vec2 = Vec2::new(f32::INFINITY, 0.0); let ivec2 = IVec2::try_from(vec2); assert_eq!(ivec2.err().unwrap(), FloatConversionError::Infinite); } #[test] #[cfg(feature = "int")] fn vec2_to_ivec2_neg_infinity() { let vec2 = Vec2::new(f32::NEG_INFINITY, 0.0); let ivec2 = IVec2::try_from(vec2); assert_eq!(ivec2.err().unwrap(), FloatConversionError::Infinite); } #[test] #[cfg(feature = "int")] fn vec2_to_ivec2_pos_overflow() { let vec2 = Vec2::new(f32::MAX, 0.0); let ivec2 = IVec2::try_from(vec2); assert_eq!(ivec2.err().unwrap(), FloatConversionError::PosOverflow); } #[test] #[cfg(feature = "int")] fn vec2_to_ivec2_neg_overflow() { let vec2 = Vec2::new(f32::MIN, 0.0); let ivec2 = IVec2::try_from(vec2); assert_eq!(ivec2.err().unwrap(), FloatConversionError::NegOverflow); } #[test] #[cfg(feature = "int")] fn ivec2_to_vec2() { let ivec2 = IVec2::new(1, 2); let vec2 = Vec2::from(ivec2); assert_eq!(vec2, Vec2::new(1.0, 2.0)); } #[test] #[cfg(feature = "int")] fn vec2_to_uvec2_neg_overflow() { let vec2 = Vec2::new(-1.0, 0.0); let uvec2 = UVec2::try_from(vec2); assert_eq!(uvec2.err().unwrap(), FloatConversionError::NegOverflow); } } ultraviolet-0.10.0/src/impl_bytemuck.rs000064400000000000000000000063521046102023000162630ustar 00000000000000use crate::*; use bytemuck::{Pod, Zeroable}; // ... unsafe impl Pod for Vec2 {} unsafe impl Zeroable for Vec2 {} unsafe impl Pod for Vec3 {} unsafe impl Zeroable for Vec3 {} unsafe impl Pod for Vec4 {} unsafe impl Zeroable for Vec4 {} unsafe impl Pod for Bivec2 {} unsafe impl Zeroable for Bivec2 {} unsafe impl Pod for Bivec3 {} unsafe impl Zeroable for Bivec3 {} unsafe impl Pod for Rotor2 {} unsafe impl Zeroable for Rotor2 {} unsafe impl Pod for Rotor3 {} unsafe impl Zeroable for Rotor3 {} unsafe impl Pod for Mat2 {} unsafe impl Zeroable for Mat2 {} unsafe impl Pod for Mat3 {} unsafe impl Zeroable for Mat3 {} unsafe impl Pod for Mat4 {} unsafe impl Zeroable for Mat4 {} unsafe impl Pod for Isometry2 {} unsafe impl Zeroable for Isometry2 {} unsafe impl Pod for Isometry3 {} unsafe impl Zeroable for Isometry3 {} unsafe impl Pod for Similarity2 {} unsafe impl Zeroable for Similarity2 {} unsafe impl Pod for Similarity3 {} unsafe impl Zeroable for Similarity3 {} // ... #[cfg(feature = "f64")] unsafe impl Pod for DVec2 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DVec2 {} #[cfg(feature = "f64")] unsafe impl Pod for DVec3 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DVec3 {} #[cfg(feature = "f64")] unsafe impl Pod for DVec4 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DVec4 {} #[cfg(feature = "f64")] unsafe impl Pod for DBivec2 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DBivec2 {} #[cfg(feature = "f64")] unsafe impl Pod for DBivec3 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DBivec3 {} #[cfg(feature = "f64")] unsafe impl Pod for DRotor2 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DRotor2 {} #[cfg(feature = "f64")] unsafe impl Pod for DRotor3 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DRotor3 {} #[cfg(feature = "f64")] unsafe impl Pod for DMat2 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DMat2 {} #[cfg(feature = "f64")] unsafe impl Pod for DMat3 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DMat3 {} #[cfg(feature = "f64")] unsafe impl Pod for DMat4 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DMat4 {} #[cfg(feature = "f64")] unsafe impl Pod for DIsometry2 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DIsometry2 {} #[cfg(feature = "f64")] unsafe impl Pod for DIsometry3 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DIsometry3 {} #[cfg(feature = "f64")] unsafe impl Pod for DSimilarity2 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DSimilarity2 {} #[cfg(feature = "f64")] unsafe impl Pod for DSimilarity3 {} #[cfg(feature = "f64")] unsafe impl Zeroable for DSimilarity3 {} // ... #[cfg(feature = "int")] unsafe impl Pod for IVec2 {} #[cfg(feature = "int")] unsafe impl Zeroable for IVec2 {} #[cfg(feature = "int")] unsafe impl Pod for IVec3 {} #[cfg(feature = "int")] unsafe impl Zeroable for IVec3 {} #[cfg(feature = "int")] unsafe impl Pod for IVec4 {} #[cfg(feature = "int")] unsafe impl Zeroable for IVec4 {} #[cfg(feature = "int")] unsafe impl Pod for UVec2 {} #[cfg(feature = "int")] unsafe impl Zeroable for UVec2 {} #[cfg(feature = "int")] unsafe impl Pod for UVec3 {} #[cfg(feature = "int")] unsafe impl Zeroable for UVec3 {} #[cfg(feature = "int")] unsafe impl Pod for UVec4 {} #[cfg(feature = "int")] unsafe impl Zeroable for UVec4 {} ultraviolet-0.10.0/src/impl_mint.rs000064400000000000000000000124131046102023000154020ustar 00000000000000use crate::*; macro_rules! from_vec2s { ($($minttype:ty => $uvtype:ty),+) => { $(impl From<$minttype> for $uvtype { #[inline] fn from(v: $minttype) -> Self { Self::new(v.x, v.y) } } impl From<$uvtype> for $minttype { #[inline] fn from(v: $uvtype) -> Self { Self { x: v.x, y: v.y, } } })+ } } macro_rules! from_vec3s { ($($minttype:ty => $uvtype:ty),+) => { $(impl From<$minttype> for $uvtype { #[inline] fn from(v: $minttype) -> Self { Self::new(v.x, v.y, v.z) } } impl From<$uvtype> for $minttype { #[inline] fn from(v: $uvtype) -> Self { Self { x: v.x, y: v.y, z: v.z, } } })+ } } macro_rules! from_vec4s { ($($minttype:ty => $uvtype:ty),+) => { $(impl From<$minttype> for $uvtype { #[inline] fn from(v: $minttype) -> Self { Self::new(v.x, v.y, v.z, v.w) } } impl From<$uvtype> for $minttype { #[inline] fn from(v: $uvtype) -> Self { Self { x: v.x, y: v.y, z: v.z, w: v.w, } } })+ } } from_vec2s!( mint::Vector2 => Vec2, mint::Point2 => Vec2 ); #[cfg(feature = "int")] from_vec2s!( mint::Vector2 => IVec2, mint::Point2 => IVec2, mint::Vector2 => UVec2, mint::Point2 => UVec2 ); #[cfg(feature = "f64")] from_vec2s!( mint::Vector2 => DVec2, mint::Point2 => DVec2 ); from_vec3s!( mint::Vector3 => Vec3, mint::Point3 => Vec3 ); #[cfg(feature = "int")] from_vec3s!( mint::Vector3 => IVec3, mint::Point3 => IVec3, mint::Vector3 => UVec3, mint::Point3 => UVec3 ); #[cfg(feature = "f64")] from_vec3s!( mint::Vector3 => DVec3, mint::Point3 => DVec3 ); from_vec4s!( mint::Vector4 => Vec4 ); #[cfg(feature = "int")] from_vec4s!( mint::Vector4 => IVec4, mint::Vector4 => UVec4 ); #[cfg(feature = "f64")] from_vec4s!( mint::Vector4 => DVec4 ); macro_rules! from_mat2s { ($($minttype:ty => $uvtype:ty),+) => { $(impl From<$minttype> for $uvtype { #[inline] fn from(v: $minttype) -> Self { Self::new(v.x.into(), v.y.into()) } } impl From<$uvtype> for $minttype { #[inline] fn from(v: $uvtype) -> Self { Self { x: v.cols[0].into(), y: v.cols[1].into(), } } })+ } } macro_rules! from_mat3s { ($($minttype:ty => $uvtype:ty),+) => { $(impl From<$minttype> for $uvtype { #[inline] fn from(v: $minttype) -> Self { Self::new(v.x.into(), v.y.into(), v.z.into()) } } impl From<$uvtype> for $minttype { #[inline] fn from(v: $uvtype) -> Self { Self { x: v.cols[0].into(), y: v.cols[1].into(), z: v.cols[2].into(), } } })+ } } macro_rules! from_mat4s { ($($minttype:ty => $uvtype:ty),+) => { $(impl From<$minttype> for $uvtype { #[inline] fn from(v: $minttype) -> Self { Self::new(v.x.into(), v.y.into(), v.z.into(), v.w.into()) } } impl From<$uvtype> for $minttype { #[inline] fn from(v: $uvtype) -> Self { Self { x: v.cols[0].into(), y: v.cols[1].into(), z: v.cols[2].into(), w: v.cols[3].into(), } } })+ } } from_mat2s!(mint::ColumnMatrix2 => Mat2); #[cfg(feature = "f64")] from_mat2s!(mint::ColumnMatrix2 => DMat2); from_mat3s!(mint::ColumnMatrix3 => Mat3); #[cfg(feature = "f64")] from_mat3s!(mint::ColumnMatrix3 => DMat3); from_mat4s!(mint::ColumnMatrix4 => Mat4); #[cfg(feature = "f64")] from_mat4s!(mint::ColumnMatrix4 => DMat4); macro_rules! from_quat { ($($minttype:ty => $uvtype:ty),+) => { $(impl From<$minttype> for $uvtype { #[inline] fn from(q: $minttype) -> Self { Self::from_quaternion_array([q.v.x, q.v.y, q.v.z, q.s]) } } impl From<$uvtype> for $minttype { #[inline] fn from(r: $uvtype) -> Self { let arr = r.into_quaternion_array(); Self { v: mint::Vector3 { x: arr[0], y: arr[1], z: arr[2], }, s: arr[3], } } })+ } } from_quat!(mint::Quaternion => Rotor3); #[cfg(feature = "f64")] from_quat!(mint::Quaternion => DRotor3); ultraviolet-0.10.0/src/impl_serde.rs000064400000000000000000002217141046102023000155430ustar 00000000000000use crate::*; use serde::{ de::{MapAccess, SeqAccess, Visitor}, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer, }; macro_rules! impl_serde_vec2 { ($name:ident) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { let mut state = serializer.serialize_struct(stringify!($name), 2)?; state.serialize_field("x", &self.x)?; state.serialize_field("y", &self.y)?; state.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { enum Field { X, Y, } impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct FieldVisitor; impl<'de> Visitor<'de> for FieldVisitor { type Value = Field; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str("`x` or `y`") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { match value { "x" => Ok(Field::X), "y" => Ok(Field::Y), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } } deserializer.deserialize_identifier(FieldVisitor) } } struct TVisitor; impl<'de> Visitor<'de> for TVisitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str(&["struct ", stringify!($type)].concat()) } fn visit_seq(self, mut seq: V) -> Result where V: SeqAccess<'de>, { let x = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; let y = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; Ok(Self::Value::new(x, y)) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut x = None; let mut y = None; while let Some(key) = map.next_key()? { match key { Field::X => { if x.is_some() { return Err(serde::de::Error::duplicate_field("x")); } x = Some(map.next_value()?); } Field::Y => { if y.is_some() { return Err(serde::de::Error::duplicate_field("y")); } y = Some(map.next_value()?); } } } let x = x.ok_or_else(|| serde::de::Error::missing_field("x"))?; let y = y.ok_or_else(|| serde::de::Error::missing_field("y"))?; Ok(Self::Value::new(x, y)) } } const FIELDS: &'static [&'static str] = &["x", "y"]; deserializer.deserialize_struct(stringify!($name), FIELDS, TVisitor) } } }; } macro_rules! impl_serde_vec3 { ($name:ident) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { let mut state = serializer.serialize_struct(stringify!($name), 3)?; state.serialize_field("x", &self.x)?; state.serialize_field("y", &self.y)?; state.serialize_field("z", &self.z)?; state.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { enum Field { X, Y, Z, } impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct FieldVisitor; impl<'de> Visitor<'de> for FieldVisitor { type Value = Field; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str("`x` or `y` or `z`") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { match value { "x" => Ok(Field::X), "y" => Ok(Field::Y), "z" => Ok(Field::Z), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } } deserializer.deserialize_identifier(FieldVisitor) } } struct TVisitor; impl<'de> Visitor<'de> for TVisitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str(&["struct ", stringify!($name)].concat()) } fn visit_seq(self, mut seq: V) -> Result where V: SeqAccess<'de>, { let x = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; let y = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; let z = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(2, &self))?; Ok(Self::Value::new(x, y, z)) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut x = None; let mut y = None; let mut z = None; while let Some(key) = map.next_key()? { match key { Field::X => { if x.is_some() { return Err(serde::de::Error::duplicate_field("x")); } x = Some(map.next_value()?); } Field::Y => { if y.is_some() { return Err(serde::de::Error::duplicate_field("y")); } y = Some(map.next_value()?); } Field::Z => { if z.is_some() { return Err(serde::de::Error::duplicate_field("z")); } z = Some(map.next_value()?); } } } let x = x.ok_or_else(|| serde::de::Error::missing_field("x"))?; let y = y.ok_or_else(|| serde::de::Error::missing_field("y"))?; let z = z.ok_or_else(|| serde::de::Error::missing_field("z"))?; Ok(Self::Value::new(x, y, z)) } } const FIELDS: &'static [&'static str] = &["x", "y", "z"]; deserializer.deserialize_struct(stringify!($name), FIELDS, TVisitor) } } }; } macro_rules! impl_serde_vec4 { ($name:ident) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { let mut state = serializer.serialize_struct(stringify!($name), 4)?; state.serialize_field("x", &self.x)?; state.serialize_field("y", &self.y)?; state.serialize_field("z", &self.z)?; state.serialize_field("w", &self.w)?; state.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { enum Field { X, Y, Z, W, } impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct FieldVisitor; impl<'de> Visitor<'de> for FieldVisitor { type Value = Field; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str("`x` or `y` or `z` or `w`") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { match value { "x" => Ok(Field::X), "y" => Ok(Field::Y), "z" => Ok(Field::Z), "w" => Ok(Field::W), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } } deserializer.deserialize_identifier(FieldVisitor) } } struct TVisitor; impl<'de> Visitor<'de> for TVisitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str(&["struct ", stringify!($name)].concat()) } fn visit_seq(self, mut seq: V) -> Result where V: SeqAccess<'de>, { let x = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; let y = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; let z = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(2, &self))?; let w = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(3, &self))?; Ok(Self::Value::new(x, y, z, w)) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut x = None; let mut y = None; let mut z = None; let mut w = None; while let Some(key) = map.next_key()? { match key { Field::X => { if x.is_some() { return Err(serde::de::Error::duplicate_field("x")); } x = Some(map.next_value()?); } Field::Y => { if y.is_some() { return Err(serde::de::Error::duplicate_field("y")); } y = Some(map.next_value()?); } Field::Z => { if z.is_some() { return Err(serde::de::Error::duplicate_field("z")); } z = Some(map.next_value()?); } Field::W => { if w.is_some() { return Err(serde::de::Error::duplicate_field("w")); } w = Some(map.next_value()?); } } } let x = x.ok_or_else(|| serde::de::Error::missing_field("x"))?; let y = y.ok_or_else(|| serde::de::Error::missing_field("y"))?; let z = z.ok_or_else(|| serde::de::Error::missing_field("z"))?; let w = w.ok_or_else(|| serde::de::Error::missing_field("w"))?; Ok(Self::Value::new(x, y, z, w)) } } const FIELDS: &'static [&'static str] = &["x", "y", "z", "w"]; deserializer.deserialize_struct(stringify!($name), FIELDS, TVisitor) } } }; } impl_serde_vec2!(Vec2); #[cfg(feature = "int")] impl_serde_vec2!(UVec2); #[cfg(feature = "int")] impl_serde_vec2!(IVec2); #[cfg(feature = "f64")] impl_serde_vec2!(DVec2); impl_serde_vec3!(Vec3); #[cfg(feature = "int")] impl_serde_vec3!(UVec3); #[cfg(feature = "int")] impl_serde_vec3!(IVec3); #[cfg(feature = "f64")] impl_serde_vec3!(DVec3); impl_serde_vec4!(Vec4); #[cfg(feature = "int")] impl_serde_vec4!(UVec4); #[cfg(feature = "int")] impl_serde_vec4!(IVec4); #[cfg(feature = "f64")] impl_serde_vec4!(DVec4); #[cfg(test)] mod vec_serde_tests { use crate::vec::{Vec2, Vec3, Vec4}; use serde_test::{assert_tokens, Token}; #[test] fn vec2() { let vec2 = Vec2::new(1.0, 2.0); assert_tokens( &vec2, &[ Token::Struct { name: "Vec2", len: 2, }, Token::Str("x"), Token::F32(1.0), Token::Str("y"), Token::F32(2.0), Token::StructEnd, ], ); } #[test] fn vec3() { let vec3 = Vec3::new(1.0, 2.0, 3.0); assert_tokens( &vec3, &[ Token::Struct { name: "Vec3", len: 3, }, Token::Str("x"), Token::F32(1.0), Token::Str("y"), Token::F32(2.0), Token::Str("z"), Token::F32(3.0), Token::StructEnd, ], ); } #[test] fn vec4() { let vec4 = Vec4::new(1.0, 2.0, 3.0, 4.0); assert_tokens( &vec4, &[ Token::Struct { name: "Vec4", len: 4, }, Token::Str("x"), Token::F32(1.0), Token::Str("y"), Token::F32(2.0), Token::Str("z"), Token::F32(3.0), Token::Str("w"), Token::F32(4.0), Token::StructEnd, ], ); } } #[cfg(feature = "int")] #[cfg(test)] mod int_vec_serde_tests { use crate::{IVec2, IVec3, IVec4, UVec2, UVec3, UVec4}; use serde_test::{assert_tokens, Token}; #[test] fn uvec2() { let vec2 = UVec2::new(1, 2); assert_tokens( &vec2, &[ Token::Struct { name: "UVec2", len: 2, }, Token::Str("x"), Token::U32(1), Token::Str("y"), Token::U32(2), Token::StructEnd, ], ); } #[test] fn uvec3() { let vec3 = UVec3::new(1, 2, 3); assert_tokens( &vec3, &[ Token::Struct { name: "UVec3", len: 3, }, Token::Str("x"), Token::U32(1), Token::Str("y"), Token::U32(2), Token::Str("z"), Token::U32(3), Token::StructEnd, ], ); } #[test] fn uvec4() { let vec4 = UVec4::new(1, 2, 3, 4); assert_tokens( &vec4, &[ Token::Struct { name: "UVec4", len: 4, }, Token::Str("x"), Token::U32(1), Token::Str("y"), Token::U32(2), Token::Str("z"), Token::U32(3), Token::Str("w"), Token::U32(4), Token::StructEnd, ], ); } #[test] fn ivec2() { let vec2 = IVec2::new(1, 2); assert_tokens( &vec2, &[ Token::Struct { name: "IVec2", len: 2, }, Token::Str("x"), Token::I32(1), Token::Str("y"), Token::I32(2), Token::StructEnd, ], ); } #[test] fn ivec3() { let vec3 = IVec3::new(1, 2, 3); assert_tokens( &vec3, &[ Token::Struct { name: "IVec3", len: 3, }, Token::Str("x"), Token::I32(1), Token::Str("y"), Token::I32(2), Token::Str("z"), Token::I32(3), Token::StructEnd, ], ); } #[test] fn ivec4() { let vec4 = IVec4::new(1, 2, 3, 4); assert_tokens( &vec4, &[ Token::Struct { name: "IVec4", len: 4, }, Token::Str("x"), Token::I32(1), Token::Str("y"), Token::I32(2), Token::Str("z"), Token::I32(3), Token::Str("w"), Token::I32(4), Token::StructEnd, ], ); } } macro_rules! impl_serde_mat2 { ($name:ident, $vec:ident, $content:ident, $expecting:expr) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { use serde::ser::SerializeTuple; let mut tuple = serializer.serialize_tuple(4)?; tuple.serialize_element(&self.cols[0].x)?; tuple.serialize_element(&self.cols[0].y)?; tuple.serialize_element(&self.cols[1].x)?; tuple.serialize_element(&self.cols[1].y)?; tuple.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct Mat2Visitor; impl<'de> serde::de::Visitor<'de> for Mat2Visitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str($expecting) } #[inline] fn visit_seq(self, mut seq: A) -> Result where A: SeqAccess<'de>, { use serde::de::Error; Ok(Self::Value { cols: [ $vec::new( match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(0, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(1, &self)), }, ), $vec::new( match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(2, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(3, &self)), }, ), ], }) } } deserializer.deserialize_tuple(4, Mat2Visitor) } // @TODO I understand how to implement it in the context of arrays but not matrices // fn deserialize_in_place( // deserializer: D, // place: &mut Self, // ) -> Result<(), >::Error> // where // D: Deserializer<'de>, // { // unimplemented!() // } } }; } macro_rules! impl_serde_mat3 { ($name:ident, $vec:ident, $content:ident, $expecting:expr) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { use serde::ser::SerializeTuple; let mut tuple = serializer.serialize_tuple(9)?; tuple.serialize_element(&self.cols[0].x)?; tuple.serialize_element(&self.cols[0].y)?; tuple.serialize_element(&self.cols[0].z)?; tuple.serialize_element(&self.cols[1].x)?; tuple.serialize_element(&self.cols[1].y)?; tuple.serialize_element(&self.cols[1].z)?; tuple.serialize_element(&self.cols[2].x)?; tuple.serialize_element(&self.cols[2].y)?; tuple.serialize_element(&self.cols[2].z)?; tuple.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct Mat3Visitor; impl<'de> serde::de::Visitor<'de> for Mat3Visitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str($expecting) } #[inline] fn visit_seq(self, mut seq: A) -> Result where A: SeqAccess<'de>, { use serde::de::Error; Ok(Self::Value { cols: [ $vec::new( match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(0, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(1, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(2, &self)), }, ), $vec::new( match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(3, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(4, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(5, &self)), }, ), $vec::new( match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(6, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(7, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(8, &self)), }, ), ], }) } } deserializer.deserialize_tuple(9, Mat3Visitor) } // @TODO I understand how to implement it in the context of arrays but not matrices // fn deserialize_in_place( // deserializer: D, // place: &mut Self, // ) -> Result<(), >::Error> // where // D: Deserializer<'de>, // { // unimplemented!() // } } }; } macro_rules! impl_serde_mat4 { ($name:ident, $vec:ident, $content:ident, $expecting:expr) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { use serde::ser::SerializeTuple; let mut tuple = serializer.serialize_tuple(16)?; tuple.serialize_element(&self.cols[0].x)?; tuple.serialize_element(&self.cols[0].y)?; tuple.serialize_element(&self.cols[0].z)?; tuple.serialize_element(&self.cols[0].w)?; tuple.serialize_element(&self.cols[1].x)?; tuple.serialize_element(&self.cols[1].y)?; tuple.serialize_element(&self.cols[1].z)?; tuple.serialize_element(&self.cols[1].w)?; tuple.serialize_element(&self.cols[2].x)?; tuple.serialize_element(&self.cols[2].y)?; tuple.serialize_element(&self.cols[2].z)?; tuple.serialize_element(&self.cols[2].w)?; tuple.serialize_element(&self.cols[3].x)?; tuple.serialize_element(&self.cols[3].y)?; tuple.serialize_element(&self.cols[3].z)?; tuple.serialize_element(&self.cols[3].w)?; tuple.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct Mat4Visitor; impl<'de> serde::de::Visitor<'de> for Mat4Visitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str($expecting) } #[inline] fn visit_seq(self, mut seq: A) -> Result where A: SeqAccess<'de>, { use serde::de::Error; Ok(Self::Value { cols: [ $vec::new( match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(0, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(1, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(2, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(3, &self)), }, ), $vec::new( match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(4, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(5, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(6, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(7, &self)), }, ), $vec::new( match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(8, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(9, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(10, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(11, &self)), }, ), $vec::new( match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(12, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(13, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(14, &self)), }, match seq.next_element::<$content>()? { Some(val) => val, None => return Err(Error::invalid_length(15, &self)), }, ), ], }) } } deserializer.deserialize_tuple(16, Mat4Visitor) } // @TODO I understand how to implement it in the context of arrays but not matrices // fn deserialize_in_place( // deserializer: D, // place: &mut Self, // ) -> Result<(), >::Error> // where // D: Deserializer<'de>, // { // unimplemented!() // } } }; } // Allowing the $expected macro in case of extending to integer matrices impl_serde_mat2!(Mat2, Vec2, f32, "tuple of 4 floats"); #[cfg(feature = "f64")] impl_serde_mat2!(DMat2, DVec2, f64, "tuple of 4 floats"); impl_serde_mat3!(Mat3, Vec3, f32, "tuple of 9 floats"); #[cfg(feature = "f64")] impl_serde_mat3!(DMat3, DVec3, f64, "tuple of 9 floats"); impl_serde_mat4!(Mat4, Vec4, f32, "tuple of 16 floats"); #[cfg(feature = "f64")] impl_serde_mat4!(DMat4, DVec4, f64, "tuple of 16 floats"); #[cfg(test)] mod mat_serde_tests { use crate::mat::{Mat2, Mat3, Mat4}; use crate::vec::{Vec2, Vec3, Vec4}; use serde_test::{assert_tokens, Token}; #[test] fn mat2() { let mat2 = Mat2::new(Vec2::new(1.0, 2.0), Vec2::new(3.0, 4.0)); assert_tokens( &mat2, &[ Token::Tuple { len: 4 }, Token::F32(1.0), Token::F32(2.0), Token::F32(3.0), Token::F32(4.0), Token::TupleEnd, ], ); } #[test] fn mat3() { let mat3 = Mat3::new( Vec3::new(1.0, 2.0, 3.0), Vec3::new(4.0, 5.0, 6.0), Vec3::new(7.0, 8.0, 9.0), ); assert_tokens( &mat3, &[ Token::Tuple { len: 9 }, Token::F32(1.0), Token::F32(2.0), Token::F32(3.0), Token::F32(4.0), Token::F32(5.0), Token::F32(6.0), Token::F32(7.0), Token::F32(8.0), Token::F32(9.0), Token::TupleEnd, ], ); } #[test] fn mat4() { let mat4 = Mat4::new( Vec4::new(1.0, 2.0, 3.0, 4.0), Vec4::new(5.0, 6.0, 7.0, 8.0), Vec4::new(9.0, 10.0, 11.0, 12.0), Vec4::new(13.0, 14.0, 15.0, 16.0), ); assert_tokens( &mat4, &[ Token::Tuple { len: 16 }, Token::F32(1.0), Token::F32(2.0), Token::F32(3.0), Token::F32(4.0), Token::F32(5.0), Token::F32(6.0), Token::F32(7.0), Token::F32(8.0), Token::F32(9.0), Token::F32(10.0), Token::F32(11.0), Token::F32(12.0), Token::F32(13.0), Token::F32(14.0), Token::F32(15.0), Token::F32(16.0), Token::TupleEnd, ], ); } } macro_rules! impl_serde_bivec2 { ($name:ident) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { let mut state = serializer.serialize_struct(stringify!($name), 1)?; state.serialize_field("xy", &self.xy)?; state.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { enum Field { Xy, } impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct FieldVisitor; impl<'de> Visitor<'de> for FieldVisitor { type Value = Field; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str("`xy`") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { match value { "xy" => Ok(Field::Xy), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } } deserializer.deserialize_identifier(FieldVisitor) } } struct TVisitor; impl<'de> Visitor<'de> for TVisitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str(&["struct ", stringify!($name)].concat()) } fn visit_seq(self, mut seq: V) -> Result where V: SeqAccess<'de>, { let xy = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; Ok(Self::Value::new(xy)) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut xy = None; while let Some(key) = map.next_key()? { match key { Field::Xy => { if xy.is_some() { return Err(serde::de::Error::duplicate_field("xy")); } xy = Some(map.next_value()?); } } } let xy = xy.ok_or_else(|| serde::de::Error::missing_field("xy"))?; Ok(Self::Value::new(xy)) } } const FIELDS: &[&str] = &["xy"]; deserializer.deserialize_struct(stringify!($name), FIELDS, TVisitor) } } }; } macro_rules! impl_serde_bivec3 { ($name:ident) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { let mut state = serializer.serialize_struct(stringify!($name), 3)?; state.serialize_field("xy", &self.xy)?; state.serialize_field("xz", &self.xz)?; state.serialize_field("yz", &self.yz)?; state.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { enum Field { Xy, Xz, Yz, } impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct FieldVisitor; impl<'de> Visitor<'de> for FieldVisitor { type Value = Field; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str("`xy` or `xz` or `yz`") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { match value { "xy" => Ok(Field::Xy), "xz" => Ok(Field::Xz), "yz" => Ok(Field::Yz), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } } deserializer.deserialize_identifier(FieldVisitor) } } struct TVisitor; impl<'de> Visitor<'de> for TVisitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str(&["struct ", stringify!($name)].concat()) } fn visit_seq(self, mut seq: V) -> Result where V: SeqAccess<'de>, { let xy = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; let xz = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; let yz = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(2, &self))?; Ok(Self::Value::new(xy, xz, yz)) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut xy = None; let mut xz = None; let mut yz = None; while let Some(key) = map.next_key()? { match key { Field::Xy => { if xy.is_some() { return Err(serde::de::Error::duplicate_field("xy")); } xy = Some(map.next_value()?); } Field::Xz => { if xz.is_some() { return Err(serde::de::Error::duplicate_field("xz")); } xz = Some(map.next_value()?); } Field::Yz => { if yz.is_some() { return Err(serde::de::Error::duplicate_field("yz")); } yz = Some(map.next_value()?); } } } let xy = xy.ok_or_else(|| serde::de::Error::missing_field("xy"))?; let xz = xz.ok_or_else(|| serde::de::Error::missing_field("xz"))?; let yz = yz.ok_or_else(|| serde::de::Error::missing_field("yz"))?; Ok(Self::Value::new(xy, xz, yz)) } } const FIELDS: &[&str] = &["xy", "xz", "yz"]; deserializer.deserialize_struct(stringify!($name), FIELDS, TVisitor) } } }; } impl_serde_bivec2!(Bivec2); #[cfg(feature = "f64")] impl_serde_bivec2!(DBivec2); impl_serde_bivec3!(Bivec3); #[cfg(feature = "f64")] impl_serde_bivec3!(DBivec3); #[cfg(test)] mod bivec_serde_tests { use crate::bivec::{Bivec2, Bivec3}; use serde_test::{assert_tokens, Token}; #[test] fn bivec2() { let bivec2 = Bivec2::new(0.78); assert_tokens( &bivec2, &[ Token::Struct { name: "Bivec2", len: 1, }, Token::Str("xy"), Token::F32(0.78), Token::StructEnd, ], ); } #[test] fn bivec3() { let bivec3 = Bivec3::new(0.78, 0.36, 0.63); assert_tokens( &bivec3, &[ Token::Struct { name: "Bivec3", len: 3, }, Token::Str("xy"), Token::F32(0.78), Token::Str("xz"), Token::F32(0.36), Token::Str("yz"), Token::F32(0.63), Token::StructEnd, ], ); } } macro_rules! impl_serde_rotor { ($name:ident) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { let mut state = serializer.serialize_struct(stringify!($name), 2)?; state.serialize_field("s", &self.s)?; state.serialize_field("bv", &self.bv)?; state.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { enum Field { S, Bv, } impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct FieldVisitor; impl<'de> Visitor<'de> for FieldVisitor { type Value = Field; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str("`s` or `bv`") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { match value { "s" => Ok(Field::S), "bv" => Ok(Field::Bv), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } } deserializer.deserialize_identifier(FieldVisitor) } } struct TVisitor; impl<'de> Visitor<'de> for TVisitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str(&["struct ", stringify!($name)].concat()) } fn visit_seq(self, mut seq: V) -> Result where V: SeqAccess<'de>, { let s = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; let bv = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; Ok(Self::Value::new(s, bv)) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut s = None; let mut bv = None; while let Some(key) = map.next_key()? { match key { Field::S => { if s.is_some() { return Err(serde::de::Error::duplicate_field("s")); } s = Some(map.next_value()?); } Field::Bv => { if bv.is_some() { return Err(serde::de::Error::duplicate_field("bv")); } bv = Some(map.next_value()?); } } } let s = s.ok_or_else(|| serde::de::Error::missing_field("s"))?; let bv = bv.ok_or_else(|| serde::de::Error::missing_field("bv"))?; Ok(Self::Value::new(s, bv)) } } const FIELDS: &[&str] = &["s", "bv"]; deserializer.deserialize_struct(stringify!($name), FIELDS, TVisitor) } } }; } impl_serde_rotor!(Rotor2); #[cfg(feature = "f64")] impl_serde_rotor!(DRotor2); impl_serde_rotor!(Rotor3); #[cfg(feature = "f64")] impl_serde_rotor!(DRotor3); #[cfg(test)] mod rotor_serde_tests { use crate::bivec::{Bivec2, Bivec3}; use crate::rotor::{Rotor2, Rotor3}; use serde_test::{assert_tokens, Token}; #[test] fn rotor2() { let rotor2 = Rotor2::new(1., Bivec2::new(0.78)); assert_tokens( &rotor2, &[ Token::Struct { name: "Rotor2", len: 2, }, Token::Str("s"), Token::F32(1.), Token::Str("bv"), Token::Struct { name: "Bivec2", len: 1, }, Token::Str("xy"), Token::F32(0.78), Token::StructEnd, Token::StructEnd, ], ); } #[test] fn rotor3() { let rotor3 = Rotor3::new(1., Bivec3::new(0.78, 0.36, 0.63)); assert_tokens( &rotor3, &[ Token::Struct { name: "Rotor3", len: 2, }, Token::Str("s"), Token::F32(1.), Token::Str("bv"), Token::Struct { name: "Bivec3", len: 3, }, Token::Str("xy"), Token::F32(0.78), Token::Str("xz"), Token::F32(0.36), Token::Str("yz"), Token::F32(0.63), Token::StructEnd, Token::StructEnd, ], ); } } macro_rules! impl_serde_isometry { ($name:ident) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { let mut state = serializer.serialize_struct(stringify!($name), 2)?; state.serialize_field("translation", &self.translation)?; state.serialize_field("rotation", &self.rotation)?; state.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { enum Field { Translation, Rotation, } impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct FieldVisitor; impl<'de> Visitor<'de> for FieldVisitor { type Value = Field; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str("`translation` or `rotation`") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { match value { "translation" => Ok(Field::Translation), "rotation" => Ok(Field::Rotation), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } } deserializer.deserialize_identifier(FieldVisitor) } } struct TVisitor; impl<'de> Visitor<'de> for TVisitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str(&["struct ", stringify!($name)].concat()) } fn visit_seq(self, mut seq: V) -> Result where V: SeqAccess<'de>, { let translation = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; let rotation = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; Ok(Self::Value::new(translation, rotation)) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut translation = None; let mut rotation = None; while let Some(key) = map.next_key()? { match key { Field::Translation => { if translation.is_some() { return Err(serde::de::Error::duplicate_field( "translation", )); } translation = Some(map.next_value()?); } Field::Rotation => { if rotation.is_some() { return Err(serde::de::Error::duplicate_field("rotation")); } rotation = Some(map.next_value()?); } } } let translation = translation .ok_or_else(|| serde::de::Error::missing_field("translation"))?; let rotation = rotation.ok_or_else(|| serde::de::Error::missing_field("rotation"))?; Ok(Self::Value::new(translation, rotation)) } } const FIELDS: &[&str] = &["rotation", "translation"]; deserializer.deserialize_struct(stringify!($name), FIELDS, TVisitor) } } }; } impl_serde_isometry!(Isometry2); #[cfg(feature = "f64")] impl_serde_isometry!(DIsometry2); impl_serde_isometry!(Isometry3); #[cfg(feature = "f64")] impl_serde_isometry!(DIsometry3); #[cfg(test)] mod isometry_serde_tests { use crate::rotor::{Rotor2, Rotor3}; use crate::transform::{Isometry2, Isometry3}; use crate::{Vec2, Vec3}; use serde_test::{assert_tokens, Token}; #[test] fn isometry2() { let isometry2 = Isometry2::new(Vec2::new(1., 2.), Rotor2::from_angle(0.)); assert_tokens( &isometry2, &[ Token::Struct { name: "Isometry2", len: 2, }, Token::Str("translation"), Token::Struct { name: "Vec2", len: 2, }, Token::Str("x"), Token::F32(1.), Token::Str("y"), Token::F32(2.), Token::StructEnd, Token::Str("rotation"), Token::Struct { name: "Rotor2", len: 2, }, Token::Str("s"), Token::F32(1.), Token::Str("bv"), Token::Struct { name: "Bivec2", len: 1, }, Token::Str("xy"), Token::F32(0.), Token::StructEnd, Token::StructEnd, Token::StructEnd, ], ); } #[test] fn isometry3() { let isometry3 = Isometry3::new(Vec3::new(1., 2., 3.), Rotor3::from_rotation_xy(0.)); assert_tokens( &isometry3, &[ Token::Struct { name: "Isometry3", len: 2, }, Token::Str("translation"), Token::Struct { name: "Vec3", len: 3, }, Token::Str("x"), Token::F32(1.), Token::Str("y"), Token::F32(2.), Token::Str("z"), Token::F32(3.), Token::StructEnd, Token::Str("rotation"), Token::Struct { name: "Rotor3", len: 2, }, Token::Str("s"), Token::F32(1.), Token::Str("bv"), Token::Struct { name: "Bivec3", len: 3, }, Token::Str("xy"), Token::F32(0.), Token::Str("xz"), Token::F32(0.), Token::Str("yz"), Token::F32(0.), Token::StructEnd, Token::StructEnd, Token::StructEnd, ], ); } } macro_rules! impl_serde_similarity { ($name:ident) => { impl Serialize for $name { fn serialize(&self, serializer: T) -> Result where T: Serializer, { let mut state = serializer.serialize_struct(stringify!($name), 3)?; state.serialize_field("translation", &self.translation)?; state.serialize_field("rotation", &self.rotation)?; state.serialize_field("scale", &self.scale)?; state.end() } } impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { enum Field { Translation, Rotation, Scale, } impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct FieldVisitor; impl<'de> Visitor<'de> for FieldVisitor { type Value = Field; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str("`translation`, `rotation` or `scale`") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { match value { "translation" => Ok(Field::Translation), "rotation" => Ok(Field::Rotation), "scale" => Ok(Field::Scale), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } } deserializer.deserialize_identifier(FieldVisitor) } } struct TVisitor; impl<'de> Visitor<'de> for TVisitor { type Value = $name; fn expecting( &self, formatter: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { formatter.write_str(&["struct ", stringify!($name)].concat()) } fn visit_seq(self, mut seq: V) -> Result where V: SeqAccess<'de>, { let translation = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; let rotation = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?; let scale = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(2, &self))?; Ok(Self::Value::new(translation, rotation, scale)) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut translation = None; let mut rotation = None; let mut scale = None; while let Some(key) = map.next_key()? { match key { Field::Translation => { if translation.is_some() { return Err(serde::de::Error::duplicate_field( "translation", )); } translation = Some(map.next_value()?); } Field::Rotation => { if rotation.is_some() { return Err(serde::de::Error::duplicate_field("rotation")); } rotation = Some(map.next_value()?); } Field::Scale => { if scale.is_some() { return Err(serde::de::Error::duplicate_field("scale")); } scale = Some(map.next_value()?); } } } let translation = translation .ok_or_else(|| serde::de::Error::missing_field("translation"))?; let rotation = rotation.ok_or_else(|| serde::de::Error::missing_field("rotation"))?; let scale = scale.ok_or_else(|| serde::de::Error::missing_field("scale"))?; Ok(Self::Value::new(translation, rotation, scale)) } } const FIELDS: &[&str] = &["rotation", "translation", "scale"]; deserializer.deserialize_struct(stringify!($name), FIELDS, TVisitor) } } }; } impl_serde_similarity!(Similarity2); #[cfg(feature = "f64")] impl_serde_similarity!(DSimilarity2); impl_serde_similarity!(Similarity3); #[cfg(feature = "f64")] impl_serde_similarity!(DSimilarity3); #[cfg(test)] mod similarity_serde_tests { use crate::rotor::{Rotor2, Rotor3}; use crate::transform::{Similarity2, Similarity3}; use crate::{Vec2, Vec3}; use serde_test::{assert_tokens, Token}; #[test] fn similarity2() { let similarity2 = Similarity2::new(Vec2::new(1., 2.), Rotor2::from_angle(0.), 9.); assert_tokens( &similarity2, &[ Token::Struct { name: "Similarity2", len: 3, }, Token::Str("translation"), Token::Struct { name: "Vec2", len: 2, }, Token::Str("x"), Token::F32(1.), Token::Str("y"), Token::F32(2.), Token::StructEnd, Token::Str("rotation"), Token::Struct { name: "Rotor2", len: 2, }, Token::Str("s"), Token::F32(1.), Token::Str("bv"), Token::Struct { name: "Bivec2", len: 1, }, Token::Str("xy"), Token::F32(0.), Token::StructEnd, Token::StructEnd, Token::Str("scale"), Token::F32(9.), Token::StructEnd, ], ); } #[test] fn similarity3() { let similarity3 = Similarity3::new(Vec3::new(1., 2., 3.), Rotor3::from_rotation_xy(0.), 9.); assert_tokens( &similarity3, &[ Token::Struct { name: "Similarity3", len: 3, }, Token::Str("translation"), Token::Struct { name: "Vec3", len: 3, }, Token::Str("x"), Token::F32(1.), Token::Str("y"), Token::F32(2.), Token::Str("z"), Token::F32(3.), Token::StructEnd, Token::Str("rotation"), Token::Struct { name: "Rotor3", len: 2, }, Token::Str("s"), Token::F32(1.), Token::Str("bv"), Token::Struct { name: "Bivec3", len: 3, }, Token::Str("xy"), Token::F32(0.), Token::Str("xz"), Token::F32(0.), Token::Str("yz"), Token::F32(0.), Token::StructEnd, Token::StructEnd, Token::Str("scale"), Token::F32(9.), Token::StructEnd, ], ); } } ultraviolet-0.10.0/src/int.rs000064400000000000000000001253201046102023000142060ustar 00000000000000use crate::*; use std::convert::{TryFrom, TryInto}; use std::ops::*; pub trait MulAdd { /// The resulting type after applying the fused multiply-add. type Output; /// Performs the fused multiply-add operation. fn mul_add(self, a: A, b: B) -> Self::Output; } impl MulAdd for u32 { type Output = u32; fn mul_add(self, a: u32, b: u32) -> Self::Output { (self * a) + b } } impl MulAdd for i32 { type Output = i32; fn mul_add(self, a: i32, b: i32) -> Self::Output { (self * a) + b } } macro_rules! ivec2s { ($(($n:ident, $v3t:ident, $v4t:ident) => $t:ident),+) => { $( /// A set of two coordinates which may be interpreted as a vector or point in 2d space. /// /// Generally this distinction between a point and vector is more of a pain than it is worth /// to distinguish on a type level, however when converting to and from homogeneous /// coordinates it is quite important. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[repr(C)] pub struct $n { pub x: $t, pub y: $t, } impl $n { #[inline] pub const fn new(x: $t, y: $t) -> Self { $n { x, y } } #[inline] pub fn broadcast(val: $t) -> Self { Self::new(val, val) } #[inline] pub fn unit_x() -> Self { $n{ x: 1, y: 0 } } #[inline] pub fn unit_y() -> Self { $n{ x: 0, y: 1 } } /// Create a homogeneous 2d *point* from this vector interpreted as a point, /// meaning the homogeneous component will start with a value of 1. #[inline] pub fn into_homogeneous_point(self) -> $v3t { $v3t { x: self.x, y: self.y, z: 1 } } /// Create a homogeneous 2d *vector* from this vector, /// meaning the homogeneous component will always have a value of 0. #[inline] pub fn into_homogeneous_vector(self) -> $v3t { $v3t { x: self.x, y: self.y, z: 0 } } /// Create a 2d point from a homogeneous 2d *point*, performing /// division by the homogeneous component. This should not be used /// for homogeneous 2d *vectors*, which will have 0 as their /// homogeneous component. #[inline] pub fn from_homogeneous_point(v: $v3t) -> Self { Self { x: v.x / v.z, y: v.y / v.z } } /// Create a 2d vector from homogeneous 2d *vector*, which simply /// discards the homogeneous component. #[inline] pub fn from_homogeneous_vector(v: $v3t) -> Self { v.into() } #[inline] pub fn dot(&self, other: $n) -> $t { (self.x * other.x) + (self.y * other.y) } #[inline] pub fn reflected(&self, normal: $n) -> Self { *self - (2 * self.dot(normal) * normal) } #[inline] pub fn mag(&self) -> $t { (self.mag_sq() as f64).sqrt() as $t } #[inline] pub fn mag_sq(&self) -> $t { (self.x * self.x) + (self.y * self.y) } #[inline] pub fn mul_add(&self, mul: $n, add: $n) -> Self { $n::new( self.x.mul_add(mul.x, add.x), self.y.mul_add(mul.y, add.y), ) } #[inline] pub fn clamp(&mut self, min: Self, max: Self) { self.x = self.x.max(min.x).min(max.x); self.y = self.y.max(min.y).min(max.y); } #[inline] pub fn clamped(mut self, min: Self, max: Self) -> Self { self.clamp(min, max); self } #[inline] pub fn map(&self, mut f: F) -> Self where F: FnMut($t) -> $t { $n::new( f(self.x), f(self.y), ) } #[inline] pub fn apply(&mut self, mut f: F) where F: FnMut($t) -> $t { self.x = f(self.x); self.y = f(self.y); } #[inline] pub fn max_by_component(mut self, other: Self) -> Self { self.x = self.x.max(other.x); self.y = self.y.max(other.y); self } #[inline] pub fn min_by_component(mut self, other: Self) -> Self { self.x = self.x.min(other.x); self.y = self.y.min(other.y); self } #[inline] pub fn component_max(&self) -> $t { self.x.max(self.y) } #[inline] pub fn component_min(&self) -> $t { self.x.min(self.y) } #[inline] pub fn zero() -> Self { Self::broadcast(0) } #[inline] pub fn one() -> Self { Self::broadcast(1) } #[inline] pub fn xyz(&self) -> $v3t { $v3t::new(self.x, self.y, 0) } #[inline] pub fn xyzw(&self) -> $v4t { $v4t::new(self.x, self.y, 0, 0) } #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $t, 2) } } #[inline] pub fn as_array(&self) -> [$t; 2] { use std::convert::TryInto; self.as_slice().try_into().unwrap() } #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const u8, 2 * std::mem::size_of::<$t>()) } } #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $t, 2) } } #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut u8, 2 * std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_ptr(&self) -> *const $t { self as *const $n as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $n as *mut $t } } impl From<[$t; 2]> for $n { #[inline] fn from(comps: [$t; 2]) -> Self { Self::new(comps[0], comps[1]) } } impl From<$n> for [$t; 2] { #[inline] fn from(v: $n) -> Self { [v.x, v.y] } } impl From<&[$t; 2]> for $n { #[inline] fn from(comps: &[$t; 2]) -> Self { Self::from(*comps) } } impl From<&mut [$t; 2]> for $n { #[inline] fn from(comps: &mut [$t; 2]) -> Self { Self::from(*comps) } } impl From<($t, $t)> for $n { #[inline] fn from(comps: ($t, $t)) -> Self { Self::new(comps.0, comps.1) } } impl From<&($t, $t)> for $n { #[inline] fn from(comps: &($t, $t)) -> Self { Self::from(*comps) } } impl From<$n> for ($t, $t) { #[inline] fn from(v: $n) -> Self { (v.x, v.y) } } impl Add for $n { type Output = Self; #[inline] fn add(self, rhs: $n) -> Self { $n::new(self.x + rhs.x, self.y + rhs.y) } } impl AddAssign for $n { #[inline] fn add_assign(&mut self, rhs: $n) { self.x += rhs.x; self.y += rhs.y; } } impl Sub for $n { type Output = Self; #[inline] fn sub(self, rhs: $n) -> Self { $n::new(self.x - rhs.x, self.y - rhs.y) } } impl SubAssign for $n { #[inline] fn sub_assign(&mut self, rhs: $n) { self.x -= rhs.x; self.y -= rhs.y; } } impl Mul for $n { type Output = Self; #[inline] fn mul(self, rhs: $n) -> Self { $n::new(self.x * rhs.x, self.y * rhs.y) } } impl Mul<$n> for $t { type Output = $n; #[inline] fn mul(self, rhs: $n) -> $n { $n::new(self * rhs.x, self * rhs.y) } } impl Mul<$t> for $n { type Output = $n; #[inline] fn mul(self, rhs: $t) -> $n { $n::new(self.x * rhs, self.y * rhs) } } impl MulAssign for $n { #[inline] fn mul_assign(&mut self, rhs: $n) { self.x *= rhs.x; self.y *= rhs.y; } } impl MulAssign<$t> for $n { #[inline] fn mul_assign(&mut self, rhs: $t) { self.x *= rhs; self.y *= rhs; } } impl Div for $n { type Output = Self; #[inline] fn div(self, rhs: $n) -> Self { $n::new(self.x / rhs.x, self.y / rhs.y) } } impl Div<$t> for $n { type Output = $n; #[inline] fn div(self, rhs: $t) -> $n { $n::new(self.x / rhs, self.y / rhs) } } impl DivAssign for $n { #[inline] fn div_assign(&mut self, rhs: $n) { self.x /= rhs.x; self.y /= rhs.y; } } impl DivAssign<$t> for $n { #[inline] fn div_assign(&mut self, rhs: $t) { self.x /= rhs; self.y /= rhs; } } impl Index for $n { type Output = $t; fn index(&self, index: usize) -> &Self::Output { match index { 0 => &self.x, 1 => &self.y, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } impl IndexMut for $n { fn index_mut(&mut self, index: usize) -> &mut Self::Output { match index { 0 => &mut self.x, 1 => &mut self.y, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } )+ }; } macro_rules! ivec3s { ($(($v2t:ident, $n:ident, $v4t:ident) => $t:ident),+) => { /// A set of three coordinates which may be interpreted as a point or vector in 3d space, /// or as a homogeneous 2d vector or point. /// /// Generally this distinction between a point and vector is more of a pain than it is worth /// to distinguish on a type level, however when converting to and from homogeneous /// coordinates it is quite important. $(#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[repr(C)] pub struct $n { pub x: $t, pub y: $t, pub z: $t, } impl $n { #[inline] pub const fn new(x: $t, y: $t, z: $t) -> Self { $n { x, y, z } } #[inline] pub fn broadcast(val: $t) -> Self { Self::new(val, val, val) } #[inline] pub fn unit_x() -> Self { $n{ x: 1, y: 0, z: 0 } } #[inline] pub fn unit_y() -> Self { $n{ x: 0, y: 1, z: 0 } } #[inline] pub fn unit_z() -> Self { $n{ x: 0, y: 0, z: 1 } } #[inline] pub fn cross(&self, other: $n) -> Self { $n::new( self.y.mul_add(other.z, -(self.z as i32) as $t * other.y), self.z.mul_add(other.x, -(self.x as i32) as $t * other.z), self.x.mul_add(other.y, -(self.y as i32) as $t * other.x), ) } /// Create a homogeneous 3d *point* from this vector interpreted as a point, /// meaning the homogeneous component will start with a value of 1. #[inline] pub fn into_homogeneous_point(self) -> $v4t { $v4t { x: self.x, y: self.y, z: self.z, w: 1 } } /// Create a homogeneous 3d *vector* from this vector, /// meaning the homogeneous component will always have a value of 0. #[inline] pub fn into_homogeneous_vector(self) -> $v4t { $v4t { x: self.x, y: self.y, z: self.z, w: 0 } } /// Create a 3d point from a homogeneous 3d *point*, performing /// division by the homogeneous component. This should not be used /// for homogeneous 3d *vectors*, which will have 0 as their /// homogeneous component. #[inline] pub fn from_homogeneous_point(v: $v4t) -> Self { Self { x: v.x / v.w, y: v.y / v.w, z: v.z / v.w } } /// Create a 3d vector from homogeneous 2d *vector*, which simply /// discards the homogeneous component. #[inline] pub fn from_homogeneous_vector(v: $v4t) -> Self { v.into() } #[inline] pub fn dot(&self, other: $n) -> $t { (self.x * other.x) + (self.y * other.y) + (self.z * other.z) } #[inline] pub fn reflect(&mut self, normal: $n) { *self -= 2 * self.dot(normal) * normal; } #[inline] pub fn reflected(&self, normal: $n) -> Self { let mut a = *self; a.reflect(normal); a } #[inline] pub fn mag(&self) -> $t { (self.mag_sq() as f64).sqrt() as $t } #[inline] pub fn mag_sq(&self) -> $t { (self.x * self.x) + (self.y * self.y) + (self.z * self.z) } #[inline] pub fn mul_add(&self, mul: $n, add: $n) -> Self { $n::new( self.x.mul_add(mul.x, add.x), self.y.mul_add(mul.y, add.y), self.z.mul_add(mul.z, add.z), ) } #[inline] pub fn clamp(&mut self, min: Self, max: Self) { self.x = self.x.max(min.x).min(max.x); self.y = self.y.max(min.y).min(max.y); self.z = self.z.max(min.z).min(max.z); } #[inline] pub fn clamped(mut self, min: Self, max: Self) -> Self { self.clamp(min, max); self } #[inline] pub fn map(&self, mut f: F) -> Self where F: FnMut($t) -> $t { $n::new( f(self.x), f(self.y), f(self.z) ) } #[inline] pub fn apply(&mut self, mut f: F) where F: FnMut($t) -> $t { self.x = f(self.x); self.y = f(self.y); self.z = f(self.z); } #[inline] pub fn max_by_component(mut self, other: Self) -> Self { self.x = self.x.max(other.x); self.y = self.y.max(other.y); self.z = self.z.max(other.z); self } #[inline] pub fn min_by_component(mut self, other: Self) -> Self { self.x = self.x.min(other.x); self.y = self.y.min(other.y); self.z = self.z.min(other.z); self } #[inline] pub fn component_max(&self) -> $t { self.x.max(self.y).max(self.z) } #[inline] pub fn component_min(&self) -> $t { self.x.min(self.y).min(self.z) } #[inline] pub fn zero() -> Self { Self::broadcast(0) } #[inline] pub fn one() -> Self { Self::broadcast(1) } #[inline] pub fn xy(&self) -> $v2t { $v2t::new(self.x, self.y) } #[inline] pub fn xyzw(&self) -> $v4t { $v4t::new(self.x, self.y, self.z, 0) } #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $t, 3) } } #[inline] pub fn as_array(&self) -> [$t; 3] { use std::convert::TryInto; self.as_slice().try_into().unwrap() } #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const u8, 3 * std::mem::size_of::<$t>()) } } #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $t, 3) } } #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut u8, 3 * std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_ptr(&self) -> *const $t { self as *const $n as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $n as *mut $t } } impl From<[$t; 3]> for $n { #[inline] fn from(comps: [$t; 3]) -> Self { Self::new(comps[0], comps[1], comps[2]) } } impl From<$n> for [$t; 3] { #[inline] fn from(v: $n) -> Self { [v.x, v.y, v.z] } } impl From<&[$t; 3]> for $n { #[inline] fn from(comps: &[$t; 3]) -> Self { Self::from(*comps) } } impl From<&mut [$t; 3]> for $n { #[inline] fn from(comps: &mut [$t; 3]) -> Self { Self::from(*comps) } } impl From<($t, $t, $t)> for $n { #[inline] fn from(comps: ($t, $t, $t)) -> Self { Self::new(comps.0, comps.1, comps.2) } } impl From<&($t, $t, $t)> for $n { #[inline] fn from(comps: &($t, $t, $t)) -> Self { Self::from(*comps) } } impl From<$n> for ($t, $t, $t) { #[inline] fn from(v: $n) -> Self { (v.x, v.y, v.z) } } impl Add for $n { type Output = Self; #[inline] fn add(self, rhs: $n) -> Self { $n::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) } } impl AddAssign for $n { #[inline] fn add_assign(&mut self, rhs: $n) { self.x += rhs.x; self.y += rhs.y; self.z += rhs.z; } } impl Sub for $n { type Output = Self; #[inline] fn sub(self, rhs: $n) -> Self { $n::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) } } impl SubAssign for $n { #[inline] fn sub_assign(&mut self, rhs: $n) { self.x -= rhs.x; self.y -= rhs.y; self.z -= rhs.z; } } impl Mul for $n { type Output = Self; #[inline] fn mul(self, rhs: $n) -> Self { $n::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z) } } impl Mul<$n> for $t { type Output = $n; #[inline] fn mul(self, rhs: $n) -> $n { $n::new(self * rhs.x, self * rhs.y, self * rhs.z) } } impl Mul<$t> for $n { type Output = $n; #[inline] fn mul(self, rhs: $t) -> $n { $n::new(self.x * rhs, self.y * rhs, self.z * rhs) } } impl MulAssign for $n { #[inline] fn mul_assign(&mut self, rhs: $n) { self.x *= rhs.x; self.y *= rhs.y; self.z *= rhs.z; } } impl MulAssign<$t> for $n { #[inline] fn mul_assign(&mut self, rhs: $t) { self.x *= rhs; self.y *= rhs; self.z *= rhs; } } impl Div for $n { type Output = Self; #[inline] fn div(self, rhs: $n) -> Self { $n::new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z) } } impl Div<$t> for $n { type Output = $n; #[inline] fn div(self, rhs: $t) -> $n { $n::new(self.x / rhs, self.y / rhs, self.z / rhs) } } impl DivAssign for $n { #[inline] fn div_assign(&mut self, rhs: $n) { self.x /= rhs.x; self.y /= rhs.y; self.z /= rhs.z; } } impl DivAssign<$t> for $n { #[inline] fn div_assign(&mut self, rhs: $t) { self.x /= rhs; self.y /= rhs; self.z /= rhs; } } impl Index for $n { type Output = $t; fn index(&self, index: usize) -> &Self::Output { match index { 0 => &self.x, 1 => &self.y, 2 => &self.z, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } impl IndexMut for $n { fn index_mut(&mut self, index: usize) -> &mut Self::Output { match index { 0 => &mut self.x, 1 => &mut self.y, 2 => &mut self.z, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } )+ } } macro_rules! ivec4s { ($($n:ident, $v2t:ident, $v3t:ident => $t:ident),+) => { /// A set of four coordinates which may be interpreted as a point or vector in 4d space, /// or as a homogeneous 3d vector or point. /// /// Generally this distinction between a point and vector is more of a pain than it is worth /// to distinguish on a type level, however when converting to and from homogeneous /// coordinates it is quite important. $(#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[repr(C)] pub struct $n { pub x: $t, pub y: $t, pub z: $t, pub w: $t, } impl $n { #[inline] pub const fn new(x: $t, y: $t, z: $t, w: $t) -> Self { $n { x, y, z, w } } #[inline] pub fn broadcast(val: $t) -> Self { Self::new(val, val, val, val) } #[inline] pub fn unit_x() -> Self { $n{ x: 1, y: 0, z: 0, w: 0 } } #[inline] pub fn unit_y() -> Self { $n{ x: 0, y: 1, z: 0, w: 0 } } #[inline] pub fn unit_z() -> Self { $n{ x: 0, y: 0, z: 1, w: 0 } } #[inline] pub fn unit_w() -> Self { $n{ x: 0, y: 0, z: 0, w: 1 } } #[inline] pub fn dot(&self, other: $n) -> $t { (self.x * other.x) + (self.y * other.y) + (self.z * other.z) + (self.w * other.w) } #[inline] pub fn reflect(&mut self, normal: $n) { *self -= 2 * self.dot(normal) * normal; } #[inline] pub fn reflected(&self, normal: $n) -> Self { let mut a = *self; a.reflect(normal); a } #[inline] pub fn mag(&self) -> $t { (self.mag_sq() as f64).sqrt() as $t } #[inline] pub fn mag_sq(&self) -> $t { (self.x * self.x) + (self.y * self.y) + (self.z * self.z) + (self.w * self.w) } #[inline] pub fn mul_add(&self, mul: $n, add: $n) -> Self { $n::new( self.x.mul_add(mul.x, add.x), self.y.mul_add(mul.y, add.y), self.z.mul_add(mul.z, add.z), self.w.mul_add(mul.w, add.w), ) } #[inline] pub fn clamp(&mut self, min: Self, max: Self) { self.x = self.x.max(min.x).min(max.x); self.y = self.y.max(min.y).min(max.y); self.z = self.z.max(min.z).min(max.z); self.w = self.w.max(min.w).min(max.w); } #[inline] pub fn clamped(mut self, min: Self, max: Self) -> Self { self.clamp(min, max); self } #[inline] pub fn map(&self, mut f: F) -> Self where F: FnMut($t) -> $t { $n::new( f(self.x), f(self.y), f(self.z), f(self.w), ) } #[inline] pub fn apply(&mut self, mut f: F) where F: FnMut($t) -> $t { self.x = f(self.x); self.y = f(self.y); self.z = f(self.z); self.w = f(self.w); } #[inline] pub fn max_by_component(mut self, other: Self) -> Self { self.x = self.x.max(other.x); self.y = self.y.max(other.y); self.z = self.z.max(other.z); self.w = self.w.max(other.w); self } #[inline] pub fn min_by_component(mut self, other: Self) -> Self { self.x = self.x.min(other.x); self.y = self.y.min(other.y); self.z = self.z.min(other.z); self.w = self.w.min(other.w); self } #[inline] pub fn component_max(&self) -> $t { self.x.max(self.y).max(self.z).max(self.w) } #[inline] pub fn component_min(&self) -> $t { self.x.min(self.y).min(self.z).min(self.w) } #[inline] pub fn zero() -> Self { Self::broadcast(0 as $t) } #[inline] pub fn one() -> Self { Self::broadcast(1 as $t) } #[inline] pub fn xy(&self) -> $v2t { $v2t::new(self.x, self.y) } #[inline] pub fn xyz(&self) -> $v3t { $v3t::new(self.x, self.y, self.z) } #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $t, 4) } } #[inline] pub fn as_array(&self) -> [$t; 4] { use std::convert::TryInto; self.as_slice().try_into().unwrap() } #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const u8, 4 * std::mem::size_of::<$t>()) } } #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $t, 4) } } #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut u8, 4 * std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_ptr(&self) -> *const $t { self as *const $n as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $n as *mut $t } } impl From<[$t; 4]> for $n { #[inline] fn from(comps: [$t; 4]) -> Self { Self::new(comps[0], comps[1], comps[2], comps[3]) } } impl From<$n> for [$t; 4] { #[inline] fn from(v: $n) -> Self { [v.x, v.y, v.z, v.w] } } impl From<&[$t; 4]> for $n { #[inline] fn from(comps: &[$t; 4]) -> Self { Self::from(*comps) } } impl From<&mut [$t; 4]> for $n { #[inline] fn from(comps: &mut [$t; 4]) -> Self { Self::from(*comps) } } impl From<($t, $t, $t, $t)> for $n { #[inline] fn from(comps: ($t, $t, $t, $t)) -> Self { Self::new(comps.0, comps.1, comps.2, comps.3) } } impl From<&($t, $t, $t, $t)> for $n { #[inline] fn from(comps: &($t, $t, $t, $t)) -> Self { Self::from(*comps) } } impl From<$n> for ($t, $t, $t, $t) { #[inline] fn from(v: $n) -> Self { (v.x, v.y, v.z, v.w) } } impl Add for $n { type Output = Self; #[inline] fn add(self, rhs: $n) -> Self { $n::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z, self.w + rhs.w) } } impl AddAssign for $n { #[inline] fn add_assign(&mut self, rhs: $n) { self.x += rhs.x; self.y += rhs.y; self.z += rhs.z; self.w += rhs.w; } } impl Sub for $n { type Output = Self; #[inline] fn sub(self, rhs: $n) -> Self { $n::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z, self.w - rhs.w) } } impl SubAssign for $n { #[inline] fn sub_assign(&mut self, rhs: $n) { self.x -= rhs.x; self.y -= rhs.y; self.z -= rhs.z; self.w -= rhs.w; } } impl Mul for $n { type Output = Self; #[inline] fn mul(self, rhs: $n) -> Self { $n::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z, self.w * rhs. w) } } impl Mul<$n> for $t { type Output = $n; #[inline] fn mul(self, rhs: $n) -> $n { $n::new(self * rhs.x, self * rhs.y, self * rhs.z, self * rhs.w) } } impl Mul<$t> for $n { type Output = $n; #[inline] fn mul(self, rhs: $t) -> $n { $n::new(self.x * rhs, self.y * rhs, self.z * rhs, self.w * rhs) } } impl MulAssign for $n { #[inline] fn mul_assign(&mut self, rhs: $n) { self.x *= rhs.x; self.y *= rhs.y; self.z *= rhs.z; self.w *= rhs.w; } } impl MulAssign<$t> for $n { #[inline] fn mul_assign(&mut self, rhs: $t) { self.x *= rhs; self.y *= rhs; self.z *= rhs; self.w *= rhs; } } impl Div for $n { type Output = Self; #[inline] fn div(self, rhs: $n) -> Self { $n::new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z, self.w / rhs.w) } } impl Div<$t> for $n { type Output = $n; #[inline] fn div(self, rhs: $t) -> $n { $n::new(self.x / rhs, self.y / rhs, self.z / rhs, self.w / rhs) } } impl DivAssign for $n { #[inline] fn div_assign(&mut self, rhs: $n) { self.x /= rhs.x; self.y /= rhs.y; self.z /= rhs.z; self.w /= rhs.w; } } impl DivAssign<$t> for $n { #[inline] fn div_assign(&mut self, rhs: $t) { self.x /= rhs; self.y /= rhs; self.z /= rhs; self.w /= rhs; } } impl Index for $n { type Output = $t; fn index(&self, index: usize) -> &Self::Output { match index { 0 => &self.x, 1 => &self.y, 2 => &self.z, 3 => &self.w, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } impl IndexMut for $n { fn index_mut(&mut self, index: usize) -> &mut Self::Output { match index { 0 => &mut self.x, 1 => &mut self.y, 2 => &mut self.z, 3 => &mut self.w, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } impl std::iter::Sum<$n> for $n { fn sum(iter: I) -> Self where I: Iterator { iter.fold($n::zero(), Add::add) } } )+ } } impl Neg for IVec2 { type Output = Self; #[inline] fn neg(self) -> Self::Output { Self { x: -self.x, y: -self.y, } } } impl Neg for IVec3 { type Output = Self; #[inline] fn neg(self) -> Self::Output { Self { x: -self.x, y: -self.y, z: -self.z, } } } impl Neg for IVec4 { type Output = Self; #[inline] fn neg(self) -> Self::Output { Self { x: -self.x, y: -self.y, z: -self.z, w: -self.w, } } } impl From for UVec2 { #[inline] fn from(vec: UVec3) -> Self { Self { x: vec.x, y: vec.y } } } impl From for UVec4 { #[inline] fn from(vec: UVec3) -> Self { Self { x: vec.x, y: vec.y, z: vec.z, w: 0, } } } impl From for UVec3 { #[inline] fn from(vec: UVec4) -> Self { Self { x: vec.x, y: vec.y, z: vec.z, } } } impl From for IVec2 { #[inline] fn from(vec: IVec3) -> Self { Self { x: vec.x, y: vec.y } } } impl From for IVec4 { #[inline] fn from(vec: IVec3) -> Self { Self { x: vec.x, y: vec.y, z: vec.z, w: 0, } } } impl From for IVec3 { #[inline] fn from(vec: IVec4) -> Self { Self { x: vec.x, y: vec.y, z: vec.z, } } } impl TryFrom for IVec3 { type Error = >::Error; fn try_from(rhv: UVec3) -> Result { Ok(Self { x: rhv.x.try_into()?, y: rhv.y.try_into()?, z: rhv.z.try_into()?, }) } } impl TryFrom for UVec3 { type Error = >::Error; fn try_from(rhv: IVec3) -> Result { Ok(Self { x: rhv.x.try_into()?, y: rhv.y.try_into()?, z: rhv.z.try_into()?, }) } } /// A macro to implement abs for unsigned and signed IVecs /// the attributes (x, y, z etc.) must be given in the correct order. /// example macro use: /// impl_abs!(IVec2 => [x, y]); /// or for unsigned /// impl_abs!(UVec2 => [x, y] nosign); macro_rules! impl_abs { ($n:ident => [$($var:ident),*]) => { impl $n { #[inline] pub fn abs(&self) -> Self { Self::new($(self.$var.abs(),)* ) } } }; ($n:ident => [$($var:ident),*] nosign) => { impl $n { #[inline] pub fn abs(&self) -> Self { Self::new($(self.$var,)* ) } } } } ivec2s!((UVec2, UVec3, UVec4) => u32); ivec2s!((IVec2, IVec3, IVec4) => i32); ivec3s!((UVec2, UVec3, UVec4) => u32); ivec3s!((IVec2, IVec3, IVec4) => i32); ivec4s!(UVec4, UVec2, UVec3 => u32); ivec4s!(IVec4, IVec2, IVec3 => i32); impl_abs!(IVec2 => [x, y]); impl_abs!(IVec3 => [x, y, z]); impl_abs!(IVec4 => [x, y, z, w]); impl_abs!(UVec2 => [x, y] nosign); impl_abs!(UVec3 => [x, y, z] nosign); impl_abs!(UVec4 => [x, y, z, w] nosign); ultraviolet-0.10.0/src/interp.rs000064400000000000000000000217671046102023000147270ustar 00000000000000//! Interpolation on types for which it makes sense. use crate::*; /// Pure linear interpolation, i.e. `(1.0 - t) * self + (t) * end`. /// /// For interpolating `Rotor`s with linear interpolation, you almost certainly /// want to normalize the returned `Rotor`. For example, /// ```rs /// let interpolated_rotor = rotor1.lerp(rotor2, 0.5).normalized(); /// ``` /// For most cases (especially where performance is the primary concern, like in /// animation interpolation for games, this 'normalized lerp' or 'nlerp' is probably /// what you want to use. However, there are situations in which you really want /// the interpolation between two `Rotor`s to be of constant angular velocity. In this /// case, check out `Slerp`. pub trait Lerp { fn lerp(&self, end: Self, t: T) -> Self; } macro_rules! impl_lerp { ($($tt:ident => ($($vt:ident),+)),+) => { $($(impl Lerp<$tt> for $vt { /// Linearly interpolate between `self` and `end` by `t` between 0.0 and 1.0. /// i.e. `(1.0 - t) * self + (t) * end`. /// /// For interpolating `Rotor`s with linear interpolation, you almost certainly /// want to normalize the returned `Rotor`. For example, /// ```rs /// let interpolated_rotor = rotor1.lerp(rotor2, 0.5).normalized(); /// ``` /// For most cases (especially where performance is the primary concern, like in /// animation interpolation for games, this 'normalized lerp' or 'nlerp' is probably /// what you want to use. However, there are situations in which you really want /// the interpolation between two `Rotor`s to be of constant angular velocity. In this /// case, check out `Slerp`. #[inline] fn lerp(&self, end: Self, t: $tt) -> Self { *self * ($tt::splat(1.0) - t) + end * t } })+)+ }; } impl_lerp!( f32 => (f32, Vec2, Vec3, Vec4, Bivec2, Bivec3, Rotor2, Rotor3), f32x4 => (f32x4, Vec2x4, Vec3x4, Vec4x4, Bivec2x4, Bivec3x4, Rotor2x4, Rotor3x4), f32x8 => (f32x8, Vec2x8, Vec3x8, Vec4x8, Bivec2x8, Bivec3x8, Rotor2x8, Rotor3x8) ); #[cfg(feature = "f64")] impl_lerp!( f64 => (f64, DVec2, DVec3, DVec4, DBivec2, DBivec3, DRotor2, DRotor3), f64x2 => (f64x2, DVec2x2, DVec3x2, DVec4x2, DBivec2x2, DBivec3x2, DRotor2x2, DRotor3x2), f64x4 => (f64x4, DVec2x4, DVec3x4, DVec4x4, DBivec2x4, DBivec3x4, DRotor2x4, DRotor3x4) ); /// Spherical-linear interpolation. /// /// Basically, interpolation that maintains a constant angular velocity /// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation /// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of /// 3d normal vectors. /// /// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc! pub trait Slerp { fn slerp(&self, end: Self, t: T) -> Self; } macro_rules! impl_slerp_rotor3 { ($($tt:ident => ($($vt:ident),+)),+) => { $($(impl Slerp<$tt> for $vt { /// Spherical-linear interpolation between `self` and `end` based on `t` from 0.0 to 1.0. /// /// `self` and `end` should both be normalized or something bad will happen! /// /// Basically, interpolation that maintains a constant angular velocity /// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation /// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of /// 3d normal vectors. /// /// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc! #[inline] fn slerp(&self, mut end: Self, t: $tt) -> Self { let mut dot = self.dot(end); // make sure interpolation takes shortest path in case dot product is negative if dot < 0.0 { end *= -1.0; dot = -dot; } if dot > 0.9995 { return self.lerp(end, t); } let dot = dot.min(1.0).max(-1.0); let theta_0 = dot.acos(); // angle between inputs let theta = theta_0 * t; // amount of said angle to travel let v2 = (end - (*self * dot)).normalized(); // create orthonormal basis between self and `v2` let (s, c) = theta.sin_cos(); let mut n = *self; n.s = (c * self.s) + (s * v2.s); n.bv.xy = (c * self.bv.xy) + (s * v2.bv.xy); n.bv.xz = (c * self.bv.xz) + (s * v2.bv.xz); n.bv.yz = (c * self.bv.yz) + (s * v2.bv.yz); n } })+)+ }; } impl_slerp_rotor3!( f32 => (Rotor3) ); #[cfg(feature = "f64")] impl_slerp_rotor3!( f64 => (DRotor3) ); macro_rules! impl_slerp_rotor3_wide { ($($tt:ident => ($($vt:ident),+)),+) => { $($(impl Slerp<$tt> for $vt { /// Spherical-linear interpolation between `self` and `end` based on `t` from 0.0 to 1.0. /// /// `self` and `end` should both be normalized or something bad will happen! /// /// The implementation for SIMD types also requires that the two things being interpolated between /// are not exactly aligned, or else the result is undefined. /// /// Basically, interpolation that maintains a constant angular velocity /// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation /// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of /// 3d normal vectors. /// /// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc! #[inline] fn slerp(&self, end: Self, t: $tt) -> Self { let dot = self.dot(end); let dot = dot.min($tt::splat(1.0)).max($tt::splat(-1.0)); let theta_0 = dot.acos(); // angle between inputs let theta = theta_0 * t; // amount of said angle to travel let v2 = (end - (*self * dot)).normalized(); // create orthonormal basis between self and `v2` let (s, c) = theta.sin_cos(); let mut n = *self; n.s = (c * self.s) + (s * v2.s); n.bv.xy = (c * self.bv.xy) + (s * v2.bv.xy); n.bv.xz = (c * self.bv.xz) + (s * v2.bv.xz); n.bv.yz = (c * self.bv.yz) + (s * v2.bv.yz); n } })+)+ }; } impl_slerp_rotor3_wide!( f32x4 => (Rotor3x4), f32x8 => (Rotor3x8) ); #[cfg(feature = "f64")] impl_slerp_rotor3_wide!( f64x2 => (DRotor3x2), f64x4 => (DRotor3x4) ); macro_rules! impl_slerp_gen { ($($tt:ident => ($($vt:ident),+)),+) => { $($(impl Slerp<$tt> for $vt { /// Spherical-linear interpolation between `self` and `end` based on `t` from 0.0 to 1.0. /// /// `self` and `end` should both be normalized or something bad will happen! /// /// The implementation for SIMD types also requires that the two things being interpolated between /// are not exactly aligned, or else the result is undefined. /// /// Basically, interpolation that maintains a constant angular velocity /// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation /// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of /// 3d normal vectors. /// /// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc! #[inline] fn slerp(&self, end: Self, t: $tt) -> Self { let dot = self.dot(end); let dot = dot.min($tt::splat(1.0)).max($tt::splat(-1.0)); let theta_0 = dot.acos(); // angle between inputs let theta = theta_0 * t; // amount of said angle to travel let v2 = (end - (*self * dot)).normalized(); // create orthonormal basis between self and `v2` let (s, c) = theta.sin_cos(); *self * c + v2 * s } })+)+ }; } impl_slerp_gen!( f32 => (Vec2, Vec3, Vec4, Bivec2, Bivec3, Rotor2), f32x4 => (Vec2x4, Vec3x4, Vec4x4, Bivec2x4, Bivec3x4, Rotor2x4), f32x8 => (Vec2x8, Vec3x8, Vec4x8, Bivec2x8, Bivec3x8, Rotor2x8) ); #[cfg(feature = "f64")] impl_slerp_gen!( f64 => (DVec2, DVec3, DVec4, DBivec2, DBivec3, DRotor2), f64x2 => (DVec2x2, DVec3x2, DVec4x2, DBivec2x2, DBivec3x2, DRotor2x2), f64x4 => (DVec2x4, DVec3x4, DVec4x4, DBivec2x4, DBivec3x4, DRotor2x4) ); ultraviolet-0.10.0/src/lib.rs000064400000000000000000000147701046102023000141700ustar 00000000000000//! # `ultraviolet` //! //! This is a crate to computer-graphics and games-related linear and geometric algebra, but *fast*, both in terms //! of productivity and in terms of runtime performance. //! //! In terms of productivity, ultraviolet uses no generics and is designed to be as straightforward //! of an interface as possible, resulting in fast compilation times and clear code. In addition, the //! lack of generics and Rust type-system "hacks" result in clear and concise errors that are easy to //! parse and fix for the user. //! //! In terms of runtime performance, ultraviolet was designed from the start with performance in mind. //! To do so, we provide two separate kinds of each type, each with nearly identical functionality, //! one with usual scalar f32 values, and the other a 'wide' type which uses SIMD f32x4 vectors for //! each value. This design is clear and explicit in intent, and it also allows code to //! take full advantage of SIMD. //! //! The 'wide' types use an "SoA" (Structure of Arrays) architecture //! such that each wide data structure actually contains the data for 4 or 8 of its associated data type and will do any operation //! on all of the simd 'lanes' at the same time. For example, a `Vec3x8` is equivalent to 8 `Vec3`s all bundled together into one //! data structure. //! //! Doing this is potentially *much* (factor of 10) faster than an standard "AoS" (Array of Structs) layout, //! though it does depend on your workload and algorithm requirements. Algorithms must be carefully architected to take full advantage //! of this, and doing so can be easier said than done, especially if your algorithm involves significant branching. //! //! `ultraviolet` was the first Rust math library to be designed in this "AoSoA" manner, though //! `nalgebra` now supports it for several of their data structures as well. //! //! ## Benchmarks //! //! See [`mathbench-rs`](https://github.com/bitshifter/mathbench-rs) for latest benchmarks. //! //! ## Cargo Features //! //! To help further improve build times, `ultraviolet` puts various functionality under feature flags. For example, the 2d and 3d projective geometric algebras //! as well as f64 and integer types are disabled by default. In order to enable them, enable the corresponding crate feature flags in your `Cargo.toml`. For example: //! //! ```toml //! [dependencies] //! ultraviolet = { version = "0.9", features = [ "f64", "int" ] } //! ``` //! //! Will enable the `f64` and `int` features. Here's a list of the available features: //! //! * `f64` – Enable `f64` bit wide floating point support. Naming convention is `D[Type]`, such as `DVec3x4` would be a collection of 4 3d vectors with `f64` precision each. //! * `int` – Enable integer vector types. //! * `bytemuck` – Enable casting of many types to byte arrays, for use with graphics APIs. //! * `mint` – Enable interoperation with other math crates through the `mint` interface. //! * `num-traits` – Enable [identity traits](https://docs.rs/num-traits/latest/num_traits/identities/index.html) for interoperation with other math crates. //! * `serde` – Enable `Serialize` and `Deserialize` implementations for many scalar types. //! //! ## Crate Features //! //! This crate is currently being dogfooded in my ray tracer [`rayn`](https://github.com/termhn/rayn), //! and is being used by various independent Rust game developers for various projects. //! It does what those users have currently needed it to do. //! //! There are a couple relatively unique/novel features in this library, the most important being the use of the Geometric Algebra. //! //! Instead of implementing complex number algebra (for 2d rotations) and Quaternion algebra (for 3d rotations), we use //! Rotors, a concept taken from Geometric Algebra, to represent 2d and 3d rotations. //! //! What this means for the programmer is that you will be using the `Rotor3` type in place of //! a Quaternion, though you can expect it to do basically all the same things that a Quaternion does. In fact, Quaternions //! are directly isomorphic to Rotors (meaning they are in essense the same thing, just formulated differently). The reason this decision was made was twofold: //! first, the derivation of the math is actually quite simple to understand. All the derivations for the code implemented in the Rotor structs in this //! library are written out in the `derivations` folder of the GitHub repo; I derived them manually as part of the implementation. //! //! On the other hand, Quaternions are often basically just seen as black boxes that we programmers use to do rotations because //! they have some nice properties, but that we don't really understand. You can use Rotors this same way, but you can also easily //! understand them. Second is that in some sense they can be seen as 'more correct' than Quaternions. Specifically, they //! facilitate a more proper understanding of rotation as being something that occurs *within a plane* rather than something //! that occurs *around an axis*, as it is generally thought. Finally, Rotors also generalize to 4 and even higher dimensions, //! and if someone wants to they could implement a Rotor4 which retains all the properties of a Rotor3/Quaternion but does rotation //! in 4 dimensions instead, something which simply is not possible to do with Quaternions. //! //! If it's missing something you need it to do, bug me on the [GitHub issue tracker](https://github.com/termhn/ultraviolet/issues) and/or Rust community discord server //! (I'm Fusha there) and I'll try to add it for you, if I believe it fits with the vision of the lib :) #![deny( rust_2018_compatibility, rust_2018_idioms, future_incompatible, nonstandard_style, unused, clippy::all )] extern crate alloc; #[cfg(feature = "serde")] extern crate serde; mod util; pub(crate) use util::Splat; pub mod bivec; #[cfg(feature = "int")] pub mod conversion; #[cfg(feature = "int")] pub mod int; pub mod interp; pub mod mat; pub mod projection; pub mod rotor; pub mod transform; pub mod vec; #[cfg(feature = "serde")] mod impl_serde; #[cfg(feature = "mint")] mod impl_mint; #[cfg(feature = "bytemuck")] mod impl_bytemuck; pub use bivec::*; #[cfg(feature = "int")] pub use conversion::*; #[cfg(feature = "int")] pub use int::*; pub use interp::*; pub use mat::*; pub use rotor::*; pub use transform::*; pub use vec::*; pub use wide::f32x4; pub use wide::f32x8; pub use wide::f64x2; pub use wide::f64x4; pub use wide::f32x4 as m32x4; pub use wide::f32x8 as m32x8; pub use wide::f64x2 as m64x2; pub use wide::f64x4 as m64x4; pub(crate) use wide::{CmpGe, CmpLt}; ultraviolet-0.10.0/src/mat.rs000064400000000000000000002204301046102023000141730ustar 00000000000000//! Square matrices. use std::ops::*; use crate::*; macro_rules! mat2s { ($($n:ident => $m3t:ident, $v3t:ident, $vt:ident, $t:ident),+) => { $(/// A 2x2 square matrix. /// /// Useful for performing linear transformations (rotation, scaling) on 2d vectors. #[derive(Clone, Copy, Debug, PartialEq)] #[repr(C)] pub struct $n { pub cols: [$vt; 2], } derive_default_identity!($n); impl $n { #[inline] pub const fn new(col1: $vt, col2: $vt) -> Self { $n { cols: [col1, col2], } } #[inline] pub fn identity() -> Self { Self::new( $vt::new($t::splat(1.0), $t::splat(0.0)), $vt::new($t::splat(0.0), $t::splat(1.0)), ) } /// Turn this into a homogeneous 2d transformation matrix. #[inline] pub fn into_homogeneous(self) -> $m3t { $m3t::new( self.cols[0].into(), self.cols[1].into(), $v3t::new($t::splat(0.0), $t::splat(0.0), $t::splat(1.0)) ) } #[inline] pub fn transpose(&mut self) { *self = self.transposed(); } #[inline] pub fn transposed(&self) -> Self { let (x0, y0) = self.cols[0].into(); let (x1, y1) = self.cols[1].into(); Self::new( $vt::new(x0, x1), $vt::new(y0, y1), ) } #[inline] pub fn determinant(&self) -> $t { (self.cols[0].x * self.cols[1].y) - (self.cols[1].x * self.cols[0].y) } /// The adjugate of this matrix, i.e. the transpose of /// the cofactor matrix. /// /// This is equivalent to the inverse /// but without dividing by the determinant of the matrix, /// which can be useful in some contexts for better performance. /// /// One such case is when this matrix is interpreted as a /// a homogeneous transformation matrix, in which case uniform scaling will /// not affect the resulting projected 3d version of transformed points or /// vectors. #[inline] pub fn adjugate(&self) -> Self { Self::new( $vt::new(self.cols[1].y, -self.cols[0].y), $vt::new(-self.cols[1].x, self.cols[0].x), ) } /// If this matrix is not currently invertable, this function will return /// an invalid inverse. This status is not checked by the library. #[inline] pub fn inverse(&mut self) { let n = self.inversed(); *self = n; } /// If this matrix is not currently invertable, this function will return /// an invalid inverse. This status is not checked by the library. #[inline] pub fn inversed(&self) -> Self { let det = self.determinant(); let inv_det = $t::splat(1.0) / det; inv_det * self.adjugate() } /// Get the [`core::alloc::Layout`] of `Self` #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$vt>()).unwrap() } /// Interpret `self` as a statically-sized array of its base numeric type #[inline] pub fn as_array(&self) -> &[$t; 4] { let ptr = self as *const $n as *const [$t; 4]; unsafe { &*ptr } } /// Interpret `self` as a statically-sized array of its base numeric type #[inline] pub fn as_mut_array(&mut self) -> &mut [$t; 4] { let ptr = self as *mut $n as *mut [$t; 4]; unsafe { &mut *ptr } } /// Interpret `self` as a statically-sized array of its component (column) vector type #[inline] pub fn as_component_array(&self) -> &[$vt; 2] { let ptr = self as *const $n as *const [$vt; 2]; unsafe { &*ptr } } /// Interpret `self` as a statically-sized array of its component (column) vector type #[inline] pub fn as_mut_component_array(&mut self) -> &mut [$vt; 2] { let ptr = self as *mut $n as *mut [$vt; 2]; unsafe { &mut *ptr } } /// Interpret `self` as a slice of its base numeric type #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $t, 4) } } /// Interpret `self` as a slice of its base numeric type #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $t, 4) } } /// Interpret `self` as a slice of its component (column) vector type #[inline] pub fn as_component_slice(&self) -> &[$vt] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $vt, 2) } } /// Interpret `self` as a slice of its component (column) vector type #[inline] pub fn as_mut_component_slice(&mut self) -> &mut [$vt] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $vt, 2) } } /// Interpret `self` as a slice of bytes #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const u8, 4 * std::mem::size_of::<$t>()) } } /// Interpret `self` as a slice of bytes #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut u8, 4 * std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub const fn as_ptr(&self) -> *const $t { self as *const $n as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $n as *mut $t } } impl Mul for $n { type Output = Self; #[inline] fn mul(self, rhs: Self) -> Self { let sa = self.cols[0]; let sb = self.cols[1]; let oa = rhs.cols[0]; let ob = rhs.cols[1]; Self::new( $vt::new( (sa.x * oa.x) + (sb.x * oa.y), (sa.y * oa.x) + (sb.y * oa.y), ), $vt::new( (sa.x * ob.x) + (sb.x * ob.y), (sa.y * ob.x) + (sb.y * ob.y), ), ) } } impl Mul<$vt> for $n { type Output = $vt; #[inline] fn mul(self, rhs: $vt) -> $vt { let a = self.cols[0]; let b = self.cols[1]; $vt::new( (a.x * rhs.x) + (b.x * rhs.y), (a.y * rhs.x) + (b.y * rhs.y), ) } } impl Mul<$t> for $n { type Output = $n; #[inline] fn mul(self, rhs: $t) -> $n { $n::new( self.cols[0] * rhs, self.cols[1] * rhs, ) } } impl Mul<$n> for $t { type Output = $n; #[inline] fn mul(self, rhs: $n) -> $n { $n::new( rhs.cols[0] * self, rhs.cols[1] * self, ) } } impl Add for $n { type Output = Self; #[inline] fn add(self, rhs: $n) -> Self { $n::new( self.cols[0] + rhs.cols[0], self.cols[1] + rhs.cols[1], ) } } impl AddAssign for $n { #[inline] fn add_assign(&mut self, rhs: $n) { *self = *self + rhs; } } impl From<[$t; 4]> for $n { #[inline] fn from(comps: [$t; 4]) -> Self { Self::new( $vt::new(comps[0], comps[1]), $vt::new(comps[2], comps[3]) ) } } impl From<[[$t; 2]; 2]> for $n { #[inline] fn from(comps: [[$t; 2]; 2]) -> Self { Self::new( $vt::new(comps[0][0], comps[0][1]), $vt::new(comps[1][0], comps[1][1]) ) } } impl From<$n> for [[$t; 2]; 2] { #[inline] fn from(mat2: $n) -> Self { [ [mat2.cols[0].x, mat2.cols[0].y], [mat2.cols[1].x, mat2.cols[1].y], ] } } impl From<&[$t; 4]> for $n { #[inline] fn from(comps: &[$t; 4]) -> Self { Self::from(*comps) } } impl Index for $n { type Output = $vt; fn index(&self, index: usize) -> &Self::Output { &self.cols[index] } } impl IndexMut for $n { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.cols[index] } } )+ } } mat2s!( Mat2 => Mat3, Vec3, Vec2, f32, Mat2x4 => Mat3x4, Vec3x4, Vec2x4, f32x4, Mat2x8 => Mat3x8, Vec3x8, Vec2x8, f32x8 ); #[cfg(feature = "f64")] mat2s!( DMat2 => DMat3, DVec3, DVec2, f64, DMat2x2 => DMat3x2, DVec3x2, DVec2x2, f64x2, DMat2x4 => DMat3x4, DVec3x4, DVec2x4, f64x4 ); macro_rules! mat3s { ($($n:ident => $rt:ident, $bt:ident, $m4t:ident, $v4t:ident, $v2t:ident, $vt:ident, $t:ident),+) => { $(/// A 3x3 square matrix. /// /// Useful for performing linear transformations (rotation, scaling) on 3d vectors, /// or for performing arbitrary transformations (linear + translation, projection, etc) /// on homogeneous 2d vectors #[derive(Clone, Copy, Debug, PartialEq)] #[repr(C)] pub struct $n { pub cols: [$vt; 3], } derive_default_identity!($n); impl $n { #[inline] pub const fn new(col1: $vt, col2: $vt, col3: $vt) -> Self { $n { cols: [col1, col2, col3], } } /// Assumes homogeneous 2d coordinates. #[inline] pub fn from_translation(trans: $v2t) -> Self { Self::new( $vt::new($t::splat(1.0), $t::splat(0.0), $t::splat(0.0)), $vt::new($t::splat(0.0), $t::splat(1.0), $t::splat(0.0)), $vt::new(trans.x, trans.y, $t::splat(1.0))) } /// Assumes homogeneous 2d coordinates. #[inline] pub fn from_scale_homogeneous(scale: $t) -> Self { let zero = $t::splat(0.0); Self::new( $vt::new(scale, zero, zero), $vt::new(zero, scale, zero), $vt::new(zero, zero, $t::splat(1.0)), ) } /// Assumes homogeneous 2d coordinates. #[inline] pub fn from_nonuniform_scale_homogeneous(scale: $v2t) -> Self { let zero = $t::splat(0.0); Self::new( $vt::new(scale.x, zero, zero), $vt::new(zero, scale.y, zero), $vt::new(zero, zero, $t::splat(1.0)), ) } /// Builds a homogeneous 2d rotation matrix (in the xy plane) from a given angle in radians. #[inline] pub fn from_rotation_homogeneous(angle: $t) -> Self { let (s, c) = angle.sin_cos(); let zero = $t::splat(0.0); Self::new( $vt::new(c, s, zero), $vt::new(-s, c, zero), $vt::new(zero, zero, $t::splat(1.0)), ) } #[inline] pub fn from_scale(scale: $t) -> Self { let zero = $t::splat(0.0); Self::new( $vt::new(scale, zero, zero), $vt::new(zero, scale, zero), $vt::new(zero, zero, scale), ) } #[inline] pub fn from_nonuniform_scale(scale: $vt) -> Self { let zero = $t::splat(0.0); Self::new( $vt::new(scale.x, zero, zero), $vt::new(zero, scale.y, zero), $vt::new(zero, zero, scale.z), ) } #[inline] pub fn identity() -> Self { Self::new( $vt::new($t::splat(1.0), $t::splat(0.0), $t::splat(0.0)), $vt::new($t::splat(0.0), $t::splat(1.0), $t::splat(0.0)), $vt::new($t::splat(0.0), $t::splat(0.0), $t::splat(1.0))) } /// Angles are applied in the order roll -> pitch -> yaw. /// /// - Yaw is rotation inside the xz plane ("around the y axis") /// - Pitch is rotation inside the yz plane ("around the x axis") /// - Roll is rotation inside the xy plane ("around the z axis") #[inline] #[allow(unused_variables)] pub fn from_euler_angles(roll: $t, pitch: $t, yaw: $t) -> Self { let (sin_yaw, cos_yaw) = yaw.sin_cos(); let (sin_pitch, cos_pitch) = pitch.sin_cos(); let (sin_roll, cos_roll) = roll.sin_cos(); let sin_pitch_sin_roll = sin_pitch * sin_roll; let sin_pitch_cos_roll = sin_pitch * cos_roll; let m00 = cos_yaw * cos_roll + sin_pitch * sin_yaw* sin_roll; let m10 = cos_pitch * sin_roll; let m20 = -cos_roll * sin_yaw + cos_yaw * sin_pitch * sin_roll; let m01 = cos_roll * sin_pitch * sin_yaw - cos_yaw * sin_roll; let m11 = cos_pitch * cos_roll; let m21 = cos_yaw * cos_roll * sin_pitch + sin_yaw * sin_roll; let m02 = cos_pitch * sin_yaw; let m12 = -sin_pitch; let m22 = cos_pitch * cos_yaw; // think transposed as arguments are columns Self::new( $vt::new(m00, m10, m20), $vt::new(m01, m11, m21), $vt::new(m02, m12, m22), ) } /// Create a new rotation matrix from a rotation "around the x axis". This is /// here as a convenience function for users coming from other libraries; it is /// more proper to think of this as a rotation *in the yz plane*. #[inline] pub fn from_rotation_x(angle: $t) -> Self { let (sin, cos) = angle.sin_cos(); let zero = $t::splat(0.0); let one = $t::splat(1.0); // think transposed as arguments are columns Self::new( $vt::new(one, zero, zero), $vt::new(zero, cos, sin), $vt::new(zero, -sin, cos), ) } /// Create a new rotation matrix from a rotation "around the y axis". This is /// here as a convenience function for users coming from other libraries; it is /// more proper to think of this as a rotation *in the xz plane*. #[inline] pub fn from_rotation_y(angle: $t) -> Self { let (sin, cos) = angle.sin_cos(); let zero = $t::splat(0.0); let one = $t::splat(1.0); // think transposed as arguments are columns Self::new( $vt::new(cos, zero, -sin), $vt::new(zero, one, zero), $vt::new(sin, zero, cos), ) } /// Create a new rotation matrix from a rotation "around the z axis". This is /// here as a convenience function for users coming from other libraries; it is /// more proper to think of this as a rotation *in the xy plane*. #[inline] pub fn from_rotation_z(angle: $t) -> Self { let (sin, cos) = angle.sin_cos(); let zero = $t::splat(0.0); let one = $t::splat(1.0); // think transposed as arguments are columns Self::new( $vt::new(cos, sin, zero), $vt::new(-sin, cos, zero), $vt::new(zero, zero, one), ) } /// Create a new rotation matrix from a rotation around the given axis. /// This is here as a convenience function for users coming from other libraries. #[inline] pub fn from_rotation_around(axis: $vt, angle: $t) -> Self { let (sin, cos) = angle.sin_cos(); let mul = $t::splat(1.0) - cos; let x_sin = axis.x * sin; let y_sin = axis.y * sin; let z_sin = axis.z * sin; let xy_mul = axis.x * axis.y * mul; let xz_mul = axis.x * axis.z * mul; let yz_mul = axis.y * axis.z * mul; let m00 = (axis.x * axis.x).mul_add(mul, cos); let m10 = xy_mul + z_sin; let m20 = xz_mul - y_sin; let m01 = xy_mul - z_sin; let m11 = (axis.y * axis.y).mul_add(mul, cos); let m21 = yz_mul + x_sin; let m02 = xz_mul + y_sin; let m12 = yz_mul - x_sin; let m22 = (axis.z * axis.z).mul_add(mul, cos); // think transposed as arguments are columns Self::new( $vt::new(m00, m10, m20), $vt::new(m01, m11, m21), $vt::new(m02, m12, m22), ) } /// Construct a rotation matrix given a bivector which defines a plane and rotation orientation, /// and a rotation angle. /// /// `plane` must be normalized! /// /// This is the equivalent of an axis-angle rotation. #[inline] pub fn from_angle_plane(angle: $t, plane: $bt) -> Self { $rt::from_angle_plane(angle, plane).into_matrix() } #[inline] pub fn into_homogeneous(self) -> $m4t { let zero = $t::splat(0.0); let one = $t::splat(1.0); $m4t::new( self.cols[0].into(), self.cols[1].into(), self.cols[2].into(), $v4t::new(zero, zero, zero, one) ) } #[inline] pub fn determinant(&self) -> $t { self.cols[0].x.mul_add( self.cols[1].y.mul_add(self.cols[2].z, -(self.cols[2].y * self.cols[1].z)), -(self.cols[1].x.mul_add( self.cols[0].y.mul_add(self.cols[2].z, -(self.cols[2].y * self.cols[0].z)), -(self.cols[2].x * self.cols[0].y.mul_add(self.cols[1].z, -(self.cols[1].y * self.cols[0].z))) )) ) } /// The adjugate of this matrix, i.e. the transpose of /// the cofactor matrix. /// /// This is equivalent to the inverse /// but without dividing by the determinant of the matrix, /// which can be useful in some contexts for better performance. /// /// One such case is when this matrix is interpreted as a /// a homogeneous transformation matrix, in which case uniform scaling will /// not affect the resulting projected 3d version of transformed points or /// vectors. #[inline] pub fn adjugate(&self) -> Self { let x = self.cols[1].cross(self.cols[2]); let y = self.cols[2].cross(self.cols[0]); let z = self.cols[0].cross(self.cols[1]); Self::new(x, y, z).transposed() } /// If this matrix is not currently invertable, this function will return /// an invalid inverse. This status is not checked by the library. #[inline] pub fn inverse(&mut self) { *self = self.inversed(); } /// If this matrix is not currently invertable, this function will return /// an invalid inverse. This status is not checked by the library. #[inline] pub fn inversed(&self) -> Self { let adjugate = self.adjugate(); let det = self.determinant(); let inv_det = $t::splat(1.0) / det; inv_det * adjugate } #[inline] pub fn transpose(&mut self) { *self = self.transposed(); } #[inline] pub fn transposed(&self) -> Self { let (x0, y0, z0) = self.cols[0].into(); let (x1, y1, z1) = self.cols[1].into(); let (x2, y2, z2) = self.cols[2].into(); Self::new( $vt::new(x0, x1, x2), $vt::new(y0, y1, y2), $vt::new(z0, z1, z2), ) } /// Transform a Vec2 by self, interpreting it as a vector. #[inline] pub fn transform_vec2(&self, vec: $v2t) -> $v2t { (*self * vec.into_homogeneous_vector()).truncated() } /// Transform a Vec2 by self, interpreting it as a point. #[inline] pub fn transform_point2(&self, point: $v2t) -> $v2t { (*self * point.into_homogeneous_point()).normalized_homogeneous_point().truncated() } /// Get the [`core::alloc::Layout`] of `Self` #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } /// Interpret `self` as a statically sized array of the base numeric type. #[inline] pub fn as_array(&self) -> &[$t; 9] { let ptr = self as *const $n as *const [$t; 9]; unsafe { &*ptr } } /// Interpret `self` as a statically sized array of the base numeric type. #[inline] pub fn as_mut_array(&mut self) -> &mut [$t; 9] { let ptr = self as *mut $n as *mut [$t; 9]; unsafe { &mut *ptr } } /// Interpret `self` as a statically sized array of the component (column) vectors. #[inline] pub fn as_component_array(&self) -> &[$vt; 3] { let ptr = self as *const $n as *const [$vt; 3]; unsafe { &*ptr } } /// Interpret `self` as a statically sized array of the component (column) vectors. #[inline] pub fn as_mut_component_array(&mut self) -> &mut [$vt; 3] { let ptr = self as *mut $n as *mut [$vt; 3]; unsafe { &mut *ptr } } /// Interpret `self` as a slice of the base numeric type. #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $t, 9) } } /// Interpret `self` as a slice of the component (column) vectors. #[inline] pub fn as_component_slice(&self) -> &[$vt] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $vt, 3) } } /// Interpret `self` as a slice of bytes. #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const u8, 9 * std::mem::size_of::<$t>()) } } /// Interpret `self` as a slice of the base numeric type. #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $t, 9) } } /// Interpret `self` as a slice of the component (column) vectors. #[inline] pub fn as_mut_component_slice(&mut self) -> &mut [$vt] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $vt, 3) } } /// Interpret `self` as a slice of bytes. #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut u8, 9 * std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub const fn as_ptr(&self) -> *const $t { self as *const $n as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $n as *mut $t } } impl Mul for $n { type Output = Self; #[inline] fn mul(self, rhs: Self) -> Self { let sa = self.cols[0]; let sb = self.cols[1]; let sc = self.cols[2]; let oa = rhs.cols[0]; let ob = rhs.cols[1]; let oc = rhs.cols[2]; Self::new( $vt::new( (sa.x * oa.x) + (sb.x * oa.y) + (sc.x * oa.z), (sa.y * oa.x) + (sb.y * oa.y) + (sc.y * oa.z), (sa.z * oa.x) + (sb.z * oa.y) + (sc.z * oa.z), ), $vt::new( (sa.x * ob.x) + (sb.x * ob.y) + (sc.x * ob.z), (sa.y * ob.x) + (sb.y * ob.y) + (sc.y * ob.z), (sa.z * ob.x) + (sb.z * ob.y) + (sc.z * ob.z), ), $vt::new( (sa.x * oc.x) + (sb.x * oc.y) + (sc.x * oc.z), (sa.y * oc.x) + (sb.y * oc.y) + (sc.y * oc.z), (sa.z * oc.x) + (sb.z * oc.y) + (sc.z * oc.z), ), ) } } impl Mul<$vt> for $n { type Output = $vt; #[inline] fn mul(self, rhs: $vt) -> $vt { let a = self.cols[0]; let b = self.cols[1]; let c = self.cols[2]; $vt::new( (a.x * rhs.x) + (b.x * rhs.y) + (c.x * rhs.z), (a.y * rhs.x) + (b.y * rhs.y) + (c.y * rhs.z), (a.z * rhs.x) + (b.z * rhs.y) + (c.z * rhs.z), ) } } impl Mul<$t> for $n { type Output = $n; #[inline] fn mul(self, rhs: $t) -> $n { $n::new( self.cols[0] * rhs, self.cols[1] * rhs, self.cols[2] * rhs, ) } } impl Mul<$n> for $t { type Output = $n; #[inline] fn mul(self, rhs: $n) -> $n { $n::new( rhs.cols[0] * self, rhs.cols[1] * self, rhs.cols[2] * self, ) } } impl Add for $n { type Output = Self; #[inline] fn add(self, rhs: $n) -> Self { $n::new( self.cols[0] + rhs.cols[0], self.cols[1] + rhs.cols[1], self.cols[2] + rhs.cols[2], ) } } impl AddAssign for $n { #[inline] fn add_assign(&mut self, rhs: $n) { *self = *self + rhs; } } impl From<[$t; 9]> for $n { #[inline] fn from(comps: [$t; 9]) -> Self { Self::new( $vt::new(comps[0], comps[1], comps[2]), $vt::new(comps[3], comps[4], comps[5]), $vt::new(comps[6], comps[7], comps[8]) ) } } impl From<[[$t; 3]; 3]> for $n { #[inline] fn from(comps: [[$t; 3]; 3]) -> Self { Self::new( $vt::new(comps[0][0], comps[0][1], comps[0][2]), $vt::new(comps[1][0], comps[1][1], comps[1][2]), $vt::new(comps[2][0], comps[2][1], comps[2][2]) ) } } impl From<$n> for [[$t; 3]; 3] { #[inline] fn from(mat3: $n) -> Self { [ [mat3.cols[0].x, mat3.cols[0].y, mat3.cols[0].z], [mat3.cols[1].x, mat3.cols[1].y, mat3.cols[1].z], [mat3.cols[2].x, mat3.cols[2].y, mat3.cols[2].z] ] } } impl From<&[$t; 9]> for $n { #[inline] fn from(comps: &[$t; 9]) -> Self { Self::from(*comps) } } impl Index for $n { type Output = $vt; fn index(&self, index: usize) -> &Self::Output { &self.cols[index] } } impl IndexMut for $n { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.cols[index] } } )+ } } mat3s!( Mat3 => Rotor3, Bivec3, Mat4, Vec4, Vec2, Vec3, f32, Mat3x4 => Rotor3x4, Bivec3x4, Mat4x4, Vec4x4, Vec2x4, Vec3x4, f32x4, Mat3x8 => Rotor3x8, Bivec3x8, Mat4x8, Vec4x8, Vec2x8, Vec3x8, f32x8 ); #[cfg(feature = "f64")] mat3s!( DMat3 => DRotor3, DBivec3, DMat4, DVec4, DVec2, DVec3, f64, DMat3x2 => DRotor3x2, DBivec3x2, DMat4x2, DVec4x2, DVec2x2, DVec3x2, f64x2, DMat3x4 => DRotor3x4, DBivec3x4, DMat4x4, DVec4x4, DVec2x4, DVec3x4, f64x4 ); macro_rules! impl_mat3 { ($($mt:ident, $t:ident, $rt:ident, $bt:ident),+) => { $(impl $mt { /// If `self` is a rotation matrix, return a `Rotor3` representing the same rotation. /// /// If `self` is not a rotation matrix, the returned value is a `Rotor3` with undefied /// properties. The fact that `self` is a rotation matrix is not checked by the /// library. pub fn into_rotor3(self) -> $rt { // Adapted from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ let w = ($t::splat(1.0) + self[0][0] + self[1][1] + self[2][2]).max($t::splat(0.0)).sqrt() * $t::splat(0.5); let yz = { let s = ($t::splat(1.0) + self[0][0] - self[1][1] - self[2][2]).max($t::splat(0.0)).sqrt() * $t::splat(0.5); s.copysign(self[2][1] - self[1][2]) }; let xz = { let s = ($t::splat(1.0) - self[0][0] + self[1][1] - self[2][2]).max($t::splat(0.0)).sqrt() * $t::splat(0.5); s.copysign(self[2][0] - self[0][2]) }; let xy = { let s = ($t::splat(1.0) - self[0][0] - self[1][1] + self[2][2]).max($t::splat(0.0)).sqrt() * $t::splat(0.5); s.copysign(self[1][0] - self[0][1]) }; $rt::new(w, $bt::new(xy, xz, yz)) } })+ } } impl_mat3!(Mat3, f32, Rotor3, Bivec3); #[cfg(feature = "f64")] impl_mat3!(DMat3, f64, DRotor3, DBivec3); macro_rules! impl_mat3_wide { ($($mt:ident => $t:ident, $rt:ident, $bt:ident),+) => { $(impl $mt { /// If `self` is a rotation matrix, return a `Rotor3` representing the same rotation. /// /// If `self` is not a rotation matrix, the returned value is a `Rotor3` with undefied /// properties. The fact that `self` is a rotation matrix is not checked by the /// library. pub fn into_rotor3(self) -> $rt { // Adapted from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ let w = ($t::splat(1.0) + self[0][0] + self[1][1] + self[2][2]).max($t::splat(0.0)).sqrt() * $t::splat(0.5); let yz = { let s = ($t::splat(1.0) + self[0][0] - self[1][1] - self[2][2]).max($t::splat(0.0)).sqrt() * $t::splat(0.5); s.flip_signs(self[2][1] - self[1][2]) }; let xz = { let s = ($t::splat(1.0) - self[0][0] + self[1][1] - self[2][2]).max($t::splat(0.0)).sqrt() * $t::splat(0.5); s.flip_signs(self[2][0] - self[0][2]) }; let xy = { let s = ($t::splat(1.0) - self[0][0] - self[1][1] + self[2][2]).max($t::splat(0.0)).sqrt() * $t::splat(0.5); s.flip_signs(self[1][0] - self[0][1]) }; $rt::new(w, $bt::new(xy, xz, yz)) } })+ } } impl_mat3_wide!(Mat3x4 => f32x4, Rotor3x4, Bivec3x4, Mat3x8 => f32x8, Rotor3x8, Bivec3x8); #[cfg(feature = "f64")] impl_mat3_wide!(DMat3x2 => f64x2, DRotor3x2, DBivec3x2, DMat3x4 => f64x4, DRotor3x4, DBivec3x4); macro_rules! mat4s { ($($n:ident => $rt:ident, $bt:ident, $vt:ident, $v3t:ident, $m3t:ident, $i3t:ident, $t:ident),+) => { $(/// A 4x4 square matrix. /// /// Useful for performing linear transformations (rotation, scaling) on 4d vectors, /// or for performing arbitrary transformations (linear + translation, projection, etc) /// on homogeneous 3d vectors. /// /// Note that most constructors assume that the matrix will be used as a homogeneous 3d /// transformation matrix. #[derive(Clone, Copy, Debug, PartialEq)] #[repr(C)] pub struct $n { pub cols: [$vt; 4], } derive_default_identity!($n); impl $n { #[inline] pub const fn new(col1: $vt, col2: $vt, col3: $vt, col4: $vt) -> Self { $n { cols: [col1, col2, col3, col4], } } #[inline] pub fn identity() -> Self { Self::new( $vt::new($t::splat(1.0), $t::splat(0.0), $t::splat(0.0), $t::splat(0.0)), $vt::new($t::splat(0.0), $t::splat(1.0), $t::splat(0.0), $t::splat(0.0)), $vt::new($t::splat(0.0), $t::splat(0.0), $t::splat(1.0), $t::splat(0.0)), $vt::new($t::splat(0.0), $t::splat(0.0), $t::splat(0.0), $t::splat(1.0))) } /// Assumes homogeneous 3d coordinates. #[inline] pub fn from_translation(trans: $v3t) -> Self { Self::new( $vt::new($t::splat(1.0), $t::splat(0.0), $t::splat(0.0), $t::splat(0.0)), $vt::new($t::splat(0.0), $t::splat(1.0), $t::splat(0.0), $t::splat(0.0)), $vt::new($t::splat(0.0), $t::splat(0.0), $t::splat(1.0), $t::splat(0.0)), $vt::new(trans.x, trans.y, trans.z, $t::splat(1.0))) } /// Assumes homogeneous 3d coordinates. #[inline] pub fn from_scale(scale: $t) -> Self { let zero = $t::splat(0.0); Self::new( $vt::new(scale, zero, zero, zero), $vt::new(zero, scale, zero, zero), $vt::new(zero, zero, scale, zero), $vt::new(zero, zero, zero, $t::splat(1.0)), ) } /// Assumes homogeneous 3d coordinates. #[inline] pub fn from_nonuniform_scale(scale: $v3t) -> Self { let zero = $t::splat(0.0); Self::new( $vt::new(scale.x, zero, zero, zero), $vt::new(zero, scale.y, zero, zero), $vt::new(zero, zero, scale.z, zero), $vt::new(zero, zero, zero, $t::splat(1.0)), ) } /// Full 4d diagonal matrix. #[inline] pub fn from_scale_4d(scale: $t) -> Self { let zero = $t::splat(0.0); Self::new( $vt::new(scale, zero, zero, zero), $vt::new(zero, scale, zero, zero), $vt::new(zero, zero, scale, zero), $vt::new(zero, zero, zero, scale), ) } /// Full 4d nonuniform scaling matrix. #[inline] pub fn from_nonuniform_scale_4d(scale: $vt) -> Self { let zero = $t::splat(0.0); Self::new( $vt::new(scale.x, zero, zero, zero), $vt::new(zero, scale.y, zero, zero), $vt::new(zero, zero, scale.z, zero), $vt::new(zero, zero, zero, scale.w), ) } /// Angles are applied in the order roll -> pitch -> yaw /// /// - Roll is rotation inside the xy plane ("around the z axis") /// - Pitch is rotation inside the yz plane ("around the x axis") /// - Yaw is rotation inside the xz plane ("around the y axis") /// /// Assumes homogeneous 3d coordinates. /// /// **Important: This function assumes a right-handed, y-up coordinate space** where: /// * +X axis points *right* /// * +Y axis points *up* /// * +Z axis points *towards the viewer* (i.e. out of the screen) /// /// This means that you may see unexpected behavior when used with OpenGL or DirectX /// as they use a different coordinate system. You should use the appropriate /// projection matrix in ```projection``` module to fit your use case to remedy this. #[inline] pub fn from_euler_angles(roll: $t, pitch: $t, yaw: $t) -> Self { let (sin_yaw, cos_yaw) = yaw.sin_cos(); let (sin_pitch, cos_pitch) = pitch.sin_cos(); let (sin_roll, cos_roll) = roll.sin_cos(); let zero = $t::splat(0.0); let m00 = cos_yaw * cos_roll + sin_pitch * sin_yaw * sin_roll; let m10 = cos_pitch * sin_roll; let m20 = -cos_roll * sin_yaw + cos_yaw * sin_pitch * sin_roll; let m01 = cos_roll * sin_pitch * sin_yaw - cos_yaw * sin_roll; let m11 = cos_pitch * cos_roll; let m21 = cos_yaw * cos_roll * sin_pitch + sin_yaw * sin_roll; let m02 = cos_pitch * sin_yaw; let m12 = -sin_pitch; let m22 = cos_pitch * cos_yaw; // think transposed as arguments are columns Self::new( $vt::new(m00, m10, m20, zero), $vt::new(m01, m11, m21, zero), $vt::new(m02, m12, m22, zero), $vt::new(zero, zero, zero, $t::splat(1.0)) ) } /// Create a new rotation matrix from a rotation "around the x axis". This is /// here as a convenience function for users coming from other libraries; it is /// more proper to think of this as a rotation *in the yz plane*. /// /// Assumes homogeneous 3d coordinates. #[inline] pub fn from_rotation_x(angle: $t) -> Self { let (sin, cos) = angle.sin_cos(); let zero = $t::splat(0.0); let one = $t::splat(1.0); // think transposed as arguments are columns Self::new( $vt::new(one, zero, zero, zero), $vt::new(zero, cos, sin, zero), $vt::new(zero, -sin, cos, zero), $vt::new(zero, zero, zero, one), ) } /// Create a new rotation matrix from a rotation "around the y axis". This is /// here as a convenience function for users coming from other libraries; it is /// more proper to think of this as a rotation *in the xz plane*. /// /// Assumes homogeneous 3d coordinates. #[inline] pub fn from_rotation_y(angle: $t) -> Self { let (sin, cos) = angle.sin_cos(); let zero = $t::splat(0.0); let one = $t::splat(1.0); // think transposed as arguments are columns Self::new( $vt::new(cos, zero, -sin, zero), $vt::new(zero, one, zero, zero), $vt::new(sin, zero, cos, zero), $vt::new(zero, zero, zero, one), ) } /// Create a new rotation matrix from a rotation "around the z axis". This is /// here as a convenience function for users coming from other libraries; it is /// more proper to think of this as a rotation *in the xy plane*. /// /// Assumes homogeneous 3d coordinates. #[inline] pub fn from_rotation_z(angle: $t) -> Self { let (sin, cos) = angle.sin_cos(); let zero = $t::splat(0.0); let one = $t::splat(1.0); // think transposed as arguments are columns Self::new( $vt::new(cos, sin, zero, zero), $vt::new(-sin, cos, zero, zero), $vt::new(zero, zero, one, zero), $vt::new(zero, zero, zero, one), ) } /// Create a new rotation matrix from a rotation around the given axis. /// The axis will be interpreted as a 3d vector. /// This is here as a convenience function for users coming from other libraries. /// /// Assumes homogeneous 3d coordinates. #[inline] pub fn from_rotation_around(axis: $vt, angle: $t) -> Self { let (sin, cos) = angle.sin_cos(); let zero = $t::splat(0.0); let one = $t::splat(1.0); let mul = one - cos; let x_sin = axis.x * sin; let y_sin = axis.y * sin; let z_sin = axis.z * sin; let xy_mul = axis.x * axis.y * mul; let xz_mul = axis.x * axis.z * mul; let yz_mul = axis.y * axis.z * mul; let m00 = (axis.x * axis.x).mul_add(mul, cos); let m10 = xy_mul + z_sin; let m20 = xz_mul - y_sin; let m01 = xy_mul - z_sin; let m11 = (axis.y * axis.y).mul_add(mul, cos); let m21 = yz_mul + x_sin; let m02 = xz_mul + y_sin; let m12 = yz_mul - x_sin; let m22 = (axis.z * axis.z).mul_add(mul, cos); // think transposed as arguments are columns Self::new( $vt::new(m00, m10, m20, zero), $vt::new(m01, m11, m21, zero), $vt::new(m02, m12, m22, zero), $vt::new(zero, zero, zero, one), ) } /// Construct a rotation matrix given a bivector which defines a plane and rotation orientation, /// and a rotation angle. /// /// `plane` must be normalized! /// /// This is the equivalent of an axis-angle rotation. /// /// Assumes homogeneous 3d coordinates. #[inline] pub fn from_angle_plane(angle: $t, plane: $bt) -> Self { $rt::from_angle_plane(angle, plane).into_matrix().into_homogeneous() } /// Assumes homogeneous 3d coordinates. pub fn translate(&mut self, translation: &$v3t) { self[3].x += translation.x; self[3].y += translation.y; self[3].z += translation.z; } /// Assumes homogeneous 3d coordinates. pub fn translated(&self, translation: &$v3t) -> Self { let mut res = *self; res.translate(translation); res } /// Constructs a 'look-at' matrix from an eye position, a focus position to look towards, /// and a vector that defines the 'up' direction. /// /// This function assumes a right-handed, y-up coordinate space. #[inline] pub fn look_at(eye: $v3t, at: $v3t, up: $v3t) -> Self { let f = (at - eye).normalized(); let r = f.cross(up).normalized(); let u = r.cross(f); Self::new( $vt::new(r.x, u.x, -f.x, $t::splat(0.0)), $vt::new(r.y, u.y, -f.y, $t::splat(0.0)), $vt::new(r.z, u.z, -f.z, $t::splat(0.0)), $vt::new(-r.dot(eye), -u.dot(eye), f.dot(eye), $t::splat(1.0)) ) } /// Constructs a 'look-at' matrix from an eye position, a focus position to look towards, /// and a vector that defines the 'up' direction. /// /// This function assumes a *left*-handed, y-up coordinate space. #[inline] pub fn look_at_lh(eye: $v3t, at: $v3t, up: $v3t) -> Self { let f = (at - eye).normalized(); let r = f.cross(up).normalized(); let u = r.cross(f); Self::new( $vt::new(r.x, u.x, f.x, $t::splat(0.0)), $vt::new(r.y, u.y, f.y, $t::splat(0.0)), $vt::new(r.z, u.z, f.z, $t::splat(0.0)), $vt::new(-r.dot(eye), -u.dot(eye), -f.dot(eye), $t::splat(1.0)) ) } #[inline] pub fn transpose(&mut self) { *self = self.transposed(); } #[inline] pub fn transposed(&self) -> Self { let (x0, y0, z0, w0) = self.cols[0].into(); let (x1, y1, z1, w1) = self.cols[1].into(); let (x2, y2, z2, w2) = self.cols[2].into(); let (x3, y3, z3, w3) = self.cols[3].into(); Self::new( $vt::new(x0, x1, x2, x3), $vt::new(y0, y1, y2, y3), $vt::new(z0, z1, z2, z3), $vt::new(w0, w1, w2, w3), ) } /// If this matrix is not currently invertable, this function will return /// an invalid inverse. This status is not checked by the library. #[inline] pub fn inverse(&mut self) { *self = self.inversed(); } #[inline] pub fn determinant(&self) -> $t { let (m00, m01, m02, m03) = self.cols[0].into(); let (m10, m11, m12, m13) = self.cols[1].into(); let (m20, m21, m22, m23) = self.cols[2].into(); let (m30, m31, m32, m33) = self.cols[3].into(); let a2323 = (m22 * m33) - (m23 * m32); let a1323 = (m21 * m33) - (m23 * m31); let a1223 = (m21 * m32) - (m22 * m31); let a0323 = (m20 * m33) - (m23 * m30); let a0223 = (m20 * m32) - (m22 * m30); let a0123 = (m20 * m31) - (m21 * m30); m00 * (m11 * a2323 - m12 * a1323 + m13 * a1223) - m01 * (m10 * a2323 - m12 * a0323 + m13 * a0223) + m02 * (m10 * a1323 - m11 * a0323 + m13 * a0123) - m03 * (m10 * a1223 - m11 * a0223 + m12 * a0123) } /// The adjugate of this matrix, i.e. the transpose of /// the cofactor matrix. /// /// This is equivalent to the inverse /// but without dividing by the determinant of the matrix, /// which can be useful in some contexts for better performance. /// /// One such case is when this matrix is interpreted as a /// a homogeneous transformation matrix, in which case uniform scaling will /// not affect the resulting projected 3d version of transformed points or /// vectors. #[inline] pub fn adjugate(&self) -> Self { let (m00, m01, m02, m03) = self.cols[0].into(); let (m10, m11, m12, m13) = self.cols[1].into(); let (m20, m21, m22, m23) = self.cols[2].into(); let (m30, m31, m32, m33) = self.cols[3].into(); let coef00 = (m22 * m33) - (m32 * m23); let coef02 = (m12 * m33) - (m32 * m13); let coef03 = (m12 * m23) - (m22 * m13); let coef04 = (m21 * m33) - (m31 * m23); let coef06 = (m11 * m33) - (m31 * m13); let coef07 = (m11 * m23) - (m21 * m13); let coef08 = (m21 * m32) - (m31 * m22); let coef10 = (m11 * m32) - (m31 * m12); let coef11 = (m11 * m22) - (m21 * m12); let coef12 = (m20 * m33) - (m30 * m23); let coef14 = (m10 * m33) - (m30 * m13); let coef15 = (m10 * m23) - (m20 * m13); let coef16 = (m20 * m32) - (m30 * m22); let coef18 = (m10 * m32) - (m30 * m12); let coef19 = (m10 * m22) - (m20 * m12); let coef20 = (m20 * m31) - (m30 * m21); let coef22 = (m10 * m31) - (m30 * m11); let coef23 = (m10 * m21) - (m20 * m11); let fac0 = $vt::new(coef00, coef00, coef02, coef03); let fac1 = $vt::new(coef04, coef04, coef06, coef07); let fac2 = $vt::new(coef08, coef08, coef10, coef11); let fac3 = $vt::new(coef12, coef12, coef14, coef15); let fac4 = $vt::new(coef16, coef16, coef18, coef19); let fac5 = $vt::new(coef20, coef20, coef22, coef23); let vec0 = $vt::new(m10, m00, m00, m00); let vec1 = $vt::new(m11, m01, m01, m01); let vec2 = $vt::new(m12, m02, m02, m02); let vec3 = $vt::new(m13, m03, m03, m03); let inv0 = (vec1 * fac0) - (vec2 * fac1) + (vec3 * fac2); let inv1 = (vec0 * fac0) - (vec2 * fac3) + (vec3 * fac4); let inv2 = (vec0 * fac1) - (vec1 * fac3) + (vec3 * fac5); let inv3 = (vec0 * fac2) - (vec1 * fac4) + (vec2 * fac5); let sign_a = $vt::new($t::splat(1.0), $t::splat(-1.0), $t::splat(1.0), $t::splat(-1.0)); let sign_b = $vt::new($t::splat(-1.0), $t::splat(1.0), $t::splat(-1.0), $t::splat(1.0)); Self { cols: [ inv0 * sign_a, inv1 * sign_b, inv2 * sign_a, inv3 * sign_b, ] } } /// If this matrix is not currently invertable, this function will return /// an invalid inverse. This status is not checked by the library. #[inline] pub fn inversed(&self) -> Self { let adjugate = self.adjugate(); let row0 = $vt::new( adjugate.cols[0].x, adjugate.cols[1].x, adjugate.cols[2].x, adjugate.cols[3].x, ); let dot0 = self.cols[0] * row0; let dot1 = dot0.x + dot0.y + dot0.z + dot0.w; let rcp_det = $t::splat(1.0) / dot1; adjugate * rcp_det } /// Transform a Vec3 by self, interpreting it as a vector. #[inline] pub fn transform_vec3(&self, vec: $v3t) -> $v3t { (*self * vec.into_homogeneous_vector()).truncated() } /// Transform a Vec3 by self, interpreting it as a point. #[inline] pub fn transform_point3(&self, point: $v3t) -> $v3t { (*self * point.into_homogeneous_point()).normalized_homogeneous_point().truncated() } /// If `self` represents an affine transformation, return its translation components. /// Otherwise, the returned value has undefined properties. #[inline] pub fn extract_translation(&self) -> $v3t { self.cols[3].truncated() } /// If the 3x3 left upper block of `self` is a rotation, return the corresponding /// rotor. Otherwise, the returned value is a `Rotor3` with undefined properties. pub fn extract_rotation(&self) -> $rt { self.truncate().into_rotor3() } /// If self represents an `Isometry3` (i.e. self is a product of the from `T * R` where /// `T` is a translation and `R` a rotation), return the isometry /// /// If `self` does not represent an isometry, the returned value has undefined /// properties. #[allow(clippy::wrong_self_convention)] pub fn into_isometry(&self) -> $i3t { $i3t::new(self.extract_translation(), self.extract_rotation()) } /// Truncate `self` to a matrix consisting of the 3x3 left upper block. /// If you need a rotation, consider using [`Self::extract_rotation()`] instead. pub fn truncate(&self) -> $m3t { $m3t::new( self.cols[0].truncated(), self.cols[1].truncated(), self.cols[2].truncated(), ) } /// Get the [`core::alloc::Layout`] of `Self` #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } /// Interpret `self` as a statically sized array of the base numeric type. #[inline] pub fn as_array(&self) -> &[$t; 16] { let ptr = self as *const $n as *const [$t; 16]; unsafe { &*ptr } } /// Interpret `self` as a statically sized array of the base numeric type. #[inline] pub fn as_mut_array(&mut self) -> &mut [$t; 16] { let ptr = self as *mut $n as *mut [$t; 16]; unsafe { &mut *ptr } } /// Interpret `self` as a statically sized array of its component (column) vectors. #[inline] pub fn as_component_array(&self) -> &[$vt; 4] { let ptr = self as *const $n as *const [$vt; 4]; unsafe { &*ptr } } /// Interpret `self` as a statically sized array of its component (column) vectors. #[inline] pub fn as_mut_component_array(&mut self) -> &mut [$vt; 4] { let ptr = self as *mut $n as *mut [$vt; 4]; unsafe { &mut *ptr } } /// Interpret `self` as a slice of the base numeric type. #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $t, 16) } } /// Interpret `self` as a slice of the base numeric type. #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $t, 16) } } /// Interpret `self` as a slice of the component (column) vectors #[inline] pub fn as_component_slice(&self) -> &[$vt] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $vt, 4) } } /// Interpret `self` as a slice of the component (column) vectors #[inline] pub fn as_mut_component_slice(&mut self) -> &mut [$vt] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $vt, 4) } } /// Interpret `self` as a slice of bytes #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const u8, 16 * std::mem::size_of::<$t>()) } } /// Interpret `self` as a slice of bytes #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut u8, 16 * std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub const fn as_ptr(&self) -> *const $t { self as *const $n as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $n as *mut $t } } impl Mul for $n { type Output = Self; #[inline] fn mul(self, rhs: Self) -> Self { let sa = self.cols[0]; let sb = self.cols[1]; let sc = self.cols[2]; let sd = self.cols[3]; let oa = rhs.cols[0]; let ob = rhs.cols[1]; let oc = rhs.cols[2]; let od = rhs.cols[3]; Self::new( $vt::new( (sa.x * oa.x) + (sb.x * oa.y) + (sc.x * oa.z) + (sd.x * oa.w), (sa.y * oa.x) + (sb.y * oa.y) + (sc.y * oa.z) + (sd.y * oa.w), (sa.z * oa.x) + (sb.z * oa.y) + (sc.z * oa.z) + (sd.z * oa.w), (sa.w * oa.x) + (sb.w * oa.y) + (sc.w * oa.z) + (sd.w * oa.w), ), $vt::new( (sa.x * ob.x) + (sb.x * ob.y) + (sc.x * ob.z) + (sd.x * ob.w), (sa.y * ob.x) + (sb.y * ob.y) + (sc.y * ob.z) + (sd.y * ob.w), (sa.z * ob.x) + (sb.z * ob.y) + (sc.z * ob.z) + (sd.z * ob.w), (sa.w * ob.x) + (sb.w * ob.y) + (sc.w * ob.z) + (sd.w * ob.w), ), $vt::new( (sa.x * oc.x) + (sb.x * oc.y) + (sc.x * oc.z) + (sd.x * oc.w), (sa.y * oc.x) + (sb.y * oc.y) + (sc.y * oc.z) + (sd.y * oc.w), (sa.z * oc.x) + (sb.z * oc.y) + (sc.z * oc.z) + (sd.z * oc.w), (sa.w * oc.x) + (sb.w * oc.y) + (sc.w * oc.z) + (sd.w * oc.w), ), $vt::new( (sa.x * od.x) + (sb.x * od.y) + (sc.x * od.z) + (sd.x * od.w), (sa.y * od.x) + (sb.y * od.y) + (sc.y * od.z) + (sd.y * od.w), (sa.z * od.x) + (sb.z * od.y) + (sc.z * od.z) + (sd.z * od.w), (sa.w * od.x) + (sb.w * od.y) + (sc.w * od.z) + (sd.w * od.w), ), ) } } impl Mul<$vt> for $n { type Output = $vt; #[inline] fn mul(self, rhs: $vt) -> $vt { let a = self.cols[0]; let b = self.cols[1]; let c = self.cols[2]; let d = self.cols[3]; $vt::new( a.x * rhs.x + b.x * rhs.y + c.x * rhs.z + d.x * rhs.w, a.y * rhs.x + b.y * rhs.y + c.y * rhs.z + d.y * rhs.w, a.z * rhs.x + b.z * rhs.y + c.z * rhs.z + d.z * rhs.w, a.w * rhs.x + b.w * rhs.y + c.w * rhs.z + d.w * rhs.w, ) } } impl Mul<$t> for $n { type Output = $n; #[inline] fn mul(self, rhs: $t) -> $n { $n::new( self.cols[0] * rhs, self.cols[1] * rhs, self.cols[2] * rhs, self.cols[3] * rhs, ) } } impl Mul<$n> for $t { type Output = $n; #[inline] fn mul(self, rhs: $n) -> $n { $n::new( rhs.cols[0] * self, rhs.cols[1] * self, rhs.cols[2] * self, rhs.cols[3] * self, ) } } impl Add for $n { type Output = Self; #[inline] fn add(self, rhs: $n) -> Self { $n::new( self.cols[0] + rhs.cols[0], self.cols[1] + rhs.cols[1], self.cols[2] + rhs.cols[2], self.cols[3] + rhs.cols[3], ) } } impl AddAssign for $n { #[inline] fn add_assign(&mut self, rhs: $n) { *self = *self + rhs; } } impl From<[$t; 16]> for $n { #[inline] fn from(comps: [$t; 16]) -> Self { Self::new( $vt::new(comps[0], comps[1], comps[2], comps[3]), $vt::new(comps[4], comps[5], comps[6], comps[7]), $vt::new(comps[8], comps[9], comps[10], comps[11]), $vt::new(comps[12], comps[13], comps[14], comps[15]), ) } } impl From<[[$t; 4]; 4]> for $n { #[inline] fn from(comps: [[$t; 4]; 4]) -> Self { Self::new( $vt::new(comps[0][0], comps[0][1], comps[0][2], comps[0][3]), $vt::new(comps[1][0], comps[1][1], comps[1][2], comps[1][3]), $vt::new(comps[2][0], comps[2][1], comps[2][2], comps[2][3]), $vt::new(comps[3][0], comps[3][1], comps[3][2], comps[3][3]) ) } } impl From<$n> for [[$t; 4]; 4] { #[inline] fn from(mat4: $n) -> Self { [ [mat4.cols[0].x, mat4.cols[0].y, mat4.cols[0].z, mat4.cols[0].w], [mat4.cols[1].x, mat4.cols[1].y, mat4.cols[1].z, mat4.cols[1].w], [mat4.cols[2].x, mat4.cols[2].y, mat4.cols[2].z, mat4.cols[2].w], [mat4.cols[3].x, mat4.cols[3].y, mat4.cols[3].z, mat4.cols[3].w] ] } } impl From<&[$t; 16]> for $n { #[inline] fn from(comps: &[$t; 16]) -> Self { Self::from(*comps) } } impl Index for $n { type Output = $vt; fn index(&self, index: usize) -> &Self::Output { &self.cols[index] } } impl IndexMut for $n { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.cols[index] } } )+ } } mat4s!( Mat4 => Rotor3, Bivec3, Vec4, Vec3, Mat3, Isometry3, f32, Mat4x4 => Rotor3x4, Bivec3x4, Vec4x4, Vec3x4, Mat3x4, Isometry3x4, f32x4, Mat4x8 => Rotor3x8, Bivec3x8, Vec4x8, Vec3x8, Mat3x8, Isometry3x8, f32x8 ); #[cfg(feature = "f64")] mat4s!( DMat4 => DRotor3, DBivec3, DVec4, DVec3, DMat3, DIsometry3, f64, DMat4x2 => DRotor3x2, DBivec3x2, DVec4x2, DVec3x2, DMat3x2, DIsometry3x2, f64x2, DMat4x4 => DRotor3x4, DBivec3x4, DVec4x4, DVec3x4, DMat3x4, DIsometry3x4, f64x4 ); #[cfg(test)] mod test { use super::*; use crate::util::*; /* TODO: Re-enable these. The current way that Matrix3::into_rotor() works sometimes fails these edge cases based on rounding error accumulated from the round trip due to the way it uses use std::f32::consts::FRAC_PI_2; use std::f32::consts::PI; copysign() #[test] pub fn mat3_to_rotor_corner_cases(){ for i in 0..64 { let alpha = { match i % 4 { 0 => -FRAC_PI_2, 1 => 0., 2 => FRAC_PI_2, 3 => PI, _ => unreachable!() } }; let beta = { match (i / 4) % 4 { 0 => -FRAC_PI_2, 1 => 0., 2 => FRAC_PI_2, 3 => PI, _ => unreachable!() } }; let gamma = { match (i / 16) % 4 { 0 => -FRAC_PI_2, 1 => 0., 2 => FRAC_PI_2, 3 => PI, _ => unreachable!() } }; println!("roll {}, pitch {}, yaw {}", alpha, beta, gamma); let rotor = Rotor3::from_euler_angles(alpha, beta, gamma); let mat = rotor.into_matrix(); let rotor2 = mat.into_rotor3(); assert!(rotor.eq_eps(rotor2)); let xr = Vec3::unit_x().rotated_by(rotor); let xr2 = Vec3::unit_x().rotated_by(rotor2); assert!(xr.eq_eps(xr2)); let yr = Vec3::unit_y().rotated_by(rotor); let yr2 = Vec3::unit_y().rotated_by(rotor2); assert!(yr.eq_eps(yr2)); let zr = Vec3::unit_z().rotated_by(rotor); let zr2 = Vec3::unit_z().rotated_by(rotor2); assert!(zr.eq_eps(zr2)); } }*/ #[test] pub fn isometry_roundtrip() { let a = Vec3::new(1.0, 2.0, -5.0).normalized(); let b = Vec3::new(1.0, 1.0, 1.0).normalized(); let c = Vec3::new(2.0, 3.0, -3.0).normalized(); let r_ab = Rotor3::from_rotation_between(a, b); let iso = Isometry3::new(c, r_ab); let iso_mat4 = iso.into_homogeneous_matrix(); let iso_ = iso_mat4.into_isometry(); assert!(iso_.translation.eq_eps(c)); assert!(iso_.rotation.eq_eps(r_ab)); } #[test] pub fn test_euler_angle_conversion() { let roll = 0.4; let yaw = 0.3; let pitch = 0.2; let mat1 = Mat3::from_euler_angles(roll, pitch, yaw); let mat2 = Mat3::from_rotation_y(yaw) * Mat3::from_rotation_x(pitch) * Mat3::from_rotation_z(roll); assert_eq!(mat1[0], mat2[0]); assert_eq!(mat1[1], mat2[1]); assert_eq!(mat1[2], mat2[2]); let mat3 = Mat4::from_euler_angles(roll, pitch, yaw); let mat4 = Mat4::from_rotation_y(yaw) * Mat4::from_rotation_x(pitch) * Mat4::from_rotation_z(roll); assert_eq!(mat3[0], mat4[0]); assert_eq!(mat3[1], mat4[1]); assert_eq!(mat3[2], mat4[2]); assert_eq!(mat3[3], mat4[3]); } } ultraviolet-0.10.0/src/projection/lh_ydown.rs000064400000000000000000000060421046102023000174120ustar 00000000000000//! Projection matrices that are intended to be used when the base coordinate //! system (i.e. the one used by the application code) has the x-axis pointing right, //! y-axis pointing *down*, and z-axis pointing *towards the viewer*. //! //! Note that this module only exports orthographic matrices. That is because the only place that //! a left-handed, y-down coordinate system is common is when thinking in primarily 2d. //! //! If you're using a left-handed, y-down source coordinate system for 3d... //! stop it, get some help. use crate::mat::*; use crate::vec::*; /// Orthographic projection matrix for use with OpenGL and a source "2d y-down" coordinate space. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-down /// (+X right, +Y down, +Z towards the viewer) and the destination space is left-handed /// and y-up, with Z (depth) clip extending from -1.0 (close) to 1.0 (far). #[inline] pub fn orthographic_gl(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Mat4 { let rml = right - left; let rpl = right + left; let tmb = top - bottom; let tpb = top + bottom; let fmn = far - near; let fpn = far + near; Mat4::new( Vec4::new(2.0 / rml, 0.0, 0.0, 0.0), Vec4::new(0.0, -2.0 / tmb, 0.0, 0.0), Vec4::new(0.0, 0.0, 2.0 / fmn, 0.0), Vec4::new(-(rpl / rml), -(tpb / tmb), -(fpn / fmn), 1.0), ) } /// Orthographic projection matrix for use with Vulkan and a source "2d y-down" coordinate space. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-down /// (+X right, +Y down, +Z towards the viewer) and the destination space is right-handed /// and y-down, with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn orthographic_vk(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Mat4 { let rml = right - left; let rpl = right + left; let tmb = top - bottom; let tpb = top + bottom; let fmn = far - near; Mat4::new( Vec4::new(2.0 / rml, 0.0, 0.0, 0.0), Vec4::new(0.0, 2.0 / tmb, 0.0, 0.0), Vec4::new(0.0, 0.0, 1.0 / fmn, 0.0), Vec4::new(-(rpl / rml), -(tpb / tmb), -(near / fmn), 1.0), ) } /// Orthographic projection matrix for use with WebGPU or DirectX. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-down /// (+X right, +Y down, +Z towards the viewer) and the destination space is left-handed /// and y-up, with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn orthographic_wgpu_dx( left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32, ) -> Mat4 { let rml = right - left; let rpl = right + left; let tmb = top - bottom; let tpb = top + bottom; let fmn = far - near; Mat4::new( Vec4::new(2.0 / rml, 0.0, 0.0, 0.0), Vec4::new(0.0, -2.0 / tmb, 0.0, 0.0), Vec4::new(0.0, 0.0, 1.0 / fmn, 0.0), Vec4::new(-(rpl / rml), -(tpb / tmb), -(near / fmn), 1.0), ) } ultraviolet-0.10.0/src/projection/lh_yup.rs000064400000000000000000000336111046102023000170710ustar 00000000000000//! Projection matrices that are intended to be used when the base coordinate //! system (i.e. the one used by the application code) assumes that +X points right, +Y points up, //! and +Z points away from the viewer, similar to Unity, Cinema4d, or ZBrush: //! //! ```ignore,log //! +y +z //! | / //! | / //! 0 ----- +x //! ``` //! use crate::mat::*; use crate::vec::*; /// Orthographic projection matrix for use with OpenGL. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination space is left-handed /// and y-up, with Z (depth) clip extending from -1.0 (close) to 1.0 (far). #[inline] pub fn orthographic_gl(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Mat4 { let rml = right - left; let rpl = right + left; let tmb = top - bottom; let tpb = top + bottom; let fmn = far - near; let fpn = far + near; Mat4::new( Vec4::new(2.0 / rml, 0.0, 0.0, 0.0), Vec4::new(0.0, 2.0 / tmb, 0.0, 0.0), Vec4::new(0.0, 0.0, 2.0 / fmn, 0.0), Vec4::new(-(rpl / rml), -(tpb / tmb), -(fpn / fmn), 1.0), ) } /// Orthographic projection matrix for use with Vulkan. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination space is right-handed /// and y-down, with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn orthographic_vk(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Mat4 { let rml = right - left; let rpl = right + left; let tmb = top - bottom; let tpb = top + bottom; let fmn = far - near; Mat4::new( Vec4::new(2.0 / rml, 0.0, 0.0, 0.0), Vec4::new(0.0, -2.0 / tmb, 0.0, 0.0), Vec4::new(0.0, 0.0, 1.0 / fmn, 0.0), Vec4::new(-(rpl / rml), -(tpb / tmb), -(near / fmn), 1.0), ) } /// Orthographic projection matrix for use with WebGPU or DirectX. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination space is left-handed /// and y-up, with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn orthographic_wgpu_dx( left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32, ) -> Mat4 { let rml = right - left; let rpl = right + left; let tmb = top - bottom; let tpb = top + bottom; let fmn = far - near; Mat4::new( Vec4::new(2.0 / rml, 0.0, 0.0, 0.0), Vec4::new(0.0, 2.0 / tmb, 0.0, 0.0), Vec4::new(0.0, 0.0, 1.0 / fmn, 0.0), Vec4::new(-(rpl / rml), -(tpb / tmb), -(near / fmn), 1.0), ) } /// Perspective projection matrix meant to be used with OpenGL. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from -1.0 (close) to 1.0 (far). #[inline] pub fn perspective_gl(vertical_fov: f32, aspect_ratio: f32, z_near: f32, z_far: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; let nmf = z_near - z_far; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, (z_far + z_near) / nmf, 1.0), Vec4::new(0.0, 0.0, 2.0 * z_near * z_far / nmf, 0.0), ) } /// Perspective projection matrix meant to be used with WebGPU or DirectX. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_wgpu_dx(vertical_fov: f32, aspect_ratio: f32, z_near: f32, z_far: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; let nmf = z_near - z_far; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, z_far / nmf, 1.0), Vec4::new(0.0, 0.0, z_near * z_far / nmf, 0.0), ) } /// Perspective projection matrix meant to be used with Vulkan. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination coordinate space is /// right-handed and y-down with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_vk(vertical_fov: f32, aspect_ratio: f32, z_near: f32, z_far: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; let nmf = z_near - z_far; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, -sy, 0.0, 0.0), Vec4::new(0.0, 0.0, z_far / nmf, 1.0), Vec4::new(0.0, 0.0, z_near * z_far / nmf, 0.0), ) } /// Perspective projection matrix with infinite z-far plane meant to be used with OpenGL. /// /// This is useful for extremely large scenes where having a far clip plane is extraneous anyway, /// as allowing it to approach infinity it eliminates several approximate numerical computations /// and so can improve z-fighting behavior. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from -1.0 (close) to 1.0 (far). #[inline] pub fn perspective_infinite_z_gl(vertical_fov: f32, aspect_ratio: f32, z_near: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, -1.0, 1.0), Vec4::new(0.0, 0.0, -2.0 * z_near, 0.0), ) } /// Perspective projection matrix with infinite z-far plane meant to be used with Vulkan. /// /// This is useful for extremely large scenes where having a far clip plane is extraneous anyway, /// as allowing it to approach infinity it eliminates several approximate numerical computations /// and so can improve z-fighting behavior. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination coordinate space is /// right-handed and y-down with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_infinite_z_vk(vertical_fov: f32, aspect_ratio: f32, z_near: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, -sy, 0.0, 0.0), Vec4::new(0.0, 0.0, -1.0, 1.0), Vec4::new(0.0, 0.0, -z_near, 0.0), ) } /// Perspective projection matrix with infinite z-far plane meant to be used with WebGPU or DirectX. /// /// This is useful for extremely large scenes where having a far clip plane is extraneous anyway, /// as allowing it to approach infinity it eliminates several approximate numerical computations /// and so can improve z-fighting behavior. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_infinite_z_wgpu_dx(vertical_fov: f32, aspect_ratio: f32, z_near: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, -1.0, 1.0), Vec4::new(0.0, 0.0, -z_near, 0.0), ) } /// Perspective projection matrix with reversed z-axis meant to be used with WebGPU, DirectX, or OpenGL. /// /// Reversed-Z provides significantly better precision and therefore reduced z-fighting /// for most depth situations, especially when a floating-point depth buffer is used. You'll want to use /// a reversed depth comparison function and depth clear value when using this projection. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from 0.0 (close) to 1.0 (far). /// /// **Note that in order for this to work properly with OpenGL, you'll need to use the `gl_arb_clip_control` extension /// and set the z clip from 0.0 to 1.0 rather than the default -1.0 to 1.0** #[inline] pub fn perspective_reversed_z_wgpu_dx_gl( vertical_fov: f32, aspect_ratio: f32, z_near: f32, z_far: f32, ) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; let nmf = z_near - z_far; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, -z_far / nmf - 1.0, 1.0), Vec4::new(0.0, 0.0, -z_near * z_far / nmf, 0.0), ) } /// Perspective projection matrix with reversed z-axis meant to be used with Vulkan. /// /// Reversed-Z provides significantly better precision and therefore reduced z-fighting /// for most depth situations, especially when a floating-point depth buffer is used. You'll want to use /// a reversed depth comparison function and depth clear value when using this projection. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination coordinate space is /// right-handed and y-down with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_reversed_z_vk( vertical_fov: f32, aspect_ratio: f32, z_near: f32, z_far: f32, ) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; let nmf = z_near - z_far; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, -sy, 0.0, 0.0), Vec4::new(0.0, 0.0, z_near / nmf, 1.0), Vec4::new(0.0, 0.0, -z_near * z_far / nmf, 0.0), ) } /// Perspective projection matrix with reversed and infinite z-axis meant to be used with WebGPU, OpenGL, or DirectX. /// /// Reversed-Z provides significantly better precision and therefore reduced z-fighting /// for most depth situations, especially when a floating-point depth buffer is used. You'll want to use /// a reversed depth comparison function and depth clear value when using this projection. /// /// Infinte-Z is useful for extremely large scenes where having a far clip plane is extraneous anyway, /// as allowing it to approach infinity it eliminates several approximate numerical computations /// and so can improve z-fighting behavior. /// /// Combining them gives the best of both worlds for large scenes. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from 0.0 (close) to 1.0 (far). /// /// **Note that in order for this to work properly with OpenGL, you'll need to use the `gl_arb_clip_control` extension /// and set the z clip from 0.0 to 1.0 rather than the default -1.0 to 1.0** #[inline] pub fn perspective_reversed_infinite_z_wgpu_dx_gl( vertical_fov: f32, aspect_ratio: f32, z_near: f32, ) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, 0.0, 1.0), Vec4::new(0.0, 0.0, z_near, 0.0), ) } /// Perspective projection matrix with reversed and infinite z-axis meant to be used with Vulkan. /// /// Reversed-Z provides significantly better precision and therefore reduced z-fighting /// for most depth situations, especially when a floating-point depth buffer is used. You'll want to use /// a reversed depth comparison function and depth clear value when using this projection. /// /// Infinte-Z is useful for extremely large scenes where having a far clip plane is extraneous anyway, /// as allowing it to approach infinity it eliminates several approximate numerical computations /// and so can improve z-fighting behavior. /// /// Combining them gives the best of both worlds for large scenes. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is left-handed and y-up /// and the destination coordinate space is /// right-handed and y-down with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_reversed_infinite_z_vk( vertical_fov: f32, aspect_ratio: f32, z_near: f32, ) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, -sy, 0.0, 0.0), Vec4::new(0.0, 0.0, 0.0, 1.0), Vec4::new(0.0, 0.0, z_near, 0.0), ) } ultraviolet-0.10.0/src/projection/rh_yup.rs000064400000000000000000000353221046102023000171000ustar 00000000000000//! Projection matrices that are intended to be used when the base coordinate //! system (i.e. the one used by the application code) is assumes +X points right, +Y points up, //! and +Z points towards the viewer, similar to Godot, Maya, Houdini, Substance, etc.: //! //! ```ignore,log //! +y //! | //! | //! 0----- +x //! / //! / //! +z //! ``` //! //! This is reexported at the root of `projections` as it is the //! de-facto standard coordinate system for doing computer graphics programming. use crate::mat::*; use crate::vec::*; /// Orthographic projection matrix for use with OpenGL. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space)and the destination space is left-handed /// and y-up, with Z (depth) clip extending from -1.0 (close) to 1.0 (far). #[inline] pub fn orthographic_gl(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Mat4 { let rml = right - left; let rpl = right + left; let tmb = top - bottom; let tpb = top + bottom; let fmn = far - near; let fpn = far + near; Mat4::new( Vec4::new(2.0 / rml, 0.0, 0.0, 0.0), Vec4::new(0.0, 2.0 / tmb, 0.0, 0.0), Vec4::new(0.0, 0.0, -2.0 / fmn, 0.0), Vec4::new(-(rpl / rml), -(tpb / tmb), -(fpn / fmn), 1.0), ) } /// Orthographic projection matrix for use with Vulkan. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space)and the destination space is right-handed /// and y-down, with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn orthographic_vk(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Mat4 { let rml = right - left; let rpl = right + left; let tmb = top - bottom; let tpb = top + bottom; let fmn = far - near; Mat4::new( Vec4::new(2.0 / rml, 0.0, 0.0, 0.0), Vec4::new(0.0, -2.0 / tmb, 0.0, 0.0), Vec4::new(0.0, 0.0, -1.0 / fmn, 0.0), Vec4::new(-(rpl / rml), -(tpb / tmb), -(near / fmn), 1.0), ) } /// Orthographic projection matrix for use with WebGPU or DirectX. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space)and the destination space is left-handed /// and y-up, with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn orthographic_wgpu_dx( left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32, ) -> Mat4 { let rml = right - left; let rpl = right + left; let tmb = top - bottom; let tpb = top + bottom; let fmn = far - near; Mat4::new( Vec4::new(2.0 / rml, 0.0, 0.0, 0.0), Vec4::new(0.0, 2.0 / tmb, 0.0, 0.0), Vec4::new(0.0, 0.0, -1.0 / fmn, 0.0), Vec4::new(-(rpl / rml), -(tpb / tmb), -(near / fmn), 1.0), ) } /// Perspective projection matrix meant to be used with OpenGL. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space) and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from -1.0 (close) to 1.0 (far). #[inline] pub fn perspective_gl(vertical_fov: f32, aspect_ratio: f32, z_near: f32, z_far: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; let nmf = z_near - z_far; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, (z_far + z_near) / nmf, -1.0), Vec4::new(0.0, 0.0, 2.0 * z_near * z_far / nmf, 0.0), ) } /// Perspective projection matrix meant to be used with WebGPU or DirectX. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space) and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_wgpu_dx(vertical_fov: f32, aspect_ratio: f32, z_near: f32, z_far: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; let nmf = z_near - z_far; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, z_far / nmf, -1.0), Vec4::new(0.0, 0.0, z_near * z_far / nmf, 0.0), ) } /// Perspective projection matrix meant to be used with Vulkan. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space) and the destination coordinate space is /// right-handed and y-down with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_vk(vertical_fov: f32, aspect_ratio: f32, z_near: f32, z_far: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; let nmf = z_near - z_far; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, -sy, 0.0, 0.0), Vec4::new(0.0, 0.0, z_far / nmf, -1.0), Vec4::new(0.0, 0.0, z_near * z_far / nmf, 0.0), ) } /// Perspective projection matrix with infinite z-far plane meant to be used with OpenGL. /// /// This is useful for extremely large scenes where having a far clip plane is extraneous anyway, /// as allowing it to approach infinity it eliminates several approximate numerical computations /// and so can improve z-fighting behavior. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space) and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from -1.0 (close) to 1.0 (far). #[inline] pub fn perspective_infinite_z_gl(vertical_fov: f32, aspect_ratio: f32, z_near: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, -1.0, -1.0), Vec4::new(0.0, 0.0, -2.0 * z_near, 0.0), ) } /// Perspective projection matrix with infinite z-far plane meant to be used with Vulkan. /// /// This is useful for extremely large scenes where having a far clip plane is extraneous anyway, /// as allowing it to approach infinity it eliminates several approximate numerical computations /// and so can improve z-fighting behavior. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space) and the destination coordinate space is /// right-handed and y-down with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_infinite_z_vk(vertical_fov: f32, aspect_ratio: f32, z_near: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, -sy, 0.0, 0.0), Vec4::new(0.0, 0.0, -1.0, -1.0), Vec4::new(0.0, 0.0, -z_near, 0.0), ) } /// Perspective projection matrix with infinite z-far plane meant to be used with WebGPU or DirectX. /// /// This is useful for extremely large scenes where having a far clip plane is extraneous anyway, /// as allowing it to approach infinity it eliminates several approximate numerical computations /// and so can improve z-fighting behavior. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space) and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_infinite_z_wgpu_dx(vertical_fov: f32, aspect_ratio: f32, z_near: f32) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, -1.0, -1.0), Vec4::new(0.0, 0.0, -z_near, 0.0), ) } /// Perspective projection matrix with reversed z-axis meant to be used with WebGPU, DirectX, or OpenGL. /// /// Reversed-Z provides significantly better precision and therefore reduced z-fighting /// for most depth situations, especially when a floating-point depth buffer is used. You'll want to use /// a reversed depth comparison function and depth clear value when using this projection. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space) and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from 0.0 (close) to 1.0 (far). /// /// **Note that in order for this to work properly with OpenGL, you'll need to use the `gl_arb_clip_control` extension /// and set the z clip from 0.0 to 1.0 rather than the default -1.0 to 1.0** #[inline] pub fn perspective_reversed_z_wgpu_dx_gl( vertical_fov: f32, aspect_ratio: f32, z_near: f32, z_far: f32, ) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; let nmf = z_near - z_far; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, -z_far / nmf - 1.0, -1.0), Vec4::new(0.0, 0.0, -z_near * z_far / nmf, 0.0), ) } /// Perspective projection matrix with reversed z-axis meant to be used with Vulkan. /// /// Reversed-Z provides significantly better precision and therefore reduced z-fighting /// for most depth situations, especially when a floating-point depth buffer is used. You'll want to use /// a reversed depth comparison function and depth clear value when using this projection. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space) and the destination coordinate space is /// right-handed and y-down with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_reversed_z_vk( vertical_fov: f32, aspect_ratio: f32, z_near: f32, z_far: f32, ) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; let nmf = z_near - z_far; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, -sy, 0.0, 0.0), Vec4::new(0.0, 0.0, -z_near / nmf, -1.0), Vec4::new(0.0, 0.0, -z_near * z_far / nmf, 0.0), ) } /// Perspective projection matrix with reversed and infinite z-axis meant to be used with WebGPU, OpenGL, or DirectX. /// /// Reversed-Z provides significantly better precision and therefore reduced z-fighting /// for most depth situations, especially when a floating-point depth buffer is used. You'll want to use /// a reversed depth comparison function and depth clear value when using this projection. /// /// Infinte-Z is useful for extremely large scenes where having a far clip plane is extraneous anyway, /// as allowing it to approach infinity it eliminates several approximate numerical computations /// and so can improve z-fighting behavior. /// /// Combining them gives the best of both worlds for large scenes. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space) and the destination coordinate space is /// left-handed and y-up with Z (depth) clip extending from 0.0 (close) to 1.0 (far). /// /// **Note that in order for this to work properly with OpenGL, you'll need to use the `gl_arb_clip_control` extension /// and set the z clip from 0.0 to 1.0 rather than the default -1.0 to 1.0** #[inline] pub fn perspective_reversed_infinite_z_wgpu_dx_gl( vertical_fov: f32, aspect_ratio: f32, z_near: f32, ) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, sy, 0.0, 0.0), Vec4::new(0.0, 0.0, 0.0, -1.0), Vec4::new(0.0, 0.0, z_near, 0.0), ) } /// Perspective projection matrix with reversed and infinite z-axis meant to be used with Vulkan. /// /// Reversed-Z provides significantly better precision and therefore reduced z-fighting /// for most depth situations, especially when a floating-point depth buffer is used. You'll want to use /// a reversed depth comparison function and depth clear value when using this projection. /// /// Infinte-Z is useful for extremely large scenes where having a far clip plane is extraneous anyway, /// as allowing it to approach infinity it eliminates several approximate numerical computations /// and so can improve z-fighting behavior. /// /// Combining them gives the best of both worlds for large scenes. /// /// * `vertical_fov` should be provided in radians. /// * `aspect_ratio` should be the quotient `width / height`. /// /// This matrix is meant to be used when the source coordinate space is right-handed and y-up /// (the standard computer graphics coordinate space) and the destination coordinate space is /// right-handed and y-down with Z (depth) clip extending from 0.0 (close) to 1.0 (far). #[inline] pub fn perspective_reversed_infinite_z_vk( vertical_fov: f32, aspect_ratio: f32, z_near: f32, ) -> Mat4 { let t = (vertical_fov / 2.0).tan(); let sy = 1.0 / t; let sx = sy / aspect_ratio; Mat4::new( Vec4::new(sx, 0.0, 0.0, 0.0), Vec4::new(0.0, -sy, 0.0, 0.0), Vec4::new(0.0, 0.0, 0.0, -1.0), Vec4::new(0.0, 0.0, z_near, 0.0), ) } ultraviolet-0.10.0/src/projection.rs000064400000000000000000000035171046102023000155730ustar 00000000000000//! Utility functions to create projection matrices. //! //! You should choose a submodule based on the coordinate system convetion that *your application* //! is using, *not* which graphics api you are using. Then within that submodule, you can choose //! a specific projection matrix constructor based on the *output* coordinate system, which will be //! determined by the graphics api you're using. //! //! For example, if your code assumes that +X points right, +Y points up, and +Z points towards the //! viewer, similar to Godot, Maya, Houdini, Substance, etc.: //! //! ```ignore,log //! +y //! | //! | //! 0----- +x //! / //! / //! +z //! ``` //! //! then you should use one of the projections listed in the [`rh_yup`] module. Since this is //! generally considered to be the "most standard" (get out the pitchforks!) computer graphics //! coordinate space, these projections are also re-exported at the root of the `projection` //! module. //! //! If your code assumes that +X points right, +Y points up, and +Z points away from the viewer, //! similar to Unity, Cinema4d, or ZBrush: //! //! ```ignore,log //! +y +z //! | / //! | / //! 0 ----- +x //! ``` //! //! then you should use one of the projections listed in the [`lh_yup`] module. //! //! If you're building a 2d application which assumes the +X points right, +Y points down, and //! +Z points towards the viewer (higher depth means "over" lower depth), then you should use //! one of the projections listed in the [`lh_ydown`] module. //! //! If you're building a 3d application which uses a source Z-up coordinate space (similar to //! Blender, 3ds max, or Unreal), then we do not currently have a module with projections //! suitable for your use case. Contributions to add this are welcome! pub mod lh_ydown; pub mod lh_yup; pub mod rh_yup; pub use rh_yup::*; ultraviolet-0.10.0/src/rotor.rs000064400000000000000000000771221046102023000145670ustar 00000000000000//! Rotors, i.e. constructs that describe and perform rotations. //! //! A rotor is the geometric algebra analog of the Quaternion, and they //! end up being mathematically equivalent. They are good for doing the same //! sorts of things, and for the most part you can use rotors just like you //! would a quaternion, if you're already familiar with using those. However, //! they are significantly easier to derive yourself and build intuition for, //! and they generalize to both lower and higher dimensions than just 3, which //! is the only space for which quaternions are valuable. //! //! A rotor can be thought of in multiple ways, the first of which //! is that a rotor is the result of the 'geometric product' of two vectors, //! denoted for two vectors `u` and `v` as simply `uv`. This operation is //! defined as //! //! `uv = u · v + u ∧ v` //! //! As can be seen, this operation results in the addition of two different //! types of values: first, the dot product will result in a scalar, and second, //! the exterior (wedge) product will result in a bivector. The addition of these two different //! types is not defined, but can be understood in a similar way as complex numbers, //! i.e. as a 'bundle' of two different kinds of values. //! //! The reason we call this type of value a 'rotor' is that if you both left- and //! right-multiply (using the geometric product) a rotor with a vector, you will //! rotate the sandwiched vector. For example, if you start with two vectors, //! `a` and `b`, and create a rotor `ab` from them, then rotate a vector `u` with this //! rotor by doing `ba u ab`, you will end up rotating the vector `u` by in the plane //! that corresponds to `a ∧ b` (i.e. the plane which is parallel with both vectors), by //! twice the angle between `a` and `b`, in the opposite direction of the one that would //! bring `a` towards `b` within that plane. //! //! In `ultraviolet`, the `Mul` trait is implemented for Rotors such that doing //! //! `rotor * vec` //! //! will rotate the Vector `vec` by the Rotor `rotor`. //! //! To compose rotations, simply left-multiply the rotor by another one in the same //! way that matrix composition works. For example, //! //! `rotor_ab = rotor_b * rotor_a` //! //! Will result in the composition of `rotor_b` and `rotor_a` such that `rotor_ab` encodes //! a rotation as though `rotor_a` was applied *and then* `rotor_b` was applied. //! //! Note that *composition* of rotors is *more efficient* //! than composition of matrices, however, the operation of rotating a vector by a rotor, i.e. the //! `rotor * vec` product, is *more expensive* to //! compute than the `matrix * vec` product. So, rotors are excellent for *building* and *interpolating* //! rotations, but it may be preferable to convert them into matrices before applying them to //! vectors/points, if the same rotation will be applied to many vectors. use crate::util::*; use crate::*; use std::ops::*; macro_rules! rotor2s { ($($rn:ident => ($mt:ident, $vt:ident, $bt:ident, $t:ident)),+) => { $( /// A Rotor in 2d space. /// /// Please see the module level documentation for more information on rotors! #[derive(Clone, Copy, Debug, PartialEq)] #[repr(C)] pub struct $rn { pub s: $t, pub bv: $bt, } derive_default_identity!($rn); impl $rn { #[inline] pub const fn new(scalar: $t, bivector: $bt) -> Self { Self { s: scalar, bv: bivector, } } #[inline] pub fn identity() -> Self { Self { s: $t::splat(1.0), bv: $bt::zero(), } } /// Construct a Rotor that rotates one vector to another. /// /// A rotation between antiparallel vectors is **undefined**! #[inline] pub fn from_rotation_between(from: $vt, to: $vt) -> Self { Self::new( $t::splat(1.0) + to.dot(from), to.wedge(from)).normalized() } /// Construct a rotor given a bivector which defines a plane and rotation orientation, /// and a rotation angle. /// /// `plane` must be normalized! /// /// This is the equivalent of an axis-angle rotation. #[inline] pub fn from_angle_plane(angle: $t, plane: $bt) -> Self { let half_angle = angle * $t::splat(0.5); let (sin, cos) = half_angle.sin_cos(); Self::new(cos, plane * -sin) } /// Construct a rotor given only an angle. This is possible in 2d since there is only one /// possible plane of rotation. However, there are two possible orientations. This function /// uses the common definition of positive angle in 2d as meaning the direction which brings /// the x unit vector towards the y unit vector. #[inline] pub fn from_angle(angle: $t) -> Self { let half_angle = angle / $t::splat(2.0); let (sin, cos) = half_angle.sin_cos(); Self::new(cos, $bt::new(-sin)) } #[inline] pub fn mag_sq(&self) -> $t { self.s * self.s + self.bv.mag_sq() } #[inline] pub fn mag(&self) -> $t { self.mag_sq().sqrt() } #[inline] pub fn normalize(&mut self) { let mag = self.mag(); self.s /= mag; self.bv.xy /= mag; } #[inline] #[must_use = "Did you mean to use `.normalize()` to normalize `self` in place?"] pub fn normalized(&self) -> Self { let mut s = *self; s.normalize(); s } #[inline] pub fn reverse(&mut self) { self.bv = -self.bv; } #[inline] pub fn reversed(&self) -> Self { let mut s = *self; s.reverse(); s } #[inline] pub fn dot(&self, rhs: Self) -> $t { self.s * rhs.s + self.bv.dot(rhs.bv) } /// Rotates this rotor by another rotor in-place. Note that if you /// are looking to *compose* rotations, you should *NOT* use this /// operation and rather just use regular left-multiplication like /// for matrix composition. #[inline] pub fn rotate_by(&mut self, other: Self) { let b = *self; let a = other; let sa2_plus_baxy2 = a.s.mul_add(a.s, a.bv.xy * a.bv.xy); self.s = (a.s - b.s) * a.bv.xy * b.bv.xy + b.s * sa2_plus_baxy2; self.bv.xy = b.bv.xy * sa2_plus_baxy2; } /// Rotates this rotor by another rotor and returns the result. Note that if you /// are looking to *compose* rotations, you should *NOT* use this /// operation and rather just use regular left-multiplication like /// for matrix composition. #[inline] pub fn rotated_by(mut self, other: Self) -> Self { self.rotate_by(other); self } /// Rotates a vector by this rotor. /// /// `self` *must* be normalized! #[inline] pub fn rotate_vec(self, vec: &mut $vt) { let fx = self.s * vec.x + self.bv.xy * vec.y; let fy = self.s * vec.y - (self.bv.xy * vec.x); vec.x = self.s * fx + self.bv.xy * fy; vec.y = self.s * fy - (self.bv.xy * fx); } #[inline] pub fn into_matrix(self) -> $mt { let s2_minus_bxy2 = self.s * self.s - self.bv.xy * self.bv.xy; let two_s_bxy = $t::splat(2.0) * self.s * self.bv.xy; $mt::new( $vt::new( s2_minus_bxy2, -two_s_bxy), $vt::new( two_s_bxy, s2_minus_bxy2)) } #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } } impl From<$rn> for $mt { #[inline] fn from(rotor: $rn) -> $mt { rotor.into_matrix() } } impl EqualsEps for $rn { fn eq_eps(self, other: Self) -> bool { self.s.eq_eps(other.s) && self.bv.eq_eps(other.bv) } } /// The composition of `self` with `q`, i.e. `self * q` gives the rotation as though /// you first perform `q` and then `self`. impl Mul for $rn { type Output = Self; #[inline] fn mul(self, rhs: Self) -> Self { Self { s: self.s * rhs.s - (self.bv.xy * rhs.bv.xy), bv: $bt { xy: self.s * rhs.bv.xy + rhs.s * self.bv.xy, } } } } impl AddAssign for $rn { #[inline] fn add_assign(&mut self, rhs: Self) { self.s += rhs.s; self.bv += rhs.bv; } } impl Add for $rn { type Output = Self; #[inline] fn add(mut self, rhs: Self) -> Self { self += rhs; self } } impl SubAssign for $rn { #[inline] fn sub_assign(&mut self, rhs: Self) { self.s -= rhs.s; self.bv -= rhs.bv; } } impl Sub for $rn { type Output = Self; #[inline] fn sub(mut self, rhs: Self) -> Self { self -= rhs; self } } impl Mul<$vt> for $rn { type Output = $vt; #[inline] fn mul(self, mut rhs: $vt) -> $vt { self.rotate_vec(&mut rhs); rhs } } impl MulAssign<$t> for $rn { #[inline] fn mul_assign(&mut self, rhs: $t) { self.s *= rhs; self.bv *= rhs; } } impl Mul<$t> for $rn { type Output = Self; #[inline] fn mul(mut self, rhs: $t) -> Self { self *= rhs; self } } impl Mul<$rn> for $t { type Output = $rn; #[inline] fn mul(self, rotor: $rn) -> $rn { rotor * self } } impl DivAssign<$t> for $rn { #[inline] fn div_assign(&mut self, rhs: $t) { self.s /= rhs; self.bv /= rhs; } } impl Div<$t> for $rn { type Output = Self; #[inline] fn div(mut self, rhs: $t) -> Self { self /= rhs; self } } )+ } } rotor2s!( Rotor2 => (Mat2, Vec2, Bivec2, f32), Rotor2x4 => (Mat2x4, Vec2x4, Bivec2x4, f32x4), Rotor2x8 => (Mat2x8, Vec2x8, Bivec2x8, f32x8) ); #[cfg(feature = "f64")] rotor2s!( DRotor2 => (DMat2, DVec2, DBivec2, f64), DRotor2x2 => (DMat2x2, DVec2x2, DBivec2x2, f64x2), DRotor2x4 => (DMat2x4, DVec2x4, DBivec2x4, f64x4) ); macro_rules! rotor3s { ($($rn:ident => ($mt:ident, $vt:ident, $bt:ident, $t:ident)),+) => { $( /// A Rotor in 3d space. /// /// Please see the module level documentation for more information on rotors! #[derive(Clone, Copy, Debug, PartialEq)] #[repr(C)] pub struct $rn { pub s: $t, pub bv: $bt, } derive_default_identity!($rn); impl $rn { #[inline] pub const fn new(scalar: $t, bivector: $bt) -> Self { Self { s: scalar, bv: bivector, } } #[inline] pub fn identity() -> Self { Self { s: $t::splat(1.0), bv: $bt::zero(), } } /// Construct a Rotor that rotates one vector to another. #[inline] pub fn from_rotation_between(from: $vt, to: $vt) -> Self { Self::new( $t::splat(1.0) + to.dot(from), to.wedge(from)).normalized() } /// Construct a rotor given a bivector which defines a plane and rotation orientation, /// and a rotation angle. /// /// `plane` must be normalized! /// /// This is the equivalent of an axis-angle rotation. #[inline] pub fn from_angle_plane(angle: $t, plane: $bt) -> Self { let half_angle = angle * $t::splat(0.5); let (sin, cos) = half_angle.sin_cos(); Self::new(cos, plane * -sin) } /// Return the angle and the normalized plane of the rotation represented by self. /// The value of the returned angle is between 0 and PI. #[inline] pub fn into_angle_plane(self) -> ($t, $bt) { let cos_half_angle = self.s; let sin_half_angle = self.bv.mag(); let half_angle = sin_half_angle.atan2(cos_half_angle); (half_angle * 2., -self.bv.normalized()) } /// Multiply the angle of the rotation represented by self by `scale`. #[inline] pub fn scale_by(&mut self, scale: $t) { *self = self.scaled_by(scale) } /// Return a rotor representing the same rotatation as `self` but with an angle /// multiplied by `scale` #[inline] #[must_use] pub fn scaled_by(self, scale: $t) -> Self { let (angle, plane) = self.into_angle_plane(); Self::from_angle_plane(angle * scale, plane) } /// Create new Rotor from a rotation in the xy plane (also known as /// "around the z axis"). #[inline] pub fn from_rotation_xy(angle: $t) -> Self { Self::from_angle_plane(angle, $bt::unit_xy()) } /// Create new Rotor from a rotation in the xz plane (also known as /// "around the y axis"). #[inline] pub fn from_rotation_xz(angle: $t) -> Self { Self::from_angle_plane(angle, $bt::unit_xz()) } /// Create new Rotor from a rotation in the yz plane (also known as /// "around the x axis"). #[inline] pub fn from_rotation_yz(angle: $t) -> Self { Self::from_angle_plane(angle, $bt::unit_yz()) } /// Angles are applied in the order roll -> pitch -> yaw /// /// - Roll is rotation inside the xy plane ("around the z axis") /// - Pitch is rotation inside the yz plane ("around the x axis") /// - Yaw is rotation inside the xz plane ("around the y axis") #[inline] pub fn from_euler_angles(roll: $t, pitch: $t, yaw: $t) -> Self { Self::from_angle_plane(yaw, $bt::unit_xz()) * Self::from_angle_plane(pitch, $bt::unit_yz()) * Self::from_angle_plane(roll, $bt::unit_xy()) } #[inline] pub fn mag_sq(&self) -> $t { self.s * self.s + self.bv.mag_sq() } #[inline] pub fn mag(&self) -> $t { self.mag_sq().sqrt() } #[inline] pub fn normalize(&mut self) { let mag = self.mag(); self.s /= mag; self.bv.xy /= mag; self.bv.xz /= mag; self.bv.yz /= mag; } #[inline] #[must_use = "Did you mean to use `.normalize()` to normalize `self` in place?"] pub fn normalized(&self) -> Self { let mut s = *self; s.normalize(); s } #[inline] pub fn reverse(&mut self) { self.bv = -self.bv; } #[inline] pub fn reversed(&self) -> Self { let mut s = *self; s.reverse(); s } #[inline] pub fn dot(&self, rhs: Self) -> $t { self.s * rhs.s + self.bv.dot(rhs.bv) } /// Rotates this rotor by another rotor in-place. Note that if you /// are looking to *compose* rotations which will then be applied to another object/vector /// (you probably are), you should /// *NOT* use this operation. Rather, just use regular left-multiplication /// as in matrix composition, i.e. /// /// ```rs /// second_rotor * first_rotor /// ``` #[inline] pub fn rotate_by(&mut self, rhs: Self) { // TODO make this faster by adding intermediate factored object let b = *self; let a = rhs; let two = $t::splat(2.0); let sa2 = a.s * a.s; let baxy2 = a.bv.xy * a.bv.xy; let baxz2 = a.bv.xz * a.bv.xz; let bayz2 = a.bv.yz * a.bv.yz; let sa_baxy = a.s * a.bv.xy; let sa_baxz = a.s * a.bv.xz; let sa_bayz = a.s * a.bv.yz; let baxy_baxz = a.bv.xy * a.bv.xz; let baxy_bayz = a.bv.xy * a.bv.yz; let baxz_bayz = a.bv.xz * a.bv.yz; let two_bbxy = two * b.bv.xy; let two_bbxz = two * b.bv.xz; let two_bbyz = two * b.bv.yz; self.s = (sa2 + baxy2 + baxz2 + bayz2) * b.s; self.bv.xy = (sa2 + baxy2 - baxz2 - bayz2) * b.bv.xy + (baxy_baxz + sa_bayz) * two_bbxz + (baxy_bayz - sa_baxz) * two_bbyz; self.bv.xz = (sa2 - baxy2 + baxz2 - bayz2) * b.bv.xz + (baxy_baxz - sa_bayz) * two_bbxy + (baxz_bayz + sa_baxy) * two_bbyz; self.bv.yz = (sa2 - baxy2 - baxz2 + bayz2) * b.bv.yz + (baxy_bayz + sa_baxz) * two_bbxy + (baxz_bayz - sa_baxy) * two_bbxz; } /// Rotates this rotor by another rotor and returns the result. Note that if you /// are looking to *compose* rotations, you should *NOT* use this /// operation and rather just use regular left-multiplication like /// as in matrix composition, i.e. /// /// ```rs /// second_rotor * first_rotor /// ``` #[inline] pub fn rotated_by(mut self, rhs: Self) -> Self { self.rotate_by(rhs); self } /// Rotates a vector by this rotor. /// /// `self` *must* be normalized! #[inline] pub fn rotate_vec(self, vec: &mut $vt) { // see derivation/rotor3_rotate_vec_derivation for a derivation // f = geometric product of (self)(vec) let fx = self.s * vec.x + self.bv.xy * vec.y + self.bv.xz * vec.z; let fy = self.s * vec.y - self.bv.xy * vec.x + self.bv.yz * vec.z; let fz = self.s * vec.z - self.bv.xz * vec.x - self.bv.yz * vec.y; let fw = self.bv.xy * vec.z - self.bv.xz * vec.y + self.bv.yz * vec.x; // result = geometric product of (f)(self~) vec.x = self.s * fx + self.bv.xy * fy + self.bv.xz * fz + self.bv.yz * fw; vec.y = self.s * fy - self.bv.xy * fx - self.bv.xz * fw + self.bv.yz * fz; vec.z = self.s * fz + self.bv.xy * fw - self.bv.xz * fx - self.bv.yz * fy; } /// Rotates multiple vectors by this rotor. /// /// This will be faster than calling `rotate_vec` individually on many vecs /// as intermediate values can be precomputed once and applied to each vector. /// /// `self` must be normalized! pub fn rotate_vecs(self, vecs: &mut [$vt]) { let s2 = self.s * self.s; let bxy2 = self.bv.xy * self.bv.xy; let bxz2 = self.bv.xz * self.bv.xz; let byz2 = self.bv.yz * self.bv.yz; let s_bxy = self.s * self.bv.xy; let s_bxz = self.s * self.bv.xz; let s_byz = self.s * self.bv.yz; let bxz_byz = self.bv.xz * self.bv.yz; let bxy_byz = self.bv.xy * self.bv.yz; let bxy_bxz = self.bv.xy * self.bv.xz; let xa = s2 - bxy2 - bxz2 + byz2; let xb = s_bxy - bxz_byz; let xc = s_bxz + bxy_byz; let ya = -(bxz_byz + s_bxy); let yb = s2 - bxy2 + bxz2 - byz2; let yc = s_byz - bxy_bxz; let za = bxy_byz - s_bxz; let zb = bxy_bxz + s_byz; let zc = -(s2 + bxy2 - bxz2 - byz2); for vec in vecs { let two_vx = vec.x + vec.x; let two_vy = vec.y + vec.y; let two_vz = vec.z + vec.z; vec.x = vec.x * xa + two_vy * xb + two_vz * xc; vec.y = two_vx * ya + vec.y * yb + two_vz * yc; vec.z = two_vx * za - two_vy * zb - vec.z * zc; } } #[inline] pub fn into_matrix(self) -> $mt { let s2 = self.s * self.s; let bxy2 = self.bv.xy * self.bv.xy; let bxz2 = self.bv.xz * self.bv.xz; let byz2 = self.bv.yz * self.bv.yz; let s_bxy = self.s * self.bv.xy; let s_bxz = self.s * self.bv.xz; let s_byz = self.s * self.bv.yz; let bxz_byz = self.bv.xz * self.bv.yz; let bxy_byz = self.bv.xy * self.bv.yz; let bxy_bxz = self.bv.xy * self.bv.xz; let two = $t::splat(2.0); $mt::new( $vt::new( s2 - bxy2 - bxz2 + byz2, -two * (bxz_byz + s_bxy), two * (bxy_byz - s_bxz)), $vt::new( two * (s_bxy - bxz_byz), s2 - bxy2 + bxz2 - byz2, -two * (s_byz + bxy_bxz) ), $vt::new( two * (s_bxz + bxy_byz), two * (s_byz - bxy_bxz), s2 + bxy2 - bxz2 - byz2 ) ) } /// Convert this rotor into an array that represents a quaternion. This is in the form /// `[vector, scalar]`. #[inline] pub fn into_quaternion_array(self) -> [$t; 4] { [-self.bv.yz, self.bv.xz, -self.bv.xy, self.s] } /// Convert an array that represents a quaternion in the form `[vector, scalar]` into a /// rotor. #[inline] pub fn from_quaternion_array(array: [$t; 4]) -> Self { Self::new(array[3], $bt::new(-array[2], array[1], -array[0])) } #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } } impl From<$rn> for $mt { #[inline] fn from(rotor: $rn) -> $mt { rotor.into_matrix() } } impl EqualsEps for $rn { #[inline] fn eq_eps(self, other: Self) -> bool { self.s.eq_eps(other.s) && self.bv.eq_eps(other.bv) } } /// The composition of `self` with `q`, i.e. `self * q` gives the rotation as though /// you first perform `q` and then `self`. impl Mul for $rn { type Output = Self; /// The composition of `self` with `q`, i.e. `self * q` gives the rotation as though /// you first perform `q` and then `self`. #[inline] fn mul(self, q: Self) -> Self { Self { s: self.s * q.s - self.bv.xy * q.bv.xy - self.bv.xz * q.bv.xz - self.bv.yz * q.bv.yz, bv: $bt { xy: self.bv.xy * q.s + self.s * q.bv.xy + self.bv.yz * q.bv.xz - self.bv.xz * q.bv.yz, xz: self.bv.xz * q.s + self.s * q.bv.xz - self.bv.yz * q.bv.xy + self.bv.xy * q.bv.yz, yz: self.bv.yz * q.s + self.s * q.bv.yz + self.bv.xz * q.bv.xy - self.bv.xy * q.bv.xz, } } } } impl AddAssign for $rn { #[inline] fn add_assign(&mut self, rhs: Self) { self.s += rhs.s; self.bv += rhs.bv; } } impl Add for $rn { type Output = Self; #[inline] fn add(mut self, rhs: Self) -> Self { self += rhs; self } } impl SubAssign for $rn { #[inline] fn sub_assign(&mut self, rhs: Self) { self.s -= rhs.s; self.bv -= rhs.bv; } } impl Sub for $rn { type Output = Self; #[inline] fn sub(mut self, rhs: Self) -> Self { self -= rhs; self } } impl Mul<$vt> for $rn { type Output = $vt; #[inline] fn mul(self, mut rhs: $vt) -> $vt { self.rotate_vec(&mut rhs); rhs } } impl MulAssign<$t> for $rn { #[inline] fn mul_assign(&mut self, rhs: $t) { self.s *= rhs; self.bv *= rhs; } } impl Mul<$t> for $rn { type Output = Self; #[inline] fn mul(mut self, rhs: $t) -> Self { self *= rhs; self } } impl Mul<$rn> for $t { type Output = $rn; #[inline] fn mul(self, rotor: $rn) -> $rn { rotor * self } } impl DivAssign<$t> for $rn { #[inline] fn div_assign(&mut self, rhs: $t) { self.s /= rhs; self.bv /= rhs; } } impl Div<$t> for $rn { type Output = Self; #[inline] fn div(mut self, rhs: $t) -> Self { self /= rhs; self } } )+ } } rotor3s!( Rotor3 => (Mat3, Vec3, Bivec3, f32), Rotor3x4 => (Mat3x4, Vec3x4, Bivec3x4, f32x4), Rotor3x8 => (Mat3x8, Vec3x8, Bivec3x8, f32x8) ); #[cfg(feature = "f64")] rotor3s!( DRotor3 => (DMat3, DVec3, DBivec3, f64), DRotor3x2 => (DMat3x2, DVec3x2, DBivec3x2, f64x2), DRotor3x4 => (DMat3x4, DVec3x4, DBivec3x4, f64x4) ); #[cfg(test)] mod test { use super::*; #[test] pub fn rotate_vector_roundtrip() { let a = Vec3::new(1.0, 2.0, -5.0).normalized(); let b = Vec3::new(1.0, 1.0, 1.0).normalized(); let c = Vec3::new(2.0, 3.0, -3.0).normalized(); let rotor_ab = Rotor3::from_rotation_between(a, b); let rotor_bc = Rotor3::from_rotation_between(b, c); let rot_ab = rotor_ab * a; let rot_bc = rotor_bc * b; let rot_abc = rotor_bc * (rotor_ab * a); println!("{:?} = {:?}", rot_ab, b); println!("{:?} = {:?}", rot_bc, c); println!("{:?} = {:?}", rot_abc, c); assert!(rot_ab.eq_eps(b)); assert!(rot_bc.eq_eps(c)); assert!(rot_abc.eq_eps(c)); } #[test] pub fn rotate_rotor_trivial() { let a = Vec3::new(1.0, 2.0, -5.0).normalized(); let b = Vec3::new(1.0, 1.0, 1.0).normalized(); let c = Vec3::new(2.0, 3.0, -3.0).normalized(); let r_ab = Rotor3::from_rotation_between(a, b); let r_bc = Rotor3::from_rotation_between(b, c); let res = r_ab.rotated_by(r_bc).rotated_by(r_bc.reversed()); println!("{:?} {:?}", r_ab, res); assert!(r_ab.eq_eps(res)); } #[test] pub fn compose_rotor_roundtrip() { let a = Vec3::new(0.25, -5.0, 1.0).normalized(); let b = Vec3::new(-5.0, 2.0, 4.0).normalized(); let c = Vec3::new(-3.0, 0.0, -1.0).normalized(); let rotor_ab = Rotor3::from_rotation_between(a, b); let rotor_bc = Rotor3::from_rotation_between(b, c); let rotor_abbc = rotor_bc * rotor_ab; let res = rotor_abbc * a; println!("{:#?} {:#?}", rotor_abbc, res); assert!(c.eq_eps(res)); } #[test] pub fn rotor_interp_trivial() { let i = Rotor3::identity(); let interp = i.lerp(i, 0.5); println!("{:#?} ::: {:#?}", i, interp); assert!(interp.eq_eps(i)) } #[test] #[allow(clippy::eq_op)] pub fn rotor_equality() { let i = Rotor3::identity(); assert_eq!(i, i); } #[test] pub fn angle_plane_roundtrip() { let angle = 0.32; let plane = Bivec3::new(0.2, 0.4, 0.7).normalized(); let rotor = Rotor3::from_angle_plane(angle, plane); let (angle_, plane_) = rotor.into_angle_plane(); assert!(Rotor3::from_angle_plane(angle_, plane_).eq_eps(rotor)); let angle = -0.32; let plane = Bivec3::new(0.2, 0.4, 0.7).normalized(); let rotor = Rotor3::from_angle_plane(angle, plane); let (angle_, plane_) = rotor.into_angle_plane(); assert!(Rotor3::from_angle_plane(angle_, plane_).eq_eps(rotor)); } #[test] pub fn quaternion_convertion_roundtrip() { let a = Vec3::new(1.0, 2.0, -5.0).normalized(); let b = Vec3::new(1.0, 1.0, 1.0).normalized(); let rotor = Rotor3::from_rotation_between(a, b); assert_eq!( rotor, Rotor3::from_quaternion_array(rotor.into_quaternion_array()) ); } #[test] pub fn rotor_scaling() { use std::f32::consts::PI; let axis = Vec3::new(0.42, 0.123, 0.789).normalized(); //aribitrary rotation axis let plane = Bivec3::from_normalized_axis(axis).normalized(); let angle = PI / 10.; // rotation of angle pi/10 on the axis; let rotation_1 = Rotor3::from_angle_plane(angle, plane); let fraction = 1.234; let scaled_rotor_1 = Rotor3::from_angle_plane(fraction * angle, plane); let scaled_rotor_2 = rotation_1.scaled_by(fraction); assert!(scaled_rotor_1.eq_eps(scaled_rotor_2)); } // This test exists because Rotor3 used to implement PartialEq without DRotor3 getting the same // impl. Use `cargo test --all-features` to run #[cfg(feature = "f64")] #[test] #[allow(clippy::eq_op)] pub fn drotor_equality() { let i = DRotor3::identity(); assert_eq!(i, i); } } ultraviolet-0.10.0/src/transform.rs000064400000000000000000000350251046102023000154310ustar 00000000000000//! Dedicated transformation types as the combination of primitives. //! //! Note that you may want to us these types over the corresponding type of //! homogeneous transformation matrix because they are faster in most operations, //! especially composition and inverse. use crate::*; use std::ops::*; macro_rules! isometries { ($($ison:ident => ($mt:ident, $rt:ident, $vt:ident, $t:ident)),+) => { $( /// An Isometry, aka a "rigid body transformation". /// /// Defined as the combination of a rotation *and then* a translation. /// /// You may want to us this type over the corresponding type of /// homogeneous transformation matrix because it will be faster in most operations, /// especially composition and inverse. #[derive(Clone, Copy, Debug, PartialEq)] #[repr(C)] pub struct $ison { pub translation: $vt, pub rotation: $rt, } derive_default_identity!($ison); impl $ison { #[inline] pub const fn new(translation: $vt, rotation: $rt) -> Self { Self { translation, rotation } } #[inline] pub fn identity() -> Self { Self { rotation: $rt::identity(), translation: $vt::zero() } } /// Add a rotation *before* this isometry. /// /// This means the rotation will only affect the rotational /// part of this isometry, not the translational part. #[inline] pub fn prepend_rotation(&mut self, rotor: $rt) { self.rotation = rotor * self.rotation; } /// Add a rotation *after* this isometry. /// /// This means the rotation will affect both the rotational and /// translational parts of this isometry, since it is being applied /// 'after' this isometry's translational part. pub fn append_rotation(&mut self, rotor: $rt) { self.rotation = rotor * self.rotation; self.translation = rotor * self.translation; } /// Add a translation *before* this isometry. /// /// Doing so will mean that the translation being added will get /// transformed by this isometry's rotational part. #[inline] pub fn prepend_translation(&mut self, translation: $vt) { self.translation += self.rotation * translation; } /// Add a translation *after* this isometry. /// /// Doing so will mean that the translation being added will *not* /// transformed by this isometry's rotational part. #[inline] pub fn append_translation(&mut self, translation: $vt) { self.translation += translation; } /// Prepend transformation by another isometry. /// /// This means that the transformation being applied will take place /// *before* this isometry, i.e. both its translation and rotation will be /// rotated by this isometry's rotational part. #[inline] pub fn prepend_isometry(&mut self, other: Self) { *self = *self * other; } /// Append transformation by another isometry. /// /// This means that the transformation being applied will take place /// *after* this isometry, i.e. *this isometry's* translation and rotation will be /// rotated by the *other* isometry's rotational part. #[inline] pub fn append_isometry(&mut self, other: Self) { *self = other * *self; } #[inline] pub fn inverse(&mut self) { self.rotation.reverse(); self.translation = self.rotation * (-self.translation); } #[inline] pub fn inversed(mut self) -> Self { self.inverse(); self } #[inline] pub fn transform_vec(&self, mut vec: $vt) -> $vt { vec = self.rotation * vec; vec += self.translation; vec } #[inline] pub fn into_homogeneous_matrix(self) -> $mt { $mt::from_translation(self.translation) * self.rotation.into_matrix().into_homogeneous() } } impl Mul<$ison> for $rt { type Output = $ison; #[inline] fn mul(self, mut iso: $ison) -> $ison { iso.append_rotation(self); iso } } impl Mul<$rt> for $ison { type Output = $ison; #[inline] fn mul(mut self, rotor: $rt) -> $ison { self.prepend_rotation(rotor); self } } impl Mul<$t> for $ison { type Output = Self; #[inline] fn mul(mut self, scalar: $t) -> $ison { self.translation *= scalar; self.rotation *= scalar; self } } impl Mul<$vt> for $ison { type Output = $vt; #[inline] fn mul(self, vec: $vt) -> $vt { self.transform_vec(vec) } } impl Mul<$ison> for $ison { type Output = Self; #[inline] fn mul(self, base: $ison) -> $ison { let trans = self.transform_vec(base.translation); let rot = self.rotation * base.rotation; $ison::new(trans, rot) } } impl Add<$ison> for $ison { type Output = Self; #[inline] fn add(mut self, other: $ison) -> $ison { self.translation += other.translation; self.rotation += other.rotation; self } } )+ } } isometries!( Isometry2 => (Mat3, Rotor2, Vec2, f32), Isometry2x4 => (Mat3x4, Rotor2x4, Vec2x4, f32x4), Isometry2x8 => (Mat3x8, Rotor2x8, Vec2x8, f32x8), Isometry3 => (Mat4, Rotor3, Vec3, f32), Isometry3x4 => (Mat4x4, Rotor3x4, Vec3x4, f32x4), Isometry3x8 => (Mat4x8, Rotor3x8, Vec3x8, f32x8) ); #[cfg(feature = "f64")] isometries!( DIsometry2 => (DMat3, DRotor2, DVec2, f64), DIsometry2x2 => (DMat3x2, DRotor2x2, DVec2x2, f64x2), DIsometry2x4 => (DMat3x4, DRotor2x4, DVec2x4, f64x4), DIsometry3 => (DMat4, DRotor3, DVec3, f64), DIsometry3x2 => (DMat4x2, DRotor3x2, DVec3x2, f64x2), DIsometry3x4 => (DMat4x4, DRotor3x4, DVec3x4, f64x4) ); macro_rules! similarities { ($($sn:ident => ($mt:ident, $rt:ident, $vt:ident, $t:ident)),+) => { $( /// A Similarity, i.e. an Isometry but with an added uniform scaling. /// /// Defined as a uniform scaling followed by a rotation followed by a translation. /// /// You may want to us this type over the corresponding type of /// homogeneous transformation matrix because it will be faster in most operations, /// especially composition and inverse. #[derive(Clone, Copy, Debug, PartialEq)] #[repr(C)] pub struct $sn { pub translation: $vt, pub rotation: $rt, pub scale: $t, } derive_default_identity!($sn); impl $sn { #[inline] pub const fn new(translation: $vt, rotation: $rt, scale: $t) -> Self { Self { translation, rotation, scale } } #[inline] pub fn identity() -> Self { Self { rotation: $rt::identity(), translation: $vt::zero(), scale: $t::splat(1.0) } } /// Add a scaling *before* this similarity. /// /// This means the scaling will only affect the scaling part /// of this similarity, not the translational part. #[inline] pub fn prepend_scaling(&mut self, scaling: $t) { self.scale *= scaling; } /// Add a scaling *after* this similarity. /// /// This means the scaling will affect both the scaling /// and translational parts of this similairty, since it is being /// applied *after* this similarity's translational part. #[inline] pub fn append_scaling(&mut self, scaling: $t) { self.scale *= scaling; self.translation *= scaling; } /// Add a rotation *before* this similarity. /// /// This means the rotation will only affect the rotational /// part of this similarity, not the translational part. #[inline] pub fn prepend_rotation(&mut self, rotor: $rt) { self.rotation = rotor * self.rotation; } /// Add a rotation *after* this similarity. /// /// This means the rotation will affect both the rotational and /// translational parts of this similarity, since it is being applied /// *after* this similarity's translational part. pub fn append_rotation(&mut self, rotor: $rt) { self.rotation = rotor * self.rotation; self.translation = rotor * self.translation; } /// Add a translation *before* this similarity. /// /// Doing so will mean that the translation being added will get /// transformed by this similarity's rotational and scaling parts. #[inline] pub fn prepend_translation(&mut self, translation: $vt) { self.translation += self.scale * self.rotation * translation; } /// Add a translation *after* this similarity. /// /// Doing so will mean that the translation being added will *not* /// transformed by this similarity's rotational or scaling parts. #[inline] pub fn append_translation(&mut self, translation: $vt) { self.translation += translation; } /// Prepend transformation by another similarity. /// /// This means that the transformation being applied will take place /// *before* this similarity, i.e. both its translation and rotation will be /// rotated by the other similarity's rotational part, and its translation /// will be scaled by the other similarity's scaling part. #[inline] pub fn prepend_similarity(&mut self, other: Self) { *self = *self * other; } /// Append transformation by another similarity. /// /// This means that the transformation being applied will take place /// *after* this similarity, i.e. *this similarity's* translation and rotation will be /// rotated by the *other* similarity's rotational part, and *this similarity's* translation /// will be scaled by the *other* similarity's scaling pat. #[inline] pub fn append_similarity(&mut self, other: Self) { *self = other * *self; } #[inline] pub fn inverse(&mut self) { self.rotation.reverse(); self.scale = $t::splat(1.0) / self.scale; self.translation = self.rotation * (-self.translation) * self.scale; } #[inline] pub fn inversed(mut self) -> Self { self.inverse(); self } #[inline] pub fn transform_vec(&self, mut vec: $vt) -> $vt { vec = self.rotation * vec; vec = self.scale * vec; vec += self.translation; vec } #[inline] pub fn into_homogeneous_matrix(self) -> $mt { $mt::from_translation(self.translation) * self.rotation.into_matrix().into_homogeneous() * $mt::from_scale(self.scale) } } impl Mul<$sn> for $rt { type Output = $sn; #[inline] fn mul(self, mut iso: $sn) -> $sn { iso.append_rotation(self); iso } } impl Mul<$rt> for $sn { type Output = $sn; #[inline] fn mul(mut self, rotor: $rt) -> $sn { self.prepend_rotation(rotor); self } } impl Mul<$t> for $sn { type Output = Self; #[inline] fn mul(mut self, scalar: $t) -> $sn { self.translation *= scalar; self.rotation *= scalar; self.scale *= scalar; self } } impl Mul<$vt> for $sn { type Output = $vt; #[inline] fn mul(self, vec: $vt) -> $vt { self.transform_vec(vec) } } impl Mul<$sn> for $sn { type Output = Self; #[inline] fn mul(self, base: $sn) -> $sn { let trans = self.transform_vec(base.translation); let rot = self.rotation * base.rotation; let scale = self.scale * base.scale; $sn::new(trans, rot, scale) } } impl Add<$sn> for $sn { type Output = Self; #[inline] fn add(mut self, other: $sn) -> $sn { self.translation += other.translation; self.rotation += other.rotation; self.scale += other.scale; self } } )+ } } similarities!( Similarity2 => (Mat3, Rotor2, Vec2, f32), Similarity2x4 => (Mat3x4, Rotor2x4, Vec2x4, f32x4), Similarity2x8 => (Mat3x8, Rotor2x8, Vec2x8, f32x8), Similarity3 => (Mat4, Rotor3, Vec3, f32), Similarity3x4 => (Mat4x4, Rotor3x4, Vec3x4, f32x4), Similarity3x8 => (Mat4x8, Rotor3x8, Vec3x8, f32x8) ); #[cfg(feature = "f64")] similarities!( DSimilarity2 => (DMat3, DRotor2, DVec2, f64), DSimilarity2x2 => (DMat3x2, DRotor2x2, DVec2x2, f64x2), DSimilarity2x4 => (DMat3x4, DRotor2x4, DVec2x4, f64x4), DSimilarity3 => (DMat4, DRotor3, DVec3, f64), DSimilarity3x2 => (DMat4x2, DRotor3x2, DVec3x2, f64x2), DSimilarity3x4 => (DMat4x4, DRotor3x4, DVec3x4, f64x4) ); ultraviolet-0.10.0/src/util.rs000064400000000000000000000031101046102023000143610ustar 00000000000000use crate::*; pub(crate) trait Splat { fn splat(val: T) -> Self; } impl Splat for f32 { #[inline(always)] fn splat(val: f32) -> Self { val } } impl Splat for f64 { #[inline(always)] fn splat(val: f64) -> Self { val } } pub trait EqualsEps { fn eq_eps(self, other: Self) -> bool; } macro_rules! impl_eq_eps_wide { ($($t:ident),+) => { $(impl EqualsEps for $t { fn eq_eps(self, other: Self) -> bool { let r = (self - other).abs(); let eps = $t::splat(0.01); r.cmp_ge(eps).none() } })+ }; } impl_eq_eps_wide!(f32x4, f32x8, f64x2, f64x4); impl EqualsEps for f32 { fn eq_eps(self, other: Self) -> bool { let diff = (self - other).abs(); if diff <= 0.01 { true } else { println!( "{} should equal {} with epsilon 0.01 but doesn't.", self, other ); false } } } impl EqualsEps for f64 { fn eq_eps(self, other: Self) -> bool { let diff = (self - other).abs(); if diff <= 0.01 { true } else { println!( "{} should equal {} with epsilon 0.01 but doesn't.", self, other ); false } } } #[macro_export] macro_rules! derive_default_identity { ($t:ident) => { impl Default for $t { #[inline] fn default() -> Self { Self::identity() } } }; } ultraviolet-0.10.0/src/vec/mod.rs000064400000000000000000000003051046102023000147430ustar 00000000000000//! Vectors and points, i.e. directed line segments and locations. mod vec2; mod vec3; mod vec4; #[cfg(feature = "num-traits")] mod num_traits; pub use vec2::*; pub use vec3::*; pub use vec4::*; ultraviolet-0.10.0/src/vec/num_traits.rs000064400000000000000000000013171046102023000163550ustar 00000000000000use crate::*; macro_rules! impl_num_traits_vecs { ($($n:ident),+) => { $( impl num_traits::Zero for $n { #[inline] fn zero() -> Self { $n::zero() } #[inline] fn is_zero(&self) -> bool { &$n::zero() == self } } impl num_traits::One for $n { #[inline] fn one() -> Self { $n::one() } } )+ }; } impl_num_traits_vecs!(Vec2, Vec2x4, Vec2x8, Vec3, Vec3x4, Vec3x8, Vec4, Vec4x4, Vec4x8); #[cfg(feature = "f64")] impl_num_traits_vecs!(DVec2, DVec2x2, DVec2x4, DVec3, DVec3x2, DVec3x4, DVec4, DVec4x2, DVec4x4); ultraviolet-0.10.0/src/vec/vec2.rs000064400000000000000000000561371046102023000150410ustar 00000000000000use std::ops::*; use crate::util::EqualsEps; use crate::*; macro_rules! vec2s { ($(($n:ident, $bn:ident, $rn:ident, $v3t:ident, $v4t:ident) => $t:ident),+) => { $( /// A set of two coordinates which may be interpreted as a vector or point in 2d space. /// /// Generally this distinction between a point and vector is more of a pain than it is worth /// to distinguish on a type level, however when converting to and from homogeneous /// coordinates it is quite important. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[repr(C)] pub struct $n { pub x: $t, pub y: $t, } impl $n { #[inline] pub const fn new(x: $t, y: $t) -> Self { $n { x, y } } #[inline] pub const fn broadcast(val: $t) -> Self { Self::new(val, val) } #[inline] pub fn unit_x() -> Self { $n{ x: $t::splat(1.0), y: $t::splat(0.0) } } #[inline] pub fn unit_y() -> Self { $n{ x: $t::splat(0.0), y: $t::splat(1.0) } } /// Create a homogeneous 2d *point* from this vector interpreted as a point, /// meaning the homogeneous component will start with a value of 1.0. #[inline] pub fn into_homogeneous_point(self) -> $v3t { $v3t { x: self.x, y: self.y, z: $t::splat(1.0) } } /// Create a homogeneous 2d *vector* from this vector, /// meaning the homogeneous component will always have a value of 0.0. #[inline] pub fn into_homogeneous_vector(self) -> $v3t { $v3t { x: self.x, y: self.y, z: $t::splat(0.0) } } /// Create a 2d point from a homogeneous 2d *point*, performing /// division by the homogeneous component. This should not be used /// for homogeneous 2d *vectors*, which will have 0 as their /// homogeneous component. #[inline] pub fn from_homogeneous_point(v: $v3t) -> Self { Self { x: v.x / v.z, y: v.y / v.z } } /// Create a 2d vector from homogeneous 2d *vector*, which simply /// discards the homogeneous component. #[inline] pub fn from_homogeneous_vector(v: $v3t) -> Self { v.into() } #[inline] pub fn dot(&self, other: $n) -> $t { (self.x * other.x) + (self.y * other.y) } /// The wedge (aka exterior) product of two vectors. /// /// Note: Sometimes called "cross" product in 2D. /// Such a product is not well defined in 2 dimensions /// and is really just shorthand notation for a hacky operation that /// extends the vectors into 3 dimensions, takes the cross product, /// then returns only the resulting Z component as a pseudoscalar value. /// This value is will have the same value as /// the resulting bivector of the wedge product in 2d (a 2d /// bivector is also a kind of pseudoscalar value), so you may use /// this product to calculate the same value. /// /// This operation results in a bivector, which represents /// the plane parallel to the two vectors, and which has a /// 'oriented area' equal to the parallelogram created by extending /// the two vectors, oriented such that the positive direction is the /// one which would move `self` closer to `other`. #[inline] pub fn wedge(&self, other: $n) -> $bn { $bn::new((self.x * other.y) - (other.x * self.y)) } /// The geometric product of this and another vector, which /// is defined as the sum of the dot product and the wedge product. /// /// This operation results in a 'rotor', named as such as it may define /// a rotation. The rotor which results from the geometric product /// will rotate in the plane parallel to the two vectors, by twice the angle between /// them and in the opposite direction (i.e. it will rotate in the direction that would /// bring `other` towards `self`, and rotate in that direction by twice the angle between them). #[inline] pub fn geom(&self, other: $n) -> $rn { $rn::new(self.dot(other), self.wedge(other)) } #[inline] pub fn rotate_by(&mut self, rotor: $rn) { rotor.rotate_vec(self); } #[inline] pub fn rotated_by(mut self, rotor: $rn) -> Self { rotor.rotate_vec(&mut self); self } #[inline] pub fn reflected(&self, normal: $n) -> Self { *self - ($t::splat(2.0) * self.dot(normal) * normal) } #[inline] pub fn mag_sq(&self) -> $t { (self.x * self.x) + (self.y * self.y) } #[inline] pub fn mag(&self) -> $t { self.mag_sq().sqrt() } #[inline] pub fn normalize(&mut self) { let r_mag = $t::splat(1.0) /self.mag(); self.x *= r_mag; self.y *= r_mag; } #[inline] #[must_use = "Did you mean to use `.normalize()` to normalize `self` in place?"] pub fn normalized(&self) -> Self { let mut r = self.clone(); r.normalize(); r } #[inline] pub fn mul_add(&self, mul: $n, add: $n) -> Self { $n::new( self.x.mul_add(mul.x, add.x), self.y.mul_add(mul.y, add.y), ) } #[inline] pub fn abs(&self) -> Self { Self::new(self.x.abs(), self.y.abs()) } #[inline] pub fn clamp(&mut self, min: Self, max: Self) { self.x = self.x.max(min.x).min(max.x); self.y = self.y.max(min.y).min(max.y); } #[inline] pub fn clamped(mut self, min: Self, max: Self) -> Self { self.clamp(min, max); self } #[inline] pub fn map(&self, mut f: F) -> Self where F: FnMut($t) -> $t { $n::new( f(self.x), f(self.y), ) } #[inline] pub fn apply(&mut self, mut f: F) where F: FnMut($t) -> $t { self.x = f(self.x); self.y = f(self.y); } #[inline] pub fn max_by_component(mut self, other: Self) -> Self { self.x = self.x.max(other.x); self.y = self.y.max(other.y); self } #[inline] pub fn min_by_component(mut self, other: Self) -> Self { self.x = self.x.min(other.x); self.y = self.y.min(other.y); self } #[inline] pub fn component_max(&self) -> $t { self.x.max(self.y) } #[inline] pub fn component_min(&self) -> $t { self.x.min(self.y) } #[inline] pub fn zero() -> Self { Self::broadcast($t::splat(0.0)) } #[inline] pub fn one() -> Self { Self::broadcast($t::splat(1.0)) } #[inline] pub fn xyz(&self) -> $v3t { $v3t::new(self.x, self.y, $t::splat(0.0)) } #[inline] pub fn xyzw(&self) -> $v4t { $v4t::new(self.x, self.y, $t::splat(0.0), $t::splat(0.0)) } /// Get the [`core::alloc::Layout`] of `Self` #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } /// Interpret `self` as a statically-sized array of its base numeric type #[inline] pub fn as_array(&self) -> &[$t; 2] { let ptr = self as *const $n as *const [$t; 2]; unsafe { &*ptr } } /// Interpret `self` as a statically-sized array of its base numeric type #[inline] pub fn as_mut_array(&mut self) -> &mut [$t; 2] { let ptr = self as *mut $n as *mut [$t; 2]; unsafe { &mut *ptr } } /// Interpret `self` as a slice of its base numeric type #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $t, 2) } } /// Interpret `self` as a slice of its base numeric type #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $t, 2) } } #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const u8, 2 * std::mem::size_of::<$t>()) } } #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut u8, 2 * std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub const fn as_ptr(&self) -> *const $t { self as *const $n as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $n as *mut $t } } impl From<$n> for [$t; 2] { #[inline] fn from(v: $n) -> Self { [v.x, v.y] } } impl From<[$t; 2]> for $n { #[inline] fn from(comps: [$t; 2]) -> Self { Self::new(comps[0], comps[1]) } } impl From<&[$t; 2]> for $n { #[inline] fn from(comps: &[$t; 2]) -> Self { Self::from(*comps) } } impl From<&mut [$t; 2]> for $n { #[inline] fn from(comps: &mut [$t; 2]) -> Self { Self::from(*comps) } } impl From<($t, $t)> for $n { #[inline] fn from(comps: ($t, $t)) -> Self { Self::new(comps.0, comps.1) } } impl From<&($t, $t)> for $n { #[inline] fn from(comps: &($t, $t)) -> Self { Self::from(*comps) } } impl From<$n> for ($t, $t) { #[inline] fn from(v: $n) -> Self { (v.x, v.y) } } impl EqualsEps for $n { fn eq_eps(self, other: Self) -> bool { self.x.eq_eps(other.x) && self.y.eq_eps(other.y) } } impl Add for $n { type Output = Self; #[inline] fn add(self, rhs: $n) -> Self { $n::new(self.x + rhs.x, self.y + rhs.y) } } impl AddAssign for $n { #[inline] fn add_assign(&mut self, rhs: $n) { self.x += rhs.x; self.y += rhs.y; } } impl Sub for $n { type Output = Self; #[inline] fn sub(self, rhs: $n) -> Self { $n::new(self.x - rhs.x, self.y - rhs.y) } } impl SubAssign for $n { #[inline] fn sub_assign(&mut self, rhs: $n) { self.x -= rhs.x; self.y -= rhs.y; } } impl Mul for $n { type Output = Self; #[inline] fn mul(self, rhs: $n) -> Self { $n::new(self.x * rhs.x, self.y * rhs.y) } } impl Mul<$n> for $t { type Output = $n; #[inline] fn mul(self, rhs: $n) -> $n { $n::new(self * rhs.x, self * rhs.y) } } impl Mul<$t> for $n { type Output = $n; #[inline] fn mul(self, rhs: $t) -> $n { $n::new(self.x * rhs, self.y * rhs) } } impl MulAssign for $n { #[inline] fn mul_assign(&mut self, rhs: $n) { self.x *= rhs.x; self.y *= rhs.y; } } impl MulAssign<$t> for $n { #[inline] fn mul_assign(&mut self, rhs: $t) { self.x *= rhs; self.y *= rhs; } } impl Div for $n { type Output = Self; #[inline] fn div(self, rhs: $n) -> Self { $n::new(self.x / rhs.x, self.y / rhs.y) } } impl Div<$t> for $n { type Output = $n; #[inline] fn div(self, rhs: $t) -> $n { $n::new(self.x / rhs, self.y / rhs) } } impl DivAssign for $n { #[inline] fn div_assign(&mut self, rhs: $n) { self.x /= rhs.x; self.y /= rhs.y; } } impl DivAssign<$t> for $n { #[inline] fn div_assign(&mut self, rhs: $t) { self.x /= rhs; self.y /= rhs; } } impl Neg for $n { type Output = $n; #[inline] fn neg(self) -> $n { self * $t::splat(-1.0) } } impl Index for $n { type Output = $t; fn index(&self, index: usize) -> &Self::Output { match index { 0 => &self.x, 1 => &self.y, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } impl IndexMut for $n { fn index_mut(&mut self, index: usize) -> &mut Self::Output { match index { 0 => &mut self.x, 1 => &mut self.y, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } impl std::iter::Sum<$n> for $n { fn sum(iter: I) -> Self where I: Iterator { // Kahan summation algorithm // https://en.wikipedia.org/wiki/Kahan_summation_algorithm let mut sum = $n::zero(); let mut c = $n::zero(); for v in iter { let y = v - c; let t = sum + y; c = (t - sum) - y; sum = t; } sum } } )+ }; } // SCALAR VEC2 IMPLS macro_rules! impl_scalar_vec2s { ($(($vt:ident, $v3t:ident) => $t:ident),+) => { $(impl $vt { #[inline] pub fn refract(&mut self, normal: Self, eta: $t) { *self = self.refracted(normal, eta); } #[inline] pub fn refracted(&self, normal: Self, eta: $t) -> Self { let n = normal; let i = *self; let ndi = n.dot(i); let k = 1.0 - eta * eta * (1.0 - ndi * ndi); if k < 0.0 { Self::zero() } else { i * eta - (eta * ndi + k.sqrt()) * n } } } impl From<$v3t> for $vt { #[inline] fn from(vec: $v3t) -> Self { Self { x: vec.x, y: vec.y } } })+ }; } // WIDE VEC2 IMPLS macro_rules! impl_wide_vec2s { ($($vt:ident => $tt:ident, $t:ident, $maskt:ident, $nonwidet:ident, $v3t:ident),+) => { $(impl $vt { #[inline] pub fn new_splat(x: $tt, y: $tt) -> Self { Self { x: $t::splat(x), y: $t::splat(y), } } #[inline] pub fn splat(vec: $nonwidet) -> Self { Self { x: $t::splat(vec.x), y: $t::splat(vec.y), } } /// Blend two vectors together lanewise using `mask` as a mask. /// /// This is essentially a bitwise blend operation, such that any point where /// there is a 1 bit in `mask`, the output will put the bit from `tru`, while /// where there is a 0 bit in `mask`, the output will put the bit from `fals` #[inline] pub fn blend(mask: $maskt, tru: Self, fals: Self) -> Self { Self { x: mask.blend(tru.x, fals.x), y: mask.blend(tru.y, fals.y), } } #[inline] pub fn refract(&mut self, normal: Self, eta: $t) { *self = self.refracted(normal, eta); } #[inline] pub fn refracted(&self, normal: Self, eta: $t) -> Self { let n = normal; let i = *self; let one = $t::splat(1.0); let ndi = n.dot(i); let k = one - eta * eta * (one - ndi * ndi); let mask = k.cmp_lt($t::splat(0.0)); let out = i * eta - (eta * ndi + k.sqrt()) * n; Self::blend(mask, Self::zero(), out) } } impl From<$nonwidet> for $vt { #[inline] fn from(vec: $nonwidet) -> Self { Self::splat(vec) } } impl From<$v3t> for $vt { #[inline] fn from(vec: $v3t) -> Self { Self { x: vec.x, y: vec.y } } })+ } } impl From for [Vec2; 4] { #[inline] fn from(v: Vec2x4) -> Self { let xs: [f32; 4] = v.x.into(); let ys: [f32; 4] = v.y.into(); [ Vec2::new(xs[0], ys[0]), Vec2::new(xs[1], ys[1]), Vec2::new(xs[2], ys[2]), Vec2::new(xs[3], ys[3]), ] } } impl From<[Vec2; 4]> for Vec2x4 { #[inline] fn from(vecs: [Vec2; 4]) -> Self { Self { x: f32x4::from([vecs[0].x, vecs[1].x, vecs[2].x, vecs[3].x]), y: f32x4::from([vecs[0].y, vecs[1].y, vecs[2].y, vecs[3].y]), } } } impl From for [Vec2; 8] { #[inline] fn from(v: Vec2x8) -> Self { let xs: [f32; 8] = v.x.into(); let ys: [f32; 8] = v.y.into(); [ Vec2::new(xs[0], ys[0]), Vec2::new(xs[1], ys[1]), Vec2::new(xs[2], ys[2]), Vec2::new(xs[3], ys[3]), Vec2::new(xs[4], ys[4]), Vec2::new(xs[5], ys[5]), Vec2::new(xs[6], ys[6]), Vec2::new(xs[7], ys[7]), ] } } impl From<[Vec2; 8]> for Vec2x8 { #[inline] fn from(vecs: [Vec2; 8]) -> Self { Self { x: f32x8::from([ vecs[0].x, vecs[1].x, vecs[2].x, vecs[3].x, vecs[4].x, vecs[5].x, vecs[6].x, vecs[7].x, ]), y: f32x8::from([ vecs[0].y, vecs[1].y, vecs[2].y, vecs[3].y, vecs[4].y, vecs[5].y, vecs[6].y, vecs[7].y, ]), } } } #[cfg(feature = "f64")] impl From for [DVec2; 2] { #[inline] fn from(v: DVec2x2) -> Self { let xs: [f64; 2] = v.x.into(); let ys: [f64; 2] = v.y.into(); [DVec2::new(xs[0], ys[0]), DVec2::new(xs[1], ys[1])] } } #[cfg(feature = "f64")] impl From<[DVec2; 2]> for DVec2x2 { #[inline] fn from(vecs: [DVec2; 2]) -> Self { Self { x: f64x2::from([vecs[0].x, vecs[1].x]), y: f64x2::from([vecs[0].y, vecs[1].y]), } } } #[cfg(feature = "f64")] impl From for [DVec2; 4] { #[inline] fn from(v: DVec2x4) -> Self { let xs: [f64; 4] = v.x.into(); let ys: [f64; 4] = v.y.into(); [ DVec2::new(xs[0], ys[0]), DVec2::new(xs[1], ys[1]), DVec2::new(xs[2], ys[2]), DVec2::new(xs[3], ys[3]), ] } } #[cfg(feature = "f64")] impl From<[DVec2; 4]> for DVec2x4 { #[inline] fn from(vecs: [DVec2; 4]) -> Self { Self { x: f64x4::from([vecs[0].x, vecs[1].x, vecs[2].x, vecs[3].x]), y: f64x4::from([vecs[0].y, vecs[1].y, vecs[2].y, vecs[3].y]), } } } vec2s!( (Vec2, Bivec2, Rotor2, Vec3, Vec4) => f32, (Vec2x4, Bivec2x4, Rotor2x4, Vec3x4, Vec4x4) => f32x4, (Vec2x8, Bivec2x8, Rotor2x8, Vec3x8, Vec4x8) => f32x8 ); #[cfg(feature = "f64")] vec2s!( (DVec2, DBivec2, DRotor2, DVec3, DVec4) => f64, (DVec2x2, DBivec2x2, DRotor2x2, DVec3x2, DVec4x2) => f64x2, (DVec2x4, DBivec2x4, DRotor2x4, DVec3x4, DVec4x4) => f64x4 ); impl_scalar_vec2s!( (Vec2, Vec3) => f32 ); #[cfg(feature = "f64")] impl_scalar_vec2s!( (DVec2, DVec3) => f64 ); impl_wide_vec2s!( Vec2x4 => f32, f32x4, m32x4, Vec2, Vec3x4, Vec2x8 => f32, f32x8, m32x8, Vec2, Vec3x8 ); #[cfg(feature = "f64")] impl_wide_vec2s!( DVec2x2 => f64, f64x2, m64x2, DVec2, DVec3x2, DVec2x4 => f64, f64x4, m64x4, DVec2, DVec3x4 ); ultraviolet-0.10.0/src/vec/vec3.rs000064400000000000000000000653571046102023000150460ustar 00000000000000use std::ops::*; use crate::util::EqualsEps; use crate::*; macro_rules! vec3s { ($(($v2t:ident, $n:ident, $bn:ident, $rn:ident, $v4t:ident) => $t:ident),+) => { $(/// A set of three coordinates which may be interpreted as a point or vector in 3d space, /// or as a homogeneous 2d vector or point. /// /// Generally this distinction between a point and vector is more of a pain than it is worth /// to distinguish on a type level, however when converting to and from homogeneous /// coordinates it is quite important. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[repr(C)] pub struct $n { pub x: $t, pub y: $t, pub z: $t, } impl $n { #[inline] pub const fn new(x: $t, y: $t, z: $t) -> Self { $n { x, y, z } } #[inline] pub const fn broadcast(val: $t) -> Self { Self::new(val, val, val) } #[inline] pub fn unit_x() -> Self { $n{ x: $t::splat(1.0), y: $t::splat(0.0), z: $t::splat(0.0) } } #[inline] pub fn unit_y() -> Self { $n{ x: $t::splat(0.0), y: $t::splat(1.0), z: $t::splat(0.0) } } #[inline] pub fn unit_z() -> Self { $n{ x: $t::splat(0.0), y: $t::splat(0.0), z: $t::splat(1.0) } } /// Create a homogeneous 3d *point* from this vector interpreted as a point, /// meaning the homogeneous component will start with a value of 1.0. #[inline] pub fn into_homogeneous_point(self) -> $v4t { $v4t { x: self.x, y: self.y, z: self.z, w: $t::splat(1.0) } } /// Create a homogeneous 3d *vector* from this vector, /// meaning the homogeneous component will always have a value of 0.0. #[inline] pub fn into_homogeneous_vector(self) -> $v4t { $v4t { x: self.x, y: self.y, z: self.z, w: $t::splat(0.0) } } /// Create a 3d point from a homogeneous 3d *point*, performing /// division by the homogeneous component. This should not be used /// for homogeneous 3d *vectors*, which will have 0 as their /// homogeneous component. #[inline] pub fn from_homogeneous_point(v: $v4t) -> Self { Self { x: v.x / v.w, y: v.y / v.w, z: v.z / v.w } } /// Create a 3d vector from homogeneous 2d *vector*, which simply /// discards the homogeneous component. #[inline] pub fn from_homogeneous_vector(v: $v4t) -> Self { v.into() } #[inline] pub fn dot(&self, other: $n) -> $t { (self.x * other.x) + (self.y * other.y) + (self.z * other.z) } /// The wedge (aka exterior) product of two vectors. /// /// This operation results in a bivector, which represents /// the plane parallel to the two vectors, and which has a /// 'oriented area' equal to the parallelogram created by extending /// the two vectors, oriented such that the positive direction is the /// one which would move `self` closer to `other`. #[inline] pub fn wedge(&self, other: $n) -> $bn { $bn::new( (self.x * other.y) - (self.y * other.x), (self.x * other.z) - (self.z * other.x), (self.y * other.z) - (self.z * other.y), ) } /// The geometric product of this and another vector, which /// is defined as the sum of the dot product and the wedge product. /// /// This operation results in a 'rotor', named as such as it may define /// a rotation. The rotor which results from the geometric product /// will rotate in the plane parallel to the two vectors, by twice the angle between /// them and in the opposite direction (i.e. it will rotate in the direction that would /// bring `other` towards `self`, and rotate in that direction by twice the angle between them). #[inline] pub fn geom(&self, other: $n) -> $rn { $rn::new(self.dot(other), self.wedge(other)) } #[inline] pub fn rotate_by(&mut self, rotor: $rn) { rotor.rotate_vec(self); } #[inline] pub fn rotated_by(mut self, rotor: $rn) -> Self { rotor.rotate_vec(&mut self); self } #[inline] pub fn cross(&self, other: $n) -> Self { $n::new( (self.y * other.z) + (-self.z * other.y), (self.z * other.x) + (-self.x * other.z), (self.x * other.y) + (-self.y * other.x), ) } #[inline] pub fn reflect(&mut self, normal: $n) { *self -= $t::splat(2.0) * self.dot(normal) * normal; } #[inline] pub fn reflected(&self, normal: $n) -> Self { let mut a = *self; a.reflect(normal); a } #[inline] pub fn mag_sq(&self) -> $t { (self.x * self.x) + (self.y * self.y) + (self.z * self.z) } #[inline] pub fn mag(&self) -> $t { self.mag_sq().sqrt() } #[inline] pub fn normalize(&mut self) { let r_mag = $t::splat(1.0) / self.mag(); self.x *= r_mag; self.y *= r_mag; self.z *= r_mag; } #[inline] #[must_use = "Did you mean to use `.normalize()` to normalize `self` in place?"] pub fn normalized(&self) -> Self { let mut r = self.clone(); r.normalize(); r } /// Normalize `self` in-place by interpreting it as a homogeneous point, i.e. /// scaling the vector to ensure the homogeneous component has length 1. #[inline] pub fn normalize_homogeneous_point(&mut self) { let recip_z = $t::splat(1.0) / self.z; self.x *= recip_z; self.y *= recip_z; self.z = $t::splat(1.0); } /// Normalize `self` by interpreting it as a homogeneous point, i.e. /// scaling the vector to ensure the homogeneous component has length 1. #[inline] #[must_use = "Did you mean to use `.normalize_homogeneous_point()` to normalize `self` in place?"] pub fn normalized_homogeneous_point(&self) -> Self { let mut r = self.clone(); r.normalize_homogeneous_point(); r } /// Convert `self` into a Vec2 by simply removing its `z` component. #[inline] pub fn truncated(&self) -> $v2t { $v2t::new( self.x, self.y ) } #[inline] pub fn mul_add(&self, mul: $n, add: $n) -> Self { $n::new( self.x.mul_add(mul.x, add.x), self.y.mul_add(mul.y, add.y), self.z.mul_add(mul.z, add.z), ) } #[inline] pub fn abs(&self) -> Self { Self::new(self.x.abs(), self.y.abs(), self.z.abs()) } #[inline] pub fn clamp(&mut self, min: Self, max: Self) { self.x = self.x.max(min.x).min(max.x); self.y = self.y.max(min.y).min(max.y); self.z = self.z.max(min.z).min(max.z); } #[inline] pub fn clamped(mut self, min: Self, max: Self) -> Self { self.clamp(min, max); self } #[inline] pub fn map(&self, mut f: F) -> Self where F: FnMut($t) -> $t { $n::new( f(self.x), f(self.y), f(self.z) ) } #[inline] pub fn apply(&mut self, mut f: F) where F: FnMut($t) -> $t { self.x = f(self.x); self.y = f(self.y); self.z = f(self.z); } #[inline] pub fn max_by_component(mut self, other: Self) -> Self { self.x = self.x.max(other.x); self.y = self.y.max(other.y); self.z = self.z.max(other.z); self } #[inline] pub fn min_by_component(mut self, other: Self) -> Self { self.x = self.x.min(other.x); self.y = self.y.min(other.y); self.z = self.z.min(other.z); self } #[inline] pub fn component_max(&self) -> $t { self.x.max(self.y).max(self.z) } #[inline] pub fn component_min(&self) -> $t { self.x.min(self.y).min(self.z) } #[inline] pub fn zero() -> Self { Self::broadcast($t::splat(0.0)) } #[inline] pub fn one() -> Self { Self::broadcast($t::splat(1.0)) } #[inline] pub const fn xy(&self) -> $v2t { $v2t::new(self.x, self.y) } #[inline] pub fn xyzw(&self) -> $v4t { $v4t::new(self.x, self.y, self.z, $t::splat(0.0)) } /// Get the [`core::alloc::Layout`] of `Self` #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } /// Interpret `self` as a statically-sized array of its base numeric type #[inline] pub fn as_array(&self) -> &[$t; 3] { let ptr = self as *const $n as *const [$t; 3]; unsafe { &*ptr } } /// Interpret `self` as a statically-sized array of its base numeric type #[inline] pub fn as_mut_array(&mut self) -> &mut [$t; 3] { let ptr = self as *mut $n as *mut [$t; 3]; unsafe { &mut *ptr } } /// Interpret `self` as a slice of its base numeric type #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $t, 3) } } /// Interpret `self` as a slice of its base numeric type #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $t, 3) } } #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const u8, 3 * std::mem::size_of::<$t>()) } } #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut u8, 3 * std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub const fn as_ptr(&self) -> *const $t { self as *const $n as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $n as *mut $t } } impl EqualsEps for $n { fn eq_eps(self, other: Self) -> bool { self.x.eq_eps(other.x) && self.y.eq_eps(other.y) && self.z.eq_eps(other.z) } } impl From<$n> for [$t; 3] { #[inline] fn from(v: $n) -> Self { [v.x, v.y, v.z] } } impl From<[$t; 3]> for $n { #[inline] fn from(comps: [$t; 3]) -> Self { Self::new(comps[0], comps[1], comps[2]) } } impl From<&[$t; 3]> for $n { #[inline] fn from(comps: &[$t; 3]) -> Self { Self::from(*comps) } } impl From<&mut [$t; 3]> for $n { #[inline] fn from(comps: &mut [$t; 3]) -> Self { Self::from(*comps) } } impl From<($t, $t, $t)> for $n { #[inline] fn from(comps: ($t, $t, $t)) -> Self { Self::new(comps.0, comps.1, comps.2) } } impl From<&($t, $t, $t)> for $n { #[inline] fn from(comps: &($t, $t, $t)) -> Self { Self::from(*comps) } } impl From<$n> for ($t, $t, $t) { #[inline] fn from(v: $n) -> Self { (v.x, v.y, v.z) } } impl Add for $n { type Output = Self; #[inline] fn add(self, rhs: $n) -> Self { $n::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) } } impl AddAssign for $n { #[inline] fn add_assign(&mut self, rhs: $n) { self.x += rhs.x; self.y += rhs.y; self.z += rhs.z; } } impl Sub for $n { type Output = Self; #[inline] fn sub(self, rhs: $n) -> Self { $n::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) } } impl SubAssign for $n { #[inline] fn sub_assign(&mut self, rhs: $n) { self.x -= rhs.x; self.y -= rhs.y; self.z -= rhs.z; } } impl Mul for $n { type Output = Self; #[inline] fn mul(self, rhs: $n) -> Self { $n::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z) } } impl Mul<$n> for $t { type Output = $n; #[inline] fn mul(self, rhs: $n) -> $n { $n::new(self * rhs.x, self * rhs.y, self * rhs.z) } } impl Mul<$t> for $n { type Output = $n; #[inline] fn mul(self, rhs: $t) -> $n { $n::new(self.x * rhs, self.y * rhs, self.z * rhs) } } impl MulAssign for $n { #[inline] fn mul_assign(&mut self, rhs: $n) { self.x *= rhs.x; self.y *= rhs.y; self.z *= rhs.z; } } impl MulAssign<$t> for $n { #[inline] fn mul_assign(&mut self, rhs: $t) { self.x *= rhs; self.y *= rhs; self.z *= rhs; } } impl Div for $n { type Output = Self; #[inline] fn div(self, rhs: $n) -> Self { $n::new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z) } } impl Div<$t> for $n { type Output = $n; #[inline] fn div(self, rhs: $t) -> $n { $n::new(self.x / rhs, self.y / rhs, self.z / rhs) } } impl DivAssign for $n { #[inline] fn div_assign(&mut self, rhs: $n) { self.x /= rhs.x; self.y /= rhs.y; self.z /= rhs.z; } } impl DivAssign<$t> for $n { #[inline] fn div_assign(&mut self, rhs: $t) { self.x /= rhs; self.y /= rhs; self.z /= rhs; } } impl Neg for $n { type Output = $n; #[inline] fn neg(self) -> $n { self * $t::splat(-1.0) } } impl Index for $n { type Output = $t; fn index(&self, index: usize) -> &Self::Output { match index { 0 => &self.x, 1 => &self.y, 2 => &self.z, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } impl IndexMut for $n { fn index_mut(&mut self, index: usize) -> &mut Self::Output { match index { 0 => &mut self.x, 1 => &mut self.y, 2 => &mut self.z, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } impl std::iter::Sum<$n> for $n { fn sum(iter: I) -> Self where I: Iterator { // Kahan summation algorithm // https://en.wikipedia.org/wiki/Kahan_summation_algorithm let mut sum = $n::zero(); let mut c = $n::zero(); for v in iter { let y = v - c; let t = sum + y; c = (t - sum) - y; sum = t; } sum } } )+ } } // SCALAR VEC3 IMPLS macro_rules! impl_scalar_vec3s { ($(($vt:ident, $v2t:ident, $v4t:ident) => $t:ident),+) => { $(impl $vt { #[inline] pub fn refract(&mut self, normal: Self, eta: $t) { *self = self.refracted(normal, eta); } #[inline] pub fn refracted(&self, normal: Self, eta: $t) -> Self { let n = normal; let i = *self; let ndi = n.dot(i); let k = 1.0 - eta * eta * (1.0 - ndi * ndi); if k < 0.0 { Self::zero() } else { i * eta - (eta * ndi + k.sqrt()) * n } } } impl From<$v2t> for $vt { #[inline] fn from(vec: $v2t) -> Self { Self { x: vec.x, y: vec.y, z: 0.0, } } } impl From<$v4t> for $vt { #[inline] fn from(vec: $v4t) -> Self { Self { x: vec.x, y: vec.y, z: vec.z, } } })+ }; } // WIDE VEC3 IMPLS macro_rules! impl_wide_vec3s { ($($vt:ident => $tt:ident, $t:ident, $maskt:ident, $nonwidet:ident, $v2t:ident, $v4t:ident),+) => { $(impl $vt { #[inline] pub fn new_splat(x: $tt, y: $tt, z: $tt) -> Self { Self { x: $t::splat(x), y: $t::splat(y), z: $t::splat(z), } } #[inline] pub fn splat(vec: $nonwidet) -> Self { Self { x: $t::splat(vec.x), y: $t::splat(vec.y), z: $t::splat(vec.z), } } /// Blend two vectors together lanewise using `mask` as a mask. /// /// This is essentially a bitwise blend operation, such that any point where /// there is a 1 bit in `mask`, the output will put the bit from `tru`, while /// where there is a 0 bit in `mask`, the output will put the bit from `fals` #[inline] pub fn blend(mask: $maskt, tru: Self, fals: Self) -> Self { Self { x: mask.blend(tru.x, fals.x), y: mask.blend(tru.y, fals.y), z: mask.blend(tru.z, fals.z), } } #[inline] pub fn refract(&mut self, normal: Self, eta: $t) { *self = self.refracted(normal, eta); } #[inline] pub fn refracted(&self, normal: Self, eta: $t) -> Self { let n = normal; let i = *self; let one = $t::splat(1.0); let ndi = n.dot(i); let k = one - eta * eta * (one - ndi * ndi); let mask = k.cmp_lt($t::splat(0.0)); let out = i.mul_add(Self::broadcast(eta), -(eta * ndi + k.sqrt()) * n); Self::blend(mask, Self::zero(), out) } } impl From<$v2t> for $vt { #[inline] fn from(vec: $v2t) -> Self { Self { x: vec.x, y: vec.y, z: $t::splat(0.0), } } } impl From<$v4t> for $vt { #[inline] fn from(vec: $v4t) -> Self { Self { x: vec.x, y: vec.y, z: vec.z, } } })+ } } impl From for [Vec3; 4] { #[inline] fn from(v: Vec3x4) -> Self { let xs: [f32; 4] = v.x.into(); let ys: [f32; 4] = v.y.into(); let zs: [f32; 4] = v.z.into(); [ Vec3::new(xs[0], ys[0], zs[0]), Vec3::new(xs[1], ys[1], zs[1]), Vec3::new(xs[2], ys[2], zs[2]), Vec3::new(xs[3], ys[3], zs[3]), ] } } impl From<[Vec3; 4]> for Vec3x4 { #[inline] fn from(vecs: [Vec3; 4]) -> Self { Self { x: f32x4::from([vecs[0].x, vecs[1].x, vecs[2].x, vecs[3].x]), y: f32x4::from([vecs[0].y, vecs[1].y, vecs[2].y, vecs[3].y]), z: f32x4::from([vecs[0].z, vecs[1].z, vecs[2].z, vecs[3].z]), } } } impl From for [Vec3; 8] { #[inline] fn from(v: Vec3x8) -> [Vec3; 8] { let xs: [f32; 8] = v.x.into(); let ys: [f32; 8] = v.y.into(); let zs: [f32; 8] = v.z.into(); [ Vec3::new(xs[0], ys[0], zs[0]), Vec3::new(xs[1], ys[1], zs[1]), Vec3::new(xs[2], ys[2], zs[2]), Vec3::new(xs[3], ys[3], zs[3]), Vec3::new(xs[4], ys[4], zs[4]), Vec3::new(xs[5], ys[5], zs[5]), Vec3::new(xs[6], ys[6], zs[6]), Vec3::new(xs[7], ys[7], zs[7]), ] } } impl From<[Vec3; 8]> for Vec3x8 { #[inline] fn from(vecs: [Vec3; 8]) -> Self { Self { x: f32x8::from([ vecs[0].x, vecs[1].x, vecs[2].x, vecs[3].x, vecs[4].x, vecs[5].x, vecs[6].x, vecs[7].x, ]), y: f32x8::from([ vecs[0].y, vecs[1].y, vecs[2].y, vecs[3].y, vecs[4].y, vecs[5].y, vecs[6].y, vecs[7].y, ]), z: f32x8::from([ vecs[0].z, vecs[1].z, vecs[2].z, vecs[3].z, vecs[4].z, vecs[5].z, vecs[6].z, vecs[7].z, ]), } } } #[cfg(feature = "f64")] impl From for [DVec3; 2] { #[inline] fn from(v: DVec3x2) -> Self { let xs: [f64; 2] = v.x.into(); let ys: [f64; 2] = v.y.into(); let zs: [f64; 2] = v.z.into(); [ DVec3::new(xs[0], ys[0], zs[0]), DVec3::new(xs[1], ys[1], zs[1]), ] } } #[cfg(feature = "f64")] impl From<[DVec3; 2]> for DVec3x2 { #[inline] fn from(vecs: [DVec3; 2]) -> Self { Self { x: f64x2::from([vecs[0].x, vecs[1].x]), y: f64x2::from([vecs[0].y, vecs[1].y]), z: f64x2::from([vecs[0].z, vecs[1].z]), } } } #[cfg(feature = "f64")] impl From for [DVec3; 4] { fn from(v: DVec3x4) -> Self { let xs: [f64; 4] = v.x.into(); let ys: [f64; 4] = v.y.into(); let zs: [f64; 4] = v.z.into(); [ DVec3::new(xs[0], ys[0], zs[0]), DVec3::new(xs[1], ys[1], zs[1]), DVec3::new(xs[2], ys[2], zs[2]), DVec3::new(xs[3], ys[3], zs[3]), ] } } #[cfg(feature = "f64")] impl From<[DVec3; 4]> for DVec3x4 { #[inline] fn from(vecs: [DVec3; 4]) -> Self { Self { x: f64x4::from([vecs[0].x, vecs[1].x, vecs[2].x, vecs[3].x]), y: f64x4::from([vecs[0].y, vecs[1].y, vecs[2].y, vecs[3].y]), z: f64x4::from([vecs[0].z, vecs[1].z, vecs[2].z, vecs[3].z]), } } } vec3s!( (Vec2, Vec3, Bivec3, Rotor3, Vec4) => f32, (Vec2x4, Vec3x4, Bivec3x4, Rotor3x4, Vec4x4) => f32x4, (Vec2x8, Vec3x8, Bivec3x8, Rotor3x8, Vec4x8) => f32x8 ); #[cfg(feature = "f64")] vec3s!( (DVec2, DVec3, DBivec3, DRotor3, DVec4) => f64, (DVec2x2, DVec3x2, DBivec3x2, DRotor3x2, DVec4x2) => f64x2, (DVec2x4, DVec3x4, DBivec3x4, DRotor3x4, DVec4x4) => f64x4 ); impl_scalar_vec3s!( (Vec3, Vec2, Vec4) => f32 ); #[cfg(feature = "f64")] impl_scalar_vec3s!( (DVec3, DVec2, DVec4) => f64 ); impl_wide_vec3s!( Vec3x4 => f32, f32x4, m32x4, Vec3, Vec2x4, Vec4x4, Vec3x8 => f32, f32x8, m32x8, Vec3, Vec2x8, Vec4x8 ); #[cfg(feature = "f64")] impl_wide_vec3s!( DVec3x2 => f64, f64x2, m64x2, DVec3, DVec2x2, DVec4x2, DVec3x4 => f64, f64x4, m64x4, DVec3, DVec2x4, DVec4x4 ); ultraviolet-0.10.0/src/vec/vec4.rs000064400000000000000000000601011046102023000150250ustar 00000000000000use std::ops::*; use crate::util::EqualsEps; use crate::*; macro_rules! vec4s { ($($n:ident, $v2t:ident, $v3t:ident => $t:ident),+) => { $(/// A set of four coordinates which may be interpreted as a point or vector in 4d space, /// or as a homogeneous 3d vector or point. /// /// Generally this distinction between a point and vector is more of a pain than it is worth /// to distinguish on a type level, however when converting to and from homogeneous /// coordinates it is quite important. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[repr(C)] pub struct $n { pub x: $t, pub y: $t, pub z: $t, pub w: $t, } impl $n { #[inline] pub const fn new(x: $t, y: $t, z: $t, w: $t) -> Self { $n { x, y, z, w } } #[inline] pub const fn broadcast(val: $t) -> Self { Self::new(val, val, val, val) } #[inline] pub fn unit_x() -> Self { $n{ x: $t::splat(1.0), y: $t::splat(0.0), z: $t::splat(0.0), w: $t::splat(0.0) } } #[inline] pub fn unit_y() -> Self { $n{ x: $t::splat(0.0), y: $t::splat(1.0), z: $t::splat(0.0), w: $t::splat(0.0) } } #[inline] pub fn unit_z() -> Self { $n{ x: $t::splat(0.0), y: $t::splat(0.0), z: $t::splat(1.0), w: $t::splat(0.0) } } #[inline] pub fn unit_w() -> Self { $n{ x: $t::splat(0.0), y: $t::splat(0.0), z: $t::splat(0.0), w: $t::splat(1.0) } } #[inline] pub fn dot(&self, other: $n) -> $t { (self.x * other.x) + (self.y * other.y) + (self.z * other.z) + (self.w * other.w) } #[inline] pub fn reflect(&mut self, normal: $n) { *self -= $t::splat(2.0) * self.dot(normal) * normal; } #[inline] pub fn reflected(&self, normal: $n) -> Self { let mut a = *self; a.reflect(normal); a } #[inline] pub fn mag_sq(&self) -> $t { (self.x * self.x) + (self.y * self.y) + (self.z * self.z) + (self.w * self.w) } #[inline] pub fn mag(&self) -> $t { self.mag_sq().sqrt() } #[inline] pub fn normalize(&mut self) { let r_mag = $t::splat(1.0) / self.mag(); self.x *= r_mag; self.y *= r_mag; self.z *= r_mag; self.w *= r_mag; } #[inline] #[must_use = "Did you mean to use `.normalize()` to normalize `self` in place?"] pub fn normalized(&self) -> Self { let mut r = self.clone(); r.normalize(); r } /// Normalize `self` in-place by interpreting it as a homogeneous point, i.e. /// scaling the vector to ensure the homogeneous component has length 1. #[inline] pub fn normalize_homogeneous_point(&mut self) { let recip_z = $t::splat(1.0) / self.w; self.x *= recip_z; self.y *= recip_z; self.z *= recip_z; self.w = $t::splat(1.0); } /// Normalize `self` by interpreting it as a homogeneous point, i.e. /// scaling the vector to ensure the homogeneous component has length 1. #[must_use = "Did you mean to use `.normalize_homogeneous_point()` to normalize `self` in place?"] #[inline] pub fn normalized_homogeneous_point(&self) -> Self{ let mut r = self.clone(); r.normalize_homogeneous_point(); r } /// Convert `self` into a Vec3 by simply removing its `w` component. #[inline] pub fn truncated(&self) -> $v3t { $v3t::new( self.x, self.y, self.z ) } #[inline] pub fn mul_add(&self, mul: $n, add: $n) -> Self { $n::new( self.x.mul_add(mul.x, add.x), self.y.mul_add(mul.y, add.y), self.z.mul_add(mul.z, add.z), self.w.mul_add(mul.w, add.w), ) } #[inline] pub fn abs(&self) -> Self { Self::new(self.x.abs(), self.y.abs(), self.z.abs(), self.w.abs()) } #[inline] pub fn clamp(&mut self, min: Self, max: Self) { self.x = self.x.max(min.x).min(max.x); self.y = self.y.max(min.y).min(max.y); self.z = self.z.max(min.z).min(max.z); self.w = self.w.max(min.w).min(max.w); } #[inline] pub fn clamped(mut self, min: Self, max: Self) -> Self { self.clamp(min, max); self } #[inline] pub fn map(&self, mut f: F) -> Self where F: FnMut($t) -> $t { $n::new( f(self.x), f(self.y), f(self.z), f(self.w), ) } #[inline] pub fn apply(&mut self, mut f: F) where F: FnMut($t) -> $t { self.x = f(self.x); self.y = f(self.y); self.z = f(self.z); self.w = f(self.w); } #[inline] pub fn max_by_component(mut self, other: Self) -> Self { self.x = self.x.max(other.x); self.y = self.y.max(other.y); self.z = self.z.max(other.z); self.w = self.w.max(other.w); self } #[inline] pub fn min_by_component(mut self, other: Self) -> Self { self.x = self.x.min(other.x); self.y = self.y.min(other.y); self.z = self.z.min(other.z); self.w = self.w.min(other.w); self } #[inline] pub fn component_max(&self) -> $t { self.x.max(self.y).max(self.z).max(self.w) } #[inline] pub fn component_min(&self) -> $t { self.x.min(self.y).min(self.z).min(self.w) } #[inline] pub fn zero() -> Self { Self::broadcast($t::splat(0.0)) } #[inline] pub fn one() -> Self { Self::broadcast($t::splat(1.0)) } #[inline] pub const fn xy(&self) -> $v2t { $v2t::new(self.x, self.y) } #[inline] pub const fn xyz(&self) -> $v3t { $v3t::new(self.x, self.y, self.z) } /// Get the [`core::alloc::Layout`] of `Self` #[inline] pub fn layout() -> alloc::alloc::Layout { alloc::alloc::Layout::from_size_align(std::mem::size_of::(), std::mem::align_of::<$t>()).unwrap() } /// Interpret `self` as a statically-sized array of its base numeric type #[inline] pub fn as_array(&self) -> &[$t; 4] { let ptr = self as *const $n as *const [$t; 4]; unsafe { &*ptr } } /// Interpret `self` as a statically-sized array of its base numeric type #[inline] pub fn as_mut_array(&mut self) -> &mut [$t; 4] { let ptr = self as *mut $n as *mut [$t; 4]; unsafe { &mut *ptr } } /// Interpret `self` as a slice of its base numeric type #[inline] pub fn as_slice(&self) -> &[$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const $t, 4) } } /// Interpret `self` as a slice of its base numeric type #[inline] pub fn as_mut_slice(&mut self) -> &mut [$t] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut $t, 4) } } #[inline] pub fn as_byte_slice(&self) -> &[u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts(self as *const $n as *const u8, 4 * std::mem::size_of::<$t>()) } } #[inline] pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { // This is safe because we are statically bounding our slices to the size of these // vectors unsafe { std::slice::from_raw_parts_mut(self as *mut $n as *mut u8, 4 * std::mem::size_of::<$t>()) } } /// Returns a constant unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub const fn as_ptr(&self) -> *const $t { self as *const $n as *const $t } /// Returns a mutable unsafe pointer to the underlying data in the underlying type. /// This function is safe because all types here are repr(C) and can be represented /// as their underlying type. /// /// # Safety /// /// It is up to the caller to correctly use this pointer and its bounds. #[inline] pub fn as_mut_ptr(&mut self) -> *mut $t { self as *mut $n as *mut $t } } impl EqualsEps for $n { fn eq_eps(self, other: Self) -> bool { self.x.eq_eps(other.x) && self.y.eq_eps(other.y) && self.z.eq_eps(other.z) && self.w.eq_eps(other.w) } } impl From<$n> for [$t; 4] { #[inline] fn from(v: $n) -> Self { [v.x, v.y, v.z, v.w] } } impl From<[$t; 4]> for $n { #[inline] fn from(comps: [$t; 4]) -> Self { Self::new(comps[0], comps[1], comps[2], comps[3]) } } impl From<&[$t; 4]> for $n { #[inline] fn from(comps: &[$t; 4]) -> Self { Self::from(*comps) } } impl From<&mut [$t; 4]> for $n { #[inline] fn from(comps: &mut [$t; 4]) -> Self { Self::from(*comps) } } impl From<($t, $t, $t, $t)> for $n { #[inline] fn from(comps: ($t, $t, $t, $t)) -> Self { Self::new(comps.0, comps.1, comps.2, comps.3) } } impl From<&($t, $t, $t, $t)> for $n { #[inline] fn from(comps: &($t, $t, $t, $t)) -> Self { Self::from(*comps) } } impl From<$n> for ($t, $t, $t, $t) { #[inline] fn from(v: $n) -> Self { (v.x, v.y, v.z, v.w) } } impl Add for $n { type Output = Self; #[inline] fn add(self, rhs: $n) -> Self { $n::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z, self.w + rhs.w) } } impl AddAssign for $n { #[inline] fn add_assign(&mut self, rhs: $n) { self.x += rhs.x; self.y += rhs.y; self.z += rhs.z; self.w += rhs.w; } } impl Sub for $n { type Output = Self; #[inline] fn sub(self, rhs: $n) -> Self { $n::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z, self.w - rhs.w) } } impl SubAssign for $n { #[inline] fn sub_assign(&mut self, rhs: $n) { self.x -= rhs.x; self.y -= rhs.y; self.z -= rhs.z; self.w -= rhs.w; } } impl Mul for $n { type Output = Self; #[inline] fn mul(self, rhs: $n) -> Self { $n::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z, self.w * rhs. w) } } impl Mul<$n> for $t { type Output = $n; #[inline] fn mul(self, rhs: $n) -> $n { $n::new(self * rhs.x, self * rhs.y, self * rhs.z, self * rhs.w) } } impl Mul<$t> for $n { type Output = $n; #[inline] fn mul(self, rhs: $t) -> $n { $n::new(self.x * rhs, self.y * rhs, self.z * rhs, self.w * rhs) } } impl MulAssign for $n { #[inline] fn mul_assign(&mut self, rhs: $n) { self.x *= rhs.x; self.y *= rhs.y; self.z *= rhs.z; self.w *= rhs.w; } } impl MulAssign<$t> for $n { #[inline] fn mul_assign(&mut self, rhs: $t) { self.x *= rhs; self.y *= rhs; self.z *= rhs; self.w *= rhs; } } impl Div for $n { type Output = Self; #[inline] fn div(self, rhs: $n) -> Self { $n::new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z, self.w / rhs.w) } } impl Div<$t> for $n { type Output = $n; #[inline] fn div(self, rhs: $t) -> $n { $n::new(self.x / rhs, self.y / rhs, self.z / rhs, self.w / rhs) } } impl DivAssign for $n { #[inline] fn div_assign(&mut self, rhs: $n) { self.x /= rhs.x; self.y /= rhs.y; self.z /= rhs.z; self.w /= rhs.w; } } impl DivAssign<$t> for $n { #[inline] fn div_assign(&mut self, rhs: $t) { self.x /= rhs; self.y /= rhs; self.z /= rhs; self.w /= rhs; } } impl Neg for $n { type Output = $n; #[inline] fn neg(self) -> $n { self * $t::splat(-1.0) } } impl Index for $n { type Output = $t; fn index(&self, index: usize) -> &Self::Output { match index { 0 => &self.x, 1 => &self.y, 2 => &self.z, 3 => &self.w, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } impl IndexMut for $n { fn index_mut(&mut self, index: usize) -> &mut Self::Output { match index { 0 => &mut self.x, 1 => &mut self.y, 2 => &mut self.z, 3 => &mut self.w, _ => panic!("Invalid for vector of type: {}", std::any::type_name::<$n>()), } } } impl std::iter::Sum<$n> for $n { fn sum(iter: I) -> Self where I: Iterator { // Kahan summation algorithm // https://en.wikipedia.org/wiki/Kahan_summation_algorithm let mut sum = $n::zero(); let mut c = $n::zero(); for v in iter { let y = v - c; let t = sum + y; c = (t - sum) - y; sum = t; } sum } } )+ } } // SCALAR VEC4 IMPLS macro_rules! impl_scalar_vec4s { ($(($vt:ident, $v3t:ident) => $t:ident),+) => { $(impl $vt { #[inline] pub fn refract(&mut self, normal: Self, eta: $t) { *self = self.refracted(normal, eta); } #[inline] pub fn refracted(&self, normal: Self, eta: $t) -> Self { let n = normal; let i = *self; let ndi = n.dot(i); let k = 1.0 - eta * eta * (1.0 - ndi * ndi); if k < 0.0 { Self::zero() } else { i * eta - (eta * ndi + k.sqrt()) * n } } } impl From<$v3t> for $vt { #[inline] fn from(vec: $v3t) -> Self { Self { x: vec.x, y: vec.y, z: vec.z, w: 0.0, } } })+ } } // WIDE VEC4 IMPLS macro_rules! impl_wide_vec4s { ($($vt:ident => $tt:ident, $t:ident, $maskt:ident, $nonwidet:ident, $v3t:ident),+) => { $(impl $vt { #[inline] pub fn new_splat(x: $tt, y: $tt, z: $tt, w: $tt) -> Self { Self { x: $t::splat(x), y: $t::splat(y), z: $t::splat(z), w: $t::splat(w), } } #[inline] pub fn splat(vec: $nonwidet) -> Self { Self { x: $t::splat(vec.x), y: $t::splat(vec.y), z: $t::splat(vec.z), w: $t::splat(vec.w), } } /// Blend two vectors together lanewise using `mask` as a mask. /// /// This is essentially a bitwise blend operation, such that any point where /// there is a 1 bit in `mask`, the output will put the bit from `tru`, while /// where there is a 0 bit in `mask`, the output will put the bit from `fals` #[inline] pub fn blend(mask: $maskt, tru: Self, fals: Self) -> Self { Self { x: mask.blend(tru.x, fals.x), y: mask.blend(tru.y, fals.y), z: mask.blend(tru.z, fals.z), w: mask.blend(tru.w, fals.w), } } } impl From<$nonwidet> for $vt { #[inline] fn from(vec: $nonwidet) -> Self { Self::splat(vec) } } impl From<$v3t> for $vt { #[inline] fn from(vec: $v3t) -> Self { Self { x: vec.x, y: vec.y, z: vec.z, w: $t::splat(0.0), } } })+ }; } impl From for [Vec4; 4] { #[inline] fn from(v: Vec4x4) -> [Vec4; 4] { let xs: [f32; 4] = v.x.into(); let ys: [f32; 4] = v.y.into(); let zs: [f32; 4] = v.z.into(); let ws: [f32; 4] = v.w.into(); [ Vec4::new(xs[0], ys[0], zs[0], ws[0]), Vec4::new(xs[1], ys[1], zs[1], ws[1]), Vec4::new(xs[2], ys[2], zs[2], ws[2]), Vec4::new(xs[3], ys[3], zs[3], ws[3]), ] } } impl From<[Vec4; 4]> for Vec4x4 { #[inline] fn from(vecs: [Vec4; 4]) -> Self { Self { x: f32x4::from([vecs[0].x, vecs[1].x, vecs[2].x, vecs[3].x]), y: f32x4::from([vecs[0].y, vecs[1].y, vecs[2].y, vecs[3].y]), z: f32x4::from([vecs[0].z, vecs[1].z, vecs[2].z, vecs[3].z]), w: f32x4::from([vecs[0].w, vecs[1].w, vecs[2].w, vecs[3].w]), } } } impl From for [Vec4; 8] { #[inline] fn from(v: Vec4x8) -> [Vec4; 8] { let xs: [f32; 8] = v.x.into(); let ys: [f32; 8] = v.y.into(); let zs: [f32; 8] = v.z.into(); let ws: [f32; 8] = v.w.into(); [ Vec4::new(xs[0], ys[0], zs[0], ws[0]), Vec4::new(xs[1], ys[1], zs[1], ws[1]), Vec4::new(xs[2], ys[2], zs[2], ws[2]), Vec4::new(xs[3], ys[3], zs[3], ws[3]), Vec4::new(xs[4], ys[4], zs[4], ws[4]), Vec4::new(xs[5], ys[5], zs[5], ws[5]), Vec4::new(xs[6], ys[6], zs[6], ws[6]), Vec4::new(xs[7], ys[7], zs[7], ws[7]), ] } } impl From<[Vec4; 8]> for Vec4x8 { #[inline] fn from(vecs: [Vec4; 8]) -> Self { Self { x: f32x8::from([ vecs[0].x, vecs[1].x, vecs[2].x, vecs[3].x, vecs[4].x, vecs[5].x, vecs[6].x, vecs[7].x, ]), y: f32x8::from([ vecs[0].y, vecs[1].y, vecs[2].y, vecs[3].y, vecs[4].y, vecs[5].y, vecs[6].y, vecs[7].y, ]), z: f32x8::from([ vecs[0].z, vecs[1].z, vecs[2].z, vecs[3].z, vecs[4].z, vecs[5].z, vecs[6].z, vecs[7].z, ]), w: f32x8::from([ vecs[0].w, vecs[1].w, vecs[2].w, vecs[3].w, vecs[4].w, vecs[5].w, vecs[6].w, vecs[7].w, ]), } } } #[cfg(feature = "f64")] impl From for [DVec4; 2] { #[inline] fn from(v: DVec4x2) -> Self { let xs: [f64; 2] = v.x.into(); let ys: [f64; 2] = v.y.into(); let zs: [f64; 2] = v.z.into(); let ws: [f64; 2] = v.w.into(); [ DVec4::new(xs[0], ys[0], zs[0], ws[0]), DVec4::new(xs[1], ys[1], zs[1], ws[1]), ] } } #[cfg(feature = "f64")] impl From<[DVec4; 2]> for DVec4x2 { #[inline] fn from(vecs: [DVec4; 2]) -> Self { Self { x: f64x2::from([vecs[0].x, vecs[1].x]), y: f64x2::from([vecs[0].y, vecs[1].y]), z: f64x2::from([vecs[0].z, vecs[1].z]), w: f64x2::from([vecs[0].w, vecs[1].w]), } } } #[cfg(feature = "f64")] impl From for [DVec4; 4] { #[inline] fn from(v: DVec4x4) -> Self { let xs: [f64; 4] = v.x.into(); let ys: [f64; 4] = v.y.into(); let zs: [f64; 4] = v.z.into(); let ws: [f64; 4] = v.w.into(); [ DVec4::new(xs[0], ys[0], zs[0], ws[0]), DVec4::new(xs[1], ys[1], zs[1], ws[1]), DVec4::new(xs[2], ys[2], zs[2], ws[2]), DVec4::new(xs[3], ys[3], zs[3], ws[3]), ] } } #[cfg(feature = "f64")] impl From<[DVec4; 4]> for DVec4x4 { #[inline] fn from(vecs: [DVec4; 4]) -> Self { Self { x: f64x4::from([vecs[0].x, vecs[1].x, vecs[2].x, vecs[3].x]), y: f64x4::from([vecs[0].y, vecs[1].y, vecs[2].y, vecs[3].y]), z: f64x4::from([vecs[0].z, vecs[1].z, vecs[2].z, vecs[3].z]), w: f64x4::from([vecs[0].w, vecs[1].w, vecs[2].w, vecs[3].w]), } } } vec4s!( Vec4, Vec2, Vec3 => f32, Vec4x4, Vec2x4, Vec3x4 => f32x4, Vec4x8, Vec2x8, Vec3x8 => f32x8 ); #[cfg(feature = "f64")] vec4s!( DVec4, DVec2, DVec3 => f64, DVec4x2, DVec2x2, DVec3x2 => f64x2, DVec4x4, DVec2x4, DVec3x4 => f64x4 ); impl_scalar_vec4s!( (Vec4, Vec3) => f32 ); #[cfg(feature = "f64")] impl_scalar_vec4s!( (DVec4, DVec3) => f64 ); impl_wide_vec4s!( Vec4x4 => f32, f32x4, m32x4, Vec4, Vec3x4, Vec4x8 => f32, f32x8, m32x8, Vec4, Vec3x8 ); #[cfg(feature = "f64")] impl_wide_vec4s!( DVec4x2 => f64, f64x2, m64x2, DVec4, DVec3x2, DVec4x4 => f64, f64x4, m64x4, DVec4, DVec3x4 );