├── .github └── workflows │ └── main.yml ├── .gitignore ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── angle.rs ├── approxeq.rs ├── approxord.rs ├── box2d.rs ├── box3d.rs ├── homogen.rs ├── length.rs ├── lib.rs ├── macros.rs ├── num.rs ├── point.rs ├── rect.rs ├── rigid.rs ├── rotation.rs ├── scale.rs ├── side_offsets.rs ├── size.rs ├── transform2d.rs ├── transform3d.rs ├── translation.rs ├── trig.rs └── vector.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | workflow_dispatch: 8 | merge_group: 9 | types: [checks_requested] 10 | 11 | jobs: 12 | linux-ci: 13 | name: Linux 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | features: ["", "--features serde", "--no-default-features --features libm"] 18 | version: ["1.63.0", "stable", "beta", "nightly"] 19 | include: 20 | - version: stable 21 | features: --features mint 22 | - version: stable 23 | features: --features bytemuck 24 | - version: stable 25 | features: --features arbitrary 26 | - version: stable 27 | features: --features malloc_size_of 28 | - version: nightly 29 | features: --features unstable 30 | - version: nightly 31 | features: --features unstable,serde 32 | 33 | steps: 34 | - uses: actions/checkout@v4 35 | 36 | - name: Install toolchain 37 | uses: dtolnay/rust-toolchain@v1 38 | with: 39 | toolchain: ${{ matrix.version }} 40 | 41 | - name: Cargo build 42 | run: cargo build ${{ matrix.features }} 43 | 44 | - name: Cargo test 45 | run: cargo test ${{ matrix.features }} 46 | env: 47 | RUST_BACKTRACE: 1 48 | 49 | fmt: 50 | name: Check code formatting 51 | runs-on: ubuntu-latest 52 | steps: 53 | # GitHub runners already have a usable version of cargo & rustfmt, so an install is not needed 54 | - uses: actions/checkout@v4 55 | - run: cargo fmt --check 56 | 57 | build_result: 58 | name: Result 59 | runs-on: ubuntu-latest 60 | needs: 61 | - "linux-ci" 62 | 63 | steps: 64 | - name: Mark the job as successful 65 | run: exit 0 66 | if: success() 67 | - name: Mark the job as unsuccessful 68 | run: exit 1 69 | if: "!success()" 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 or the MIT license 3 | , at your 4 | option. All files in the project carrying such notice may not be 5 | copied, modified, or distributed except according to those terms. 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "euclid" 3 | version = "0.22.12" 4 | authors = ["The Servo Project Developers"] 5 | edition = "2021" 6 | rust-version = "1.63.0" 7 | description = "Geometry primitives" 8 | documentation = "https://docs.rs/euclid/" 9 | repository = "https://github.com/servo/euclid" 10 | keywords = ["matrix", "vector", "linear-algebra", "geometry"] 11 | categories = ["science"] 12 | license = "MIT OR Apache-2.0" 13 | 14 | [features] 15 | default = ["std"] 16 | unstable = [] 17 | std = ["num-traits/std"] 18 | libm = ["num-traits/libm"] 19 | 20 | [dependencies] 21 | num-traits = { version = "0.2.15", default-features = false } 22 | serde = { version = "1.0", default-features = false, features = ["serde_derive"], optional = true } 23 | malloc_size_of = { version = "0.1", default-features = false, optional = true } 24 | mint = { version = "0.5.1", optional = true } 25 | arbitrary = { version = "1", optional = true } 26 | bytemuck = { version = "1.9", optional = true } 27 | 28 | [dev-dependencies] 29 | serde_test = "1.0" 30 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 Mozilla Foundation 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # euclid 2 | 3 | This is a small library for geometric types with a focus on 2d graphics and 4 | layout. 5 | 6 | * [Documentation](https://docs.rs/euclid/) 7 | * [Release notes](https://github.com/servo/euclid/releases) 8 | * [crates.io](https://crates.io/crates/euclid) 9 | -------------------------------------------------------------------------------- /src/angle.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | use crate::approxeq::ApproxEq; 11 | use crate::trig::Trig; 12 | 13 | use core::cmp::{Eq, PartialEq}; 14 | use core::hash::Hash; 15 | use core::iter::Sum; 16 | use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; 17 | 18 | #[cfg(feature = "bytemuck")] 19 | use bytemuck::{Pod, Zeroable}; 20 | #[cfg(feature = "malloc_size_of")] 21 | use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 22 | use num_traits::real::Real; 23 | use num_traits::{Float, FloatConst, NumCast, One, Zero}; 24 | #[cfg(feature = "serde")] 25 | use serde::{Deserialize, Serialize}; 26 | 27 | /// An angle in radians 28 | #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Hash)] 29 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 30 | pub struct Angle { 31 | pub radians: T, 32 | } 33 | 34 | #[cfg(feature = "bytemuck")] 35 | unsafe impl Zeroable for Angle {} 36 | 37 | #[cfg(feature = "bytemuck")] 38 | unsafe impl Pod for Angle {} 39 | 40 | #[cfg(feature = "arbitrary")] 41 | impl<'a, T> arbitrary::Arbitrary<'a> for Angle 42 | where 43 | T: arbitrary::Arbitrary<'a>, 44 | { 45 | // This implementation could be derived, but the derive would require an `extern crate std`. 46 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 47 | Ok(Angle { 48 | radians: arbitrary::Arbitrary::arbitrary(u)?, 49 | }) 50 | } 51 | 52 | fn size_hint(depth: usize) -> (usize, Option) { 53 | ::size_hint(depth) 54 | } 55 | } 56 | 57 | #[cfg(feature = "malloc_size_of")] 58 | impl MallocSizeOf for Angle { 59 | fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { 60 | self.radians.size_of(ops) 61 | } 62 | } 63 | 64 | impl Angle { 65 | #[inline] 66 | pub fn radians(radians: T) -> Self { 67 | Angle { radians } 68 | } 69 | 70 | #[inline] 71 | pub fn get(self) -> T { 72 | self.radians 73 | } 74 | } 75 | 76 | impl Angle 77 | where 78 | T: Trig, 79 | { 80 | #[inline] 81 | pub fn degrees(deg: T) -> Self { 82 | Angle { 83 | radians: T::degrees_to_radians(deg), 84 | } 85 | } 86 | 87 | #[inline] 88 | pub fn to_degrees(self) -> T { 89 | T::radians_to_degrees(self.radians) 90 | } 91 | } 92 | 93 | impl Angle 94 | where 95 | T: Rem + Sub + Add + Zero + FloatConst + PartialOrd + Copy, 96 | { 97 | /// Returns this angle in the [0..2*PI[ range. 98 | pub fn positive(&self) -> Self { 99 | let two_pi = T::PI() + T::PI(); 100 | let mut a = self.radians % two_pi; 101 | if a < T::zero() { 102 | a = a + two_pi; 103 | } 104 | Angle::radians(a) 105 | } 106 | 107 | /// Returns this angle in the ]-PI..PI] range. 108 | pub fn signed(&self) -> Self { 109 | Angle::pi() - (Angle::pi() - *self).positive() 110 | } 111 | } 112 | 113 | impl Angle 114 | where 115 | T: Rem 116 | + Mul 117 | + Sub 118 | + Add 119 | + One 120 | + FloatConst 121 | + Copy, 122 | { 123 | /// Returns the shortest signed angle between two angles. 124 | /// 125 | /// Takes wrapping and signs into account. 126 | pub fn angle_to(&self, to: Self) -> Self { 127 | let two = T::one() + T::one(); 128 | let max = T::PI() * two; 129 | let d = (to.radians - self.radians) % max; 130 | 131 | Angle::radians(two * d % max - d) 132 | } 133 | 134 | /// Linear interpolation between two angles, using the shortest path. 135 | pub fn lerp(&self, other: Self, t: T) -> Self { 136 | *self + self.angle_to(other) * t 137 | } 138 | } 139 | 140 | impl Angle 141 | where 142 | T: Float, 143 | { 144 | /// Returns `true` if the angle is a finite number. 145 | #[inline] 146 | pub fn is_finite(self) -> bool { 147 | self.radians.is_finite() 148 | } 149 | } 150 | 151 | impl Angle 152 | where 153 | T: Real, 154 | { 155 | /// Returns `(sin(self), cos(self))`. 156 | pub fn sin_cos(self) -> (T, T) { 157 | self.radians.sin_cos() 158 | } 159 | } 160 | 161 | impl Angle 162 | where 163 | T: Zero, 164 | { 165 | pub fn zero() -> Self { 166 | Angle::radians(T::zero()) 167 | } 168 | } 169 | 170 | impl Angle 171 | where 172 | T: FloatConst + Add, 173 | { 174 | pub fn pi() -> Self { 175 | Angle::radians(T::PI()) 176 | } 177 | 178 | pub fn two_pi() -> Self { 179 | Angle::radians(T::PI() + T::PI()) 180 | } 181 | 182 | pub fn frac_pi_2() -> Self { 183 | Angle::radians(T::FRAC_PI_2()) 184 | } 185 | 186 | pub fn frac_pi_3() -> Self { 187 | Angle::radians(T::FRAC_PI_3()) 188 | } 189 | 190 | pub fn frac_pi_4() -> Self { 191 | Angle::radians(T::FRAC_PI_4()) 192 | } 193 | } 194 | 195 | impl Angle 196 | where 197 | T: NumCast + Copy, 198 | { 199 | /// Cast from one numeric representation to another. 200 | #[inline] 201 | pub fn cast(&self) -> Angle { 202 | self.try_cast().unwrap() 203 | } 204 | 205 | /// Fallible cast from one numeric representation to another. 206 | pub fn try_cast(&self) -> Option> { 207 | NumCast::from(self.radians).map(|radians| Angle { radians }) 208 | } 209 | 210 | // Convenience functions for common casts. 211 | 212 | /// Cast angle to `f32`. 213 | #[inline] 214 | pub fn to_f32(&self) -> Angle { 215 | self.cast() 216 | } 217 | 218 | /// Cast angle `f64`. 219 | #[inline] 220 | pub fn to_f64(&self) -> Angle { 221 | self.cast() 222 | } 223 | } 224 | 225 | impl> Add for Angle { 226 | type Output = Self; 227 | fn add(self, other: Self) -> Self { 228 | Self::radians(self.radians + other.radians) 229 | } 230 | } 231 | 232 | impl> Add<&Self> for Angle { 233 | type Output = Self; 234 | fn add(self, other: &Self) -> Self { 235 | Self::radians(self.radians + other.radians) 236 | } 237 | } 238 | 239 | impl Sum for Angle { 240 | fn sum>(iter: I) -> Self { 241 | iter.fold(Self::zero(), Add::add) 242 | } 243 | } 244 | 245 | impl<'a, T: 'a + Add + Copy + Zero> Sum<&'a Self> for Angle { 246 | fn sum>(iter: I) -> Self { 247 | iter.fold(Self::zero(), Add::add) 248 | } 249 | } 250 | 251 | impl> AddAssign for Angle { 252 | fn add_assign(&mut self, other: Angle) { 253 | self.radians += other.radians; 254 | } 255 | } 256 | 257 | impl> Sub> for Angle { 258 | type Output = Angle; 259 | fn sub(self, other: Angle) -> ::Output { 260 | Angle::radians(self.radians - other.radians) 261 | } 262 | } 263 | 264 | impl> SubAssign for Angle { 265 | fn sub_assign(&mut self, other: Angle) { 266 | self.radians -= other.radians; 267 | } 268 | } 269 | 270 | impl> Div> for Angle { 271 | type Output = T; 272 | #[inline] 273 | fn div(self, other: Angle) -> T { 274 | self.radians / other.radians 275 | } 276 | } 277 | 278 | impl> Div for Angle { 279 | type Output = Angle; 280 | #[inline] 281 | fn div(self, factor: T) -> Angle { 282 | Angle::radians(self.radians / factor) 283 | } 284 | } 285 | 286 | impl> DivAssign for Angle { 287 | fn div_assign(&mut self, factor: T) { 288 | self.radians /= factor; 289 | } 290 | } 291 | 292 | impl> Mul for Angle { 293 | type Output = Angle; 294 | #[inline] 295 | fn mul(self, factor: T) -> Angle { 296 | Angle::radians(self.radians * factor) 297 | } 298 | } 299 | 300 | impl> MulAssign for Angle { 301 | fn mul_assign(&mut self, factor: T) { 302 | self.radians *= factor; 303 | } 304 | } 305 | 306 | impl> Neg for Angle { 307 | type Output = Self; 308 | fn neg(self) -> Self { 309 | Angle::radians(-self.radians) 310 | } 311 | } 312 | 313 | impl> ApproxEq for Angle { 314 | #[inline] 315 | fn approx_epsilon() -> T { 316 | T::approx_epsilon() 317 | } 318 | 319 | #[inline] 320 | fn approx_eq_eps(&self, other: &Angle, approx_epsilon: &T) -> bool { 321 | self.radians.approx_eq_eps(&other.radians, approx_epsilon) 322 | } 323 | } 324 | 325 | #[test] 326 | fn wrap_angles() { 327 | use core::f32::consts::{FRAC_PI_2, PI}; 328 | 329 | assert!(Angle::radians(0.0).positive().approx_eq(&Angle::zero())); 330 | assert!(Angle::radians(FRAC_PI_2) 331 | .positive() 332 | .approx_eq(&Angle::frac_pi_2())); 333 | assert!(Angle::radians(-FRAC_PI_2) 334 | .positive() 335 | .approx_eq(&Angle::radians(3.0 * FRAC_PI_2))); 336 | assert!(Angle::radians(3.0 * FRAC_PI_2) 337 | .positive() 338 | .approx_eq(&Angle::radians(3.0 * FRAC_PI_2))); 339 | assert!(Angle::radians(5.0 * FRAC_PI_2) 340 | .positive() 341 | .approx_eq(&Angle::frac_pi_2())); 342 | assert!(Angle::radians(2.0 * PI) 343 | .positive() 344 | .approx_eq(&Angle::zero())); 345 | assert!(Angle::radians(-2.0 * PI) 346 | .positive() 347 | .approx_eq(&Angle::zero())); 348 | assert!(Angle::radians(PI).positive().approx_eq(&Angle::pi())); 349 | assert!(Angle::radians(-PI).positive().approx_eq(&Angle::pi())); 350 | 351 | assert!(Angle::radians(FRAC_PI_2) 352 | .signed() 353 | .approx_eq(&Angle::frac_pi_2())); 354 | assert!(Angle::radians(3.0 * FRAC_PI_2) 355 | .signed() 356 | .approx_eq(&-Angle::frac_pi_2())); 357 | assert!(Angle::radians(5.0 * FRAC_PI_2) 358 | .signed() 359 | .approx_eq(&Angle::frac_pi_2())); 360 | assert!(Angle::radians(2.0 * PI).signed().approx_eq(&Angle::zero())); 361 | assert!(Angle::radians(-2.0 * PI).signed().approx_eq(&Angle::zero())); 362 | assert!(Angle::radians(-PI).signed().approx_eq(&Angle::pi())); 363 | assert!(Angle::radians(PI).signed().approx_eq(&Angle::pi())); 364 | } 365 | 366 | #[test] 367 | fn lerp() { 368 | type A = Angle; 369 | 370 | let a = A::radians(1.0); 371 | let b = A::radians(2.0); 372 | assert!(a.lerp(b, 0.25).approx_eq(&Angle::radians(1.25))); 373 | assert!(a.lerp(b, 0.5).approx_eq(&Angle::radians(1.5))); 374 | assert!(a.lerp(b, 0.75).approx_eq(&Angle::radians(1.75))); 375 | assert!(a 376 | .lerp(b + A::two_pi(), 0.75) 377 | .approx_eq(&Angle::radians(1.75))); 378 | assert!(a 379 | .lerp(b - A::two_pi(), 0.75) 380 | .approx_eq(&Angle::radians(1.75))); 381 | assert!(a 382 | .lerp(b + A::two_pi() * 5.0, 0.75) 383 | .approx_eq(&Angle::radians(1.75))); 384 | } 385 | 386 | #[test] 387 | fn sum() { 388 | type A = Angle; 389 | let angles = [A::radians(1.0), A::radians(2.0), A::radians(3.0)]; 390 | let sum = A::radians(6.0); 391 | assert_eq!(angles.iter().sum::(), sum); 392 | } 393 | -------------------------------------------------------------------------------- /src/approxeq.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | /// Trait for testing approximate equality 11 | pub trait ApproxEq { 12 | /// Default epsilon value 13 | fn approx_epsilon() -> Eps; 14 | 15 | /// Returns `true` if this object is approximately equal to the other one, using 16 | /// a provided epsilon value. 17 | fn approx_eq_eps(&self, other: &Self, approx_epsilon: &Eps) -> bool; 18 | 19 | /// Returns `true` if this object is approximately equal to the other one, using 20 | /// the [`approx_epsilon`](ApproxEq::approx_epsilon) epsilon value. 21 | fn approx_eq(&self, other: &Self) -> bool { 22 | self.approx_eq_eps(other, &Self::approx_epsilon()) 23 | } 24 | } 25 | 26 | macro_rules! approx_eq { 27 | ($ty:ty, $eps:expr) => { 28 | impl ApproxEq<$ty> for $ty { 29 | #[inline] 30 | fn approx_epsilon() -> $ty { 31 | $eps 32 | } 33 | #[inline] 34 | fn approx_eq_eps(&self, other: &$ty, approx_epsilon: &$ty) -> bool { 35 | num_traits::Float::abs(*self - *other) < *approx_epsilon 36 | } 37 | } 38 | }; 39 | } 40 | 41 | approx_eq!(f32, 1.0e-6); 42 | approx_eq!(f64, 1.0e-6); 43 | -------------------------------------------------------------------------------- /src/approxord.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | //! Utilities for testing approximate ordering - especially true for 11 | //! floating point types, where NaN's cannot be ordered. 12 | 13 | pub fn min(x: T, y: T) -> T { 14 | if x <= y { 15 | x 16 | } else { 17 | y 18 | } 19 | } 20 | 21 | pub fn max(x: T, y: T) -> T { 22 | if x >= y { 23 | x 24 | } else { 25 | y 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use super::*; 32 | 33 | #[test] 34 | fn test_min() { 35 | assert!(min(0u32, 1u32) == 0u32); 36 | assert!(min(-1.0f32, 0.0f32) == -1.0f32); 37 | } 38 | 39 | #[test] 40 | fn test_max() { 41 | assert!(max(0u32, 1u32) == 1u32); 42 | assert!(max(-1.0f32, 0.0f32) == 0.0f32); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/box2d.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | use super::UnknownUnit; 11 | use crate::approxord::{max, min}; 12 | use crate::num::*; 13 | use crate::point::{point2, Point2D}; 14 | use crate::rect::Rect; 15 | use crate::scale::Scale; 16 | use crate::side_offsets::SideOffsets2D; 17 | use crate::size::Size2D; 18 | use crate::vector::{vec2, Vector2D}; 19 | 20 | #[cfg(feature = "bytemuck")] 21 | use bytemuck::{Pod, Zeroable}; 22 | #[cfg(feature = "malloc_size_of")] 23 | use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 24 | use num_traits::{Float, NumCast}; 25 | #[cfg(feature = "serde")] 26 | use serde::{Deserialize, Serialize}; 27 | 28 | use core::borrow::Borrow; 29 | use core::cmp::PartialOrd; 30 | use core::fmt; 31 | use core::hash::{Hash, Hasher}; 32 | use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Range, Sub}; 33 | 34 | /// A 2d axis aligned rectangle represented by its minimum and maximum coordinates. 35 | /// 36 | /// # Representation 37 | /// 38 | /// This struct is similar to [`Rect`], but stores rectangle as two endpoints 39 | /// instead of origin point and size. Such representation has several advantages over 40 | /// [`Rect`] representation: 41 | /// - Several operations are more efficient with `Box2D`, including [`intersection`], 42 | /// [`union`], and point-in-rect. 43 | /// - The representation is less susceptible to overflow. With [`Rect`], computation 44 | /// of second point can overflow for a large range of values of origin and size. 45 | /// However, with `Box2D`, computation of [`size`] cannot overflow if the coordinates 46 | /// are signed and the resulting size is unsigned. 47 | /// 48 | /// A known disadvantage of `Box2D` is that translating the rectangle requires translating 49 | /// both points, whereas translating [`Rect`] only requires translating one point. 50 | /// 51 | /// # Empty box 52 | /// 53 | /// A box is considered empty (see [`is_empty`]) if any of the following is true: 54 | /// - it's area is empty, 55 | /// - it's area is negative (`min.x > max.x` or `min.y > max.y`), 56 | /// - it contains NaNs. 57 | /// 58 | /// [`intersection`]: Self::intersection 59 | /// [`is_empty`]: Self::is_empty 60 | /// [`union`]: Self::union 61 | /// [`size`]: Self::size 62 | #[repr(C)] 63 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 64 | #[cfg_attr( 65 | feature = "serde", 66 | serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")) 67 | )] 68 | pub struct Box2D { 69 | pub min: Point2D, 70 | pub max: Point2D, 71 | } 72 | 73 | impl Hash for Box2D { 74 | fn hash(&self, h: &mut H) { 75 | self.min.hash(h); 76 | self.max.hash(h); 77 | } 78 | } 79 | 80 | impl Copy for Box2D {} 81 | 82 | impl Clone for Box2D { 83 | fn clone(&self) -> Self { 84 | Self::new(self.min.clone(), self.max.clone()) 85 | } 86 | } 87 | 88 | impl PartialEq for Box2D { 89 | fn eq(&self, other: &Self) -> bool { 90 | self.min.eq(&other.min) && self.max.eq(&other.max) 91 | } 92 | } 93 | 94 | impl Eq for Box2D {} 95 | 96 | impl fmt::Debug for Box2D { 97 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 98 | f.debug_tuple("Box2D") 99 | .field(&self.min) 100 | .field(&self.max) 101 | .finish() 102 | } 103 | } 104 | 105 | #[cfg(feature = "arbitrary")] 106 | impl<'a, T, U> arbitrary::Arbitrary<'a> for Box2D 107 | where 108 | T: arbitrary::Arbitrary<'a>, 109 | { 110 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 111 | Ok(Box2D::new( 112 | arbitrary::Arbitrary::arbitrary(u)?, 113 | arbitrary::Arbitrary::arbitrary(u)?, 114 | )) 115 | } 116 | } 117 | 118 | #[cfg(feature = "bytemuck")] 119 | unsafe impl Zeroable for Box2D {} 120 | 121 | #[cfg(feature = "bytemuck")] 122 | unsafe impl Pod for Box2D {} 123 | 124 | impl Box2D { 125 | /// Constructor. 126 | #[inline] 127 | pub const fn new(min: Point2D, max: Point2D) -> Self { 128 | Box2D { min, max } 129 | } 130 | 131 | /// Constructor. 132 | #[inline] 133 | pub fn from_origin_and_size(origin: Point2D, size: Size2D) -> Self 134 | where 135 | T: Copy + Add, 136 | { 137 | Box2D { 138 | min: origin, 139 | max: point2(origin.x + size.width, origin.y + size.height), 140 | } 141 | } 142 | 143 | /// Creates a `Box2D` of the given size, at offset zero. 144 | #[inline] 145 | pub fn from_size(size: Size2D) -> Self 146 | where 147 | T: Zero, 148 | { 149 | Box2D { 150 | min: Point2D::zero(), 151 | max: point2(size.width, size.height), 152 | } 153 | } 154 | } 155 | 156 | #[cfg(feature = "malloc_size_of")] 157 | impl MallocSizeOf for Box2D { 158 | fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { 159 | self.min.size_of(ops) + self.max.size_of(ops) 160 | } 161 | } 162 | 163 | impl Box2D 164 | where 165 | T: PartialOrd, 166 | { 167 | /// Returns `true` if the box has a negative area. 168 | /// 169 | /// The common interpretation for a negative box is to consider it empty. It can be obtained 170 | /// by calculating the intersection of two boxes that do not intersect. 171 | #[inline] 172 | pub fn is_negative(&self) -> bool { 173 | self.max.x < self.min.x || self.max.y < self.min.y 174 | } 175 | 176 | /// Returns `true` if the size is zero, negative or NaN. 177 | #[inline] 178 | pub fn is_empty(&self) -> bool { 179 | !(self.max.x > self.min.x && self.max.y > self.min.y) 180 | } 181 | 182 | /// Returns `true` if the two boxes intersect. 183 | #[inline] 184 | pub fn intersects(&self, other: &Self) -> bool { 185 | // Use bitwise and instead of && to avoid emitting branches. 186 | (self.min.x < other.max.x) 187 | & (self.max.x > other.min.x) 188 | & (self.min.y < other.max.y) 189 | & (self.max.y > other.min.y) 190 | } 191 | 192 | /// Returns `true` if this [`Box2D`] contains the point `p`. 193 | /// 194 | /// Points on the top and left edges are inside the box, whereas 195 | /// points on the bottom and right edges are outside the box. 196 | /// See [`Box2D::contains_inclusive`] for a variant that also includes those 197 | /// latter points. 198 | /// 199 | /// # Examples 200 | /// 201 | /// ``` 202 | /// use euclid::default::{Box2D, Point2D}; 203 | /// 204 | /// let rect = Box2D::new(Point2D::origin(), Point2D::new(2, 2)); 205 | /// 206 | /// assert!(rect.contains(Point2D::new(1, 1))); 207 | /// 208 | /// assert!(rect.contains(Point2D::new(0, 1))); // left edge 209 | /// assert!(rect.contains(Point2D::new(1, 0))); // top edge 210 | /// assert!(rect.contains(Point2D::origin())); 211 | /// 212 | /// assert!(!rect.contains(Point2D::new(2, 1))); // right edge 213 | /// assert!(!rect.contains(Point2D::new(1, 2))); // bottom edge 214 | /// assert!(!rect.contains(Point2D::new(2, 2))); 215 | /// ``` 216 | #[inline] 217 | pub fn contains(&self, p: Point2D) -> bool { 218 | // Use bitwise and instead of && to avoid emitting branches. 219 | (self.min.x <= p.x) & (p.x < self.max.x) & (self.min.y <= p.y) & (p.y < self.max.y) 220 | } 221 | 222 | /// Returns `true` if this box contains the point `p`. 223 | /// 224 | /// This is like [`Box2D::contains`], but points on the bottom and right 225 | /// edges are also inside the box. 226 | /// 227 | /// # Examples 228 | /// ``` 229 | /// use euclid::default::{Box2D, Point2D}; 230 | /// 231 | /// let rect = Box2D::new(Point2D::origin(), Point2D::new(2, 2)); 232 | /// 233 | /// assert!(rect.contains_inclusive(Point2D::new(1, 1))); 234 | /// 235 | /// assert!(rect.contains_inclusive(Point2D::new(0, 1))); // left edge 236 | /// assert!(rect.contains_inclusive(Point2D::new(1, 0))); // top edge 237 | /// assert!(rect.contains_inclusive(Point2D::origin())); 238 | /// 239 | /// assert!(rect.contains_inclusive(Point2D::new(2, 1))); // right edge 240 | /// assert!(rect.contains_inclusive(Point2D::new(1, 2))); // bottom edge 241 | /// assert!(rect.contains_inclusive(Point2D::new(2, 2))); 242 | /// ``` 243 | #[inline] 244 | pub fn contains_inclusive(&self, p: Point2D) -> bool { 245 | // Use bitwise and instead of && to avoid emitting branches. 246 | (self.min.x <= p.x) & (p.x <= self.max.x) & (self.min.y <= p.y) & (p.y <= self.max.y) 247 | } 248 | 249 | /// Returns `true` if this box contains the interior of the other box. Always 250 | /// returns `true` if other is empty, and always returns `false` if other is 251 | /// nonempty but this box is empty. 252 | #[inline] 253 | pub fn contains_box(&self, other: &Self) -> bool { 254 | other.is_empty() 255 | || ((self.min.x <= other.min.x) 256 | & (other.max.x <= self.max.x) 257 | & (self.min.y <= other.min.y) 258 | & (other.max.y <= self.max.y)) 259 | } 260 | } 261 | 262 | impl Box2D 263 | where 264 | T: Copy + PartialOrd, 265 | { 266 | #[inline] 267 | pub fn to_non_empty(&self) -> Option { 268 | if self.is_empty() { 269 | return None; 270 | } 271 | 272 | Some(*self) 273 | } 274 | 275 | /// Computes the intersection of two boxes, returning `None` if the boxes do not intersect. 276 | #[inline] 277 | pub fn intersection(&self, other: &Self) -> Option { 278 | let b = self.intersection_unchecked(other); 279 | 280 | if b.is_empty() { 281 | return None; 282 | } 283 | 284 | Some(b) 285 | } 286 | 287 | /// Computes the intersection of two boxes without check whether they do intersect. 288 | /// 289 | /// The result is a negative box if the boxes do not intersect. 290 | /// This can be useful for computing the intersection of more than two boxes, as 291 | /// it is possible to chain multiple `intersection_unchecked` calls and check for 292 | /// empty/negative result at the end. 293 | #[inline] 294 | pub fn intersection_unchecked(&self, other: &Self) -> Self { 295 | Box2D { 296 | min: point2(max(self.min.x, other.min.x), max(self.min.y, other.min.y)), 297 | max: point2(min(self.max.x, other.max.x), min(self.max.y, other.max.y)), 298 | } 299 | } 300 | 301 | /// Computes the union of two boxes. 302 | /// 303 | /// If either of the boxes is empty, the other one is returned. 304 | #[inline] 305 | pub fn union(&self, other: &Self) -> Self { 306 | if other.is_empty() { 307 | return *self; 308 | } 309 | if self.is_empty() { 310 | return *other; 311 | } 312 | 313 | Box2D { 314 | min: point2(min(self.min.x, other.min.x), min(self.min.y, other.min.y)), 315 | max: point2(max(self.max.x, other.max.x), max(self.max.y, other.max.y)), 316 | } 317 | } 318 | } 319 | 320 | impl Box2D 321 | where 322 | T: Copy + Add, 323 | { 324 | /// Returns the same box, translated by a vector. 325 | #[inline] 326 | pub fn translate(&self, by: Vector2D) -> Self { 327 | Box2D { 328 | min: self.min + by, 329 | max: self.max + by, 330 | } 331 | } 332 | } 333 | 334 | impl Box2D 335 | where 336 | T: Copy + Sub, 337 | { 338 | #[inline] 339 | pub fn size(&self) -> Size2D { 340 | (self.max - self.min).to_size() 341 | } 342 | 343 | /// Change the size of the box by adjusting the max endpoint 344 | /// without modifying the min endpoint. 345 | #[inline] 346 | pub fn set_size(&mut self, size: Size2D) { 347 | let diff = (self.size() - size).to_vector(); 348 | self.max -= diff; 349 | } 350 | 351 | #[inline] 352 | pub fn width(&self) -> T { 353 | self.max.x - self.min.x 354 | } 355 | 356 | #[inline] 357 | pub fn height(&self) -> T { 358 | self.max.y - self.min.y 359 | } 360 | 361 | #[inline] 362 | pub fn to_rect(&self) -> Rect { 363 | Rect { 364 | origin: self.min, 365 | size: self.size(), 366 | } 367 | } 368 | } 369 | 370 | impl Box2D 371 | where 372 | T: Copy + Add + Sub, 373 | { 374 | /// Inflates the box by the specified sizes on each side respectively. 375 | #[inline] 376 | #[must_use] 377 | pub fn inflate(&self, width: T, height: T) -> Self { 378 | Box2D { 379 | min: point2(self.min.x - width, self.min.y - height), 380 | max: point2(self.max.x + width, self.max.y + height), 381 | } 382 | } 383 | 384 | /// Calculate the size and position of an inner box. 385 | /// 386 | /// Subtracts the side offsets from all sides. The horizontal, vertical 387 | /// and applicate offsets must not be larger than the original side length. 388 | pub fn inner_box(&self, offsets: SideOffsets2D) -> Self { 389 | Box2D { 390 | min: self.min + vec2(offsets.left, offsets.top), 391 | max: self.max - vec2(offsets.right, offsets.bottom), 392 | } 393 | } 394 | 395 | /// Calculate the b and position of an outer box. 396 | /// 397 | /// Add the offsets to all sides. The expanded box is returned. 398 | pub fn outer_box(&self, offsets: SideOffsets2D) -> Self { 399 | Box2D { 400 | min: self.min - vec2(offsets.left, offsets.top), 401 | max: self.max + vec2(offsets.right, offsets.bottom), 402 | } 403 | } 404 | } 405 | 406 | impl Box2D 407 | where 408 | T: Copy + Zero + PartialOrd, 409 | { 410 | /// Returns the smallest box enclosing all of the provided points. 411 | /// 412 | /// The top/bottom/left/right-most points are exactly on the box's edges. 413 | /// Since [`Box2D::contains`] excludes points that are on the right-most and 414 | /// bottom-most edges, not all points passed to [`Box2D::from_points`] are 415 | /// contained in the returned [`Box2D`] when probed with [`Box2D::contains`], but 416 | /// are when probed with [`Box2D::contains_inclusive`]. 417 | /// 418 | /// For example: 419 | /// 420 | /// ``` 421 | /// use euclid::default::{Point2D, Box2D}; 422 | /// 423 | /// let a = Point2D::origin(); 424 | /// let b = Point2D::new(1, 2); 425 | /// let rect = Box2D::from_points([a, b]); 426 | /// 427 | /// assert_eq!(rect.width(), 1); 428 | /// assert_eq!(rect.height(), 2); 429 | /// 430 | /// assert!(rect.contains(a)); 431 | /// assert!(!rect.contains(b)); 432 | /// assert!(rect.contains_inclusive(b)); 433 | /// ``` 434 | /// 435 | /// In particular, calling [`Box2D::from_points`] with a single point 436 | /// results in an empty [`Box2D`]: 437 | /// 438 | /// ``` 439 | /// use euclid::default::{Point2D, Box2D}; 440 | /// 441 | /// let a = Point2D::new(1, 0); 442 | /// let rect = Box2D::from_points([a]); 443 | /// 444 | /// assert!(rect.is_empty()); 445 | /// assert!(!rect.contains(a)); 446 | /// assert!(rect.contains_inclusive(a)); 447 | /// ``` 448 | /// 449 | /// The [`Box2D`] enclosing no points is also empty: 450 | /// 451 | /// ``` 452 | /// use euclid::default::{Box2D, Point2D}; 453 | /// 454 | /// let rect = Box2D::from_points(std::iter::empty::>()); 455 | /// assert!(rect.is_empty()); 456 | /// ``` 457 | pub fn from_points(points: I) -> Self 458 | where 459 | I: IntoIterator, 460 | I::Item: Borrow>, 461 | { 462 | let mut points = points.into_iter(); 463 | 464 | let (mut min_x, mut min_y) = match points.next() { 465 | Some(first) => first.borrow().to_tuple(), 466 | None => return Box2D::zero(), 467 | }; 468 | 469 | let (mut max_x, mut max_y) = (min_x, min_y); 470 | for point in points { 471 | let p = point.borrow(); 472 | if p.x < min_x { 473 | min_x = p.x; 474 | } 475 | if p.x > max_x { 476 | max_x = p.x; 477 | } 478 | if p.y < min_y { 479 | min_y = p.y; 480 | } 481 | if p.y > max_y { 482 | max_y = p.y; 483 | } 484 | } 485 | 486 | Box2D { 487 | min: point2(min_x, min_y), 488 | max: point2(max_x, max_y), 489 | } 490 | } 491 | } 492 | 493 | impl Box2D 494 | where 495 | T: Copy + One + Add + Sub + Mul, 496 | { 497 | /// Linearly interpolate between this box and another box. 498 | #[inline] 499 | pub fn lerp(&self, other: Self, t: T) -> Self { 500 | Self::new(self.min.lerp(other.min, t), self.max.lerp(other.max, t)) 501 | } 502 | } 503 | 504 | impl Box2D 505 | where 506 | T: Copy + One + Add + Div, 507 | { 508 | pub fn center(&self) -> Point2D { 509 | let two = T::one() + T::one(); 510 | (self.min + self.max.to_vector()) / two 511 | } 512 | } 513 | 514 | impl Box2D 515 | where 516 | T: Copy + Mul + Sub, 517 | { 518 | #[inline] 519 | pub fn area(&self) -> T { 520 | let size = self.size(); 521 | size.width * size.height 522 | } 523 | } 524 | 525 | impl Box2D 526 | where 527 | T: Zero, 528 | { 529 | /// Constructor, setting all sides to zero. 530 | pub fn zero() -> Self { 531 | Box2D::new(Point2D::zero(), Point2D::zero()) 532 | } 533 | } 534 | 535 | impl Mul for Box2D { 536 | type Output = Box2D; 537 | 538 | #[inline] 539 | fn mul(self, scale: T) -> Self::Output { 540 | Box2D::new(self.min * scale, self.max * scale) 541 | } 542 | } 543 | 544 | impl MulAssign for Box2D { 545 | #[inline] 546 | fn mul_assign(&mut self, scale: T) { 547 | *self *= Scale::new(scale); 548 | } 549 | } 550 | 551 | impl Div for Box2D { 552 | type Output = Box2D; 553 | 554 | #[inline] 555 | fn div(self, scale: T) -> Self::Output { 556 | Box2D::new(self.min / scale, self.max / scale) 557 | } 558 | } 559 | 560 | impl DivAssign for Box2D { 561 | #[inline] 562 | fn div_assign(&mut self, scale: T) { 563 | *self /= Scale::new(scale); 564 | } 565 | } 566 | 567 | impl Mul> for Box2D { 568 | type Output = Box2D; 569 | 570 | #[inline] 571 | fn mul(self, scale: Scale) -> Self::Output { 572 | Box2D::new(self.min * scale, self.max * scale) 573 | } 574 | } 575 | 576 | impl MulAssign> for Box2D { 577 | #[inline] 578 | fn mul_assign(&mut self, scale: Scale) { 579 | self.min *= scale; 580 | self.max *= scale; 581 | } 582 | } 583 | 584 | impl Div> for Box2D { 585 | type Output = Box2D; 586 | 587 | #[inline] 588 | fn div(self, scale: Scale) -> Self::Output { 589 | Box2D::new(self.min / scale, self.max / scale) 590 | } 591 | } 592 | 593 | impl DivAssign> for Box2D { 594 | #[inline] 595 | fn div_assign(&mut self, scale: Scale) { 596 | self.min /= scale; 597 | self.max /= scale; 598 | } 599 | } 600 | 601 | impl Box2D 602 | where 603 | T: Copy, 604 | { 605 | #[inline] 606 | pub fn x_range(&self) -> Range { 607 | self.min.x..self.max.x 608 | } 609 | 610 | #[inline] 611 | pub fn y_range(&self) -> Range { 612 | self.min.y..self.max.y 613 | } 614 | 615 | /// Drop the units, preserving only the numeric value. 616 | #[inline] 617 | pub fn to_untyped(&self) -> Box2D { 618 | Box2D::new(self.min.to_untyped(), self.max.to_untyped()) 619 | } 620 | 621 | /// Tag a unitless value with units. 622 | #[inline] 623 | pub fn from_untyped(c: &Box2D) -> Box2D { 624 | Box2D::new(Point2D::from_untyped(c.min), Point2D::from_untyped(c.max)) 625 | } 626 | 627 | /// Cast the unit 628 | #[inline] 629 | pub fn cast_unit(&self) -> Box2D { 630 | Box2D::new(self.min.cast_unit(), self.max.cast_unit()) 631 | } 632 | 633 | #[inline] 634 | pub fn scale(&self, x: S, y: S) -> Self 635 | where 636 | T: Mul, 637 | { 638 | Box2D { 639 | min: point2(self.min.x * x, self.min.y * y), 640 | max: point2(self.max.x * x, self.max.y * y), 641 | } 642 | } 643 | } 644 | 645 | impl Box2D { 646 | /// Cast from one numeric representation to another, preserving the units. 647 | /// 648 | /// When casting from floating point to integer coordinates, the decimals are truncated 649 | /// as one would expect from a simple cast, but this behavior does not always make sense 650 | /// geometrically. Consider using [`round`], [`round_in`] or [`round_out`] before casting. 651 | /// 652 | /// [`round`]: Self::round 653 | /// [`round_in`]: Self::round_in 654 | /// [`round_out`]: Self::round_out 655 | #[inline] 656 | pub fn cast(&self) -> Box2D { 657 | Box2D::new(self.min.cast(), self.max.cast()) 658 | } 659 | 660 | /// Fallible cast from one numeric representation to another, preserving the units. 661 | /// 662 | /// When casting from floating point to integer coordinates, the decimals are truncated 663 | /// as one would expect from a simple cast, but this behavior does not always make sense 664 | /// geometrically. Consider using [`round`], [`round_in`] or [`round_out`] before casting. 665 | /// 666 | /// [`round`]: Self::round 667 | /// [`round_in`]: Self::round_in 668 | /// [`round_out`]: Self::round_out 669 | pub fn try_cast(&self) -> Option> { 670 | match (self.min.try_cast(), self.max.try_cast()) { 671 | (Some(a), Some(b)) => Some(Box2D::new(a, b)), 672 | _ => None, 673 | } 674 | } 675 | 676 | // Convenience functions for common casts 677 | 678 | /// Cast into an `f32` box. 679 | #[inline] 680 | pub fn to_f32(&self) -> Box2D { 681 | self.cast() 682 | } 683 | 684 | /// Cast into an `f64` box. 685 | #[inline] 686 | pub fn to_f64(&self) -> Box2D { 687 | self.cast() 688 | } 689 | 690 | /// Cast into an `usize` box, truncating decimals if any. 691 | /// 692 | /// When casting from floating point boxes, it is worth considering whether 693 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to 694 | /// obtain the desired conversion behavior. 695 | #[inline] 696 | pub fn to_usize(&self) -> Box2D { 697 | self.cast() 698 | } 699 | 700 | /// Cast into an `u32` box, truncating decimals if any. 701 | /// 702 | /// When casting from floating point boxes, it is worth considering whether 703 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to 704 | /// obtain the desired conversion behavior. 705 | #[inline] 706 | pub fn to_u32(&self) -> Box2D { 707 | self.cast() 708 | } 709 | 710 | /// Cast into an `i32` box, truncating decimals if any. 711 | /// 712 | /// When casting from floating point boxes, it is worth considering whether 713 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to 714 | /// obtain the desired conversion behavior. 715 | #[inline] 716 | pub fn to_i32(&self) -> Box2D { 717 | self.cast() 718 | } 719 | 720 | /// Cast into an `i64` box, truncating decimals if any. 721 | /// 722 | /// When casting from floating point boxes, it is worth considering whether 723 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to 724 | /// obtain the desired conversion behavior. 725 | #[inline] 726 | pub fn to_i64(&self) -> Box2D { 727 | self.cast() 728 | } 729 | } 730 | 731 | impl Box2D { 732 | /// Returns `true` if all members are finite. 733 | #[inline] 734 | pub fn is_finite(self) -> bool { 735 | self.min.is_finite() && self.max.is_finite() 736 | } 737 | } 738 | 739 | impl Box2D 740 | where 741 | T: Round, 742 | { 743 | /// Return a box with edges rounded to integer coordinates, such that 744 | /// the returned box has the same set of pixel centers as the original 745 | /// one. 746 | /// Values equal to 0.5 round up. 747 | /// Suitable for most places where integral device coordinates 748 | /// are needed, but note that any translation should be applied first to 749 | /// avoid pixel rounding errors. 750 | /// Note that this is *not* rounding to nearest integer if the values are negative. 751 | /// They are always rounding as floor(n + 0.5). 752 | #[must_use] 753 | pub fn round(&self) -> Self { 754 | Box2D::new(self.min.round(), self.max.round()) 755 | } 756 | } 757 | 758 | impl Box2D 759 | where 760 | T: Floor + Ceil, 761 | { 762 | /// Return a box with faces/edges rounded to integer coordinates, such that 763 | /// the original box contains the resulting box. 764 | #[must_use] 765 | pub fn round_in(&self) -> Self { 766 | let min = self.min.ceil(); 767 | let max = self.max.floor(); 768 | Box2D { min, max } 769 | } 770 | 771 | /// Return a box with faces/edges rounded to integer coordinates, such that 772 | /// the original box is contained in the resulting box. 773 | #[must_use] 774 | pub fn round_out(&self) -> Self { 775 | let min = self.min.floor(); 776 | let max = self.max.ceil(); 777 | Box2D { min, max } 778 | } 779 | } 780 | 781 | impl From> for Box2D 782 | where 783 | T: Copy + Zero + PartialOrd, 784 | { 785 | fn from(b: Size2D) -> Self { 786 | Self::from_size(b) 787 | } 788 | } 789 | 790 | impl Default for Box2D { 791 | fn default() -> Self { 792 | Box2D { 793 | min: Default::default(), 794 | max: Default::default(), 795 | } 796 | } 797 | } 798 | 799 | #[cfg(test)] 800 | mod tests { 801 | use crate::default::Box2D; 802 | use crate::side_offsets::SideOffsets2D; 803 | use crate::{point2, size2, vec2, Point2D}; 804 | //use super::*; 805 | 806 | #[test] 807 | fn test_size() { 808 | let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); 809 | assert_eq!(b.size().width, 20.0); 810 | assert_eq!(b.size().height, 20.0); 811 | } 812 | 813 | #[test] 814 | fn test_width_height() { 815 | let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); 816 | assert!(b.width() == 20.0); 817 | assert!(b.height() == 20.0); 818 | } 819 | 820 | #[test] 821 | fn test_center() { 822 | let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); 823 | assert_eq!(b.center(), Point2D::zero()); 824 | } 825 | 826 | #[test] 827 | fn test_area() { 828 | let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0)); 829 | assert_eq!(b.area(), 400.0); 830 | } 831 | 832 | #[test] 833 | fn test_from_points() { 834 | let b = Box2D::from_points(&[point2(50.0, 160.0), point2(100.0, 25.0)]); 835 | assert_eq!(b.min, point2(50.0, 25.0)); 836 | assert_eq!(b.max, point2(100.0, 160.0)); 837 | } 838 | 839 | #[test] 840 | fn test_round_in() { 841 | let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round_in(); 842 | assert_eq!(b.min.x, -25.0); 843 | assert_eq!(b.min.y, -40.0); 844 | assert_eq!(b.max.x, 60.0); 845 | assert_eq!(b.max.y, 36.0); 846 | } 847 | 848 | #[test] 849 | fn test_round_out() { 850 | let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round_out(); 851 | assert_eq!(b.min.x, -26.0); 852 | assert_eq!(b.min.y, -41.0); 853 | assert_eq!(b.max.x, 61.0); 854 | assert_eq!(b.max.y, 37.0); 855 | } 856 | 857 | #[test] 858 | fn test_round() { 859 | let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round(); 860 | assert_eq!(b.min.x, -25.0); 861 | assert_eq!(b.min.y, -40.0); 862 | assert_eq!(b.max.x, 60.0); 863 | assert_eq!(b.max.y, 37.0); 864 | } 865 | 866 | #[test] 867 | fn test_from_size() { 868 | let b = Box2D::from_size(size2(30.0, 40.0)); 869 | assert!(b.min == Point2D::zero()); 870 | assert!(b.size().width == 30.0); 871 | assert!(b.size().height == 40.0); 872 | } 873 | 874 | #[test] 875 | fn test_inner_box() { 876 | let b = Box2D::from_points(&[point2(50.0, 25.0), point2(100.0, 160.0)]); 877 | let b = b.inner_box(SideOffsets2D::new(10.0, 20.0, 5.0, 10.0)); 878 | assert_eq!(b.max.x, 80.0); 879 | assert_eq!(b.max.y, 155.0); 880 | assert_eq!(b.min.x, 60.0); 881 | assert_eq!(b.min.y, 35.0); 882 | } 883 | 884 | #[test] 885 | fn test_outer_box() { 886 | let b = Box2D::from_points(&[point2(50.0, 25.0), point2(100.0, 160.0)]); 887 | let b = b.outer_box(SideOffsets2D::new(10.0, 20.0, 5.0, 10.0)); 888 | assert_eq!(b.max.x, 120.0); 889 | assert_eq!(b.max.y, 165.0); 890 | assert_eq!(b.min.x, 40.0); 891 | assert_eq!(b.min.y, 15.0); 892 | } 893 | 894 | #[test] 895 | fn test_translate() { 896 | let size = size2(15.0, 15.0); 897 | let mut center = (size / 2.0).to_vector().to_point(); 898 | let b = Box2D::from_size(size); 899 | assert_eq!(b.center(), center); 900 | let translation = vec2(10.0, 2.5); 901 | let b = b.translate(translation); 902 | center += translation; 903 | assert_eq!(b.center(), center); 904 | assert_eq!(b.max.x, 25.0); 905 | assert_eq!(b.max.y, 17.5); 906 | assert_eq!(b.min.x, 10.0); 907 | assert_eq!(b.min.y, 2.5); 908 | } 909 | 910 | #[test] 911 | fn test_union() { 912 | let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(0.0, 20.0)]); 913 | let b2 = Box2D::from_points(&[point2(0.0, 20.0), point2(20.0, -20.0)]); 914 | let b = b1.union(&b2); 915 | assert_eq!(b.max.x, 20.0); 916 | assert_eq!(b.max.y, 20.0); 917 | assert_eq!(b.min.x, -20.0); 918 | assert_eq!(b.min.y, -20.0); 919 | } 920 | 921 | #[test] 922 | fn test_intersects() { 923 | let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]); 924 | let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]); 925 | assert!(b1.intersects(&b2)); 926 | } 927 | 928 | #[test] 929 | fn test_intersection_unchecked() { 930 | let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]); 931 | let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]); 932 | let b = b1.intersection_unchecked(&b2); 933 | assert_eq!(b.max.x, 10.0); 934 | assert_eq!(b.max.y, 20.0); 935 | assert_eq!(b.min.x, -10.0); 936 | assert_eq!(b.min.y, -20.0); 937 | } 938 | 939 | #[test] 940 | fn test_intersection() { 941 | let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]); 942 | let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]); 943 | assert!(b1.intersection(&b2).is_some()); 944 | 945 | let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(-10.0, 20.0)]); 946 | let b2 = Box2D::from_points(&[point2(10.0, 20.0), point2(15.0, -20.0)]); 947 | assert!(b1.intersection(&b2).is_none()); 948 | } 949 | 950 | #[test] 951 | fn test_scale() { 952 | let b = Box2D::from_points(&[point2(-10.0, -10.0), point2(10.0, 10.0)]); 953 | let b = b.scale(0.5, 0.5); 954 | assert_eq!(b.max.x, 5.0); 955 | assert_eq!(b.max.y, 5.0); 956 | assert_eq!(b.min.x, -5.0); 957 | assert_eq!(b.min.y, -5.0); 958 | } 959 | 960 | #[test] 961 | fn test_lerp() { 962 | let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(-10.0, -10.0)]); 963 | let b2 = Box2D::from_points(&[point2(10.0, 10.0), point2(20.0, 20.0)]); 964 | let b = b1.lerp(b2, 0.5); 965 | assert_eq!(b.center(), Point2D::zero()); 966 | assert_eq!(b.size().width, 10.0); 967 | assert_eq!(b.size().height, 10.0); 968 | } 969 | 970 | #[test] 971 | fn test_contains() { 972 | let b = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]); 973 | assert!(b.contains(point2(-15.3, 10.5))); 974 | } 975 | 976 | #[test] 977 | fn test_contains_box() { 978 | let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]); 979 | let b2 = Box2D::from_points(&[point2(-14.3, -16.5), point2(6.7, 17.6)]); 980 | assert!(b1.contains_box(&b2)); 981 | } 982 | 983 | #[test] 984 | fn test_inflate() { 985 | let b = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]); 986 | let b = b.inflate(10.0, 5.0); 987 | assert_eq!(b.size().width, 60.0); 988 | assert_eq!(b.size().height, 50.0); 989 | assert_eq!(b.center(), Point2D::zero()); 990 | } 991 | 992 | #[test] 993 | fn test_is_empty() { 994 | for i in 0..2 { 995 | let mut coords_neg = [-20.0, -20.0]; 996 | let mut coords_pos = [20.0, 20.0]; 997 | coords_neg[i] = 0.0; 998 | coords_pos[i] = 0.0; 999 | let b = Box2D::from_points(&[Point2D::from(coords_neg), Point2D::from(coords_pos)]); 1000 | assert!(b.is_empty()); 1001 | } 1002 | } 1003 | 1004 | #[test] 1005 | #[rustfmt::skip] 1006 | fn test_nan_empty() { 1007 | use std::f32::NAN; 1008 | assert!(Box2D { min: point2(NAN, 2.0), max: point2(1.0, 3.0) }.is_empty()); 1009 | assert!(Box2D { min: point2(0.0, NAN), max: point2(1.0, 2.0) }.is_empty()); 1010 | assert!(Box2D { min: point2(1.0, -2.0), max: point2(NAN, 2.0) }.is_empty()); 1011 | assert!(Box2D { min: point2(1.0, -2.0), max: point2(0.0, NAN) }.is_empty()); 1012 | } 1013 | 1014 | #[test] 1015 | fn test_from_origin_and_size() { 1016 | let b = Box2D::from_origin_and_size(point2(1.0, 2.0), size2(3.0, 4.0)); 1017 | assert_eq!(b.min, point2(1.0, 2.0)); 1018 | assert_eq!(b.size(), size2(3.0, 4.0)); 1019 | } 1020 | 1021 | #[test] 1022 | fn test_set_size() { 1023 | let mut b = Box2D { 1024 | min: point2(1.0, 2.0), 1025 | max: point2(3.0, 4.0), 1026 | }; 1027 | b.set_size(size2(5.0, 6.0)); 1028 | 1029 | assert_eq!(b.min, point2(1.0, 2.0)); 1030 | assert_eq!(b.size(), size2(5.0, 6.0)); 1031 | } 1032 | } 1033 | -------------------------------------------------------------------------------- /src/homogen.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | use crate::point::{Point2D, Point3D}; 11 | use crate::vector::{Vector2D, Vector3D}; 12 | 13 | use crate::num::{One, Zero}; 14 | 15 | #[cfg(feature = "bytemuck")] 16 | use bytemuck::{Pod, Zeroable}; 17 | use core::cmp::{Eq, PartialEq}; 18 | use core::fmt; 19 | use core::hash::Hash; 20 | use core::marker::PhantomData; 21 | use core::ops::Div; 22 | #[cfg(feature = "malloc_size_of")] 23 | use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 24 | #[cfg(feature = "serde")] 25 | use serde; 26 | 27 | /// Homogeneous vector in 3D space. 28 | #[repr(C)] 29 | pub struct HomogeneousVector { 30 | pub x: T, 31 | pub y: T, 32 | pub z: T, 33 | pub w: T, 34 | #[doc(hidden)] 35 | pub _unit: PhantomData, 36 | } 37 | 38 | impl Copy for HomogeneousVector {} 39 | 40 | impl Clone for HomogeneousVector { 41 | fn clone(&self) -> Self { 42 | HomogeneousVector { 43 | x: self.x.clone(), 44 | y: self.y.clone(), 45 | z: self.z.clone(), 46 | w: self.w.clone(), 47 | _unit: PhantomData, 48 | } 49 | } 50 | } 51 | 52 | #[cfg(feature = "serde")] 53 | impl<'de, T, U> serde::Deserialize<'de> for HomogeneousVector 54 | where 55 | T: serde::Deserialize<'de>, 56 | { 57 | fn deserialize(deserializer: D) -> Result 58 | where 59 | D: serde::Deserializer<'de>, 60 | { 61 | let (x, y, z, w) = serde::Deserialize::deserialize(deserializer)?; 62 | Ok(HomogeneousVector { 63 | x, 64 | y, 65 | z, 66 | w, 67 | _unit: PhantomData, 68 | }) 69 | } 70 | } 71 | 72 | #[cfg(feature = "serde")] 73 | impl serde::Serialize for HomogeneousVector 74 | where 75 | T: serde::Serialize, 76 | { 77 | fn serialize(&self, serializer: S) -> Result 78 | where 79 | S: serde::Serializer, 80 | { 81 | (&self.x, &self.y, &self.z, &self.w).serialize(serializer) 82 | } 83 | } 84 | 85 | #[cfg(feature = "arbitrary")] 86 | impl<'a, T, U> arbitrary::Arbitrary<'a> for HomogeneousVector 87 | where 88 | T: arbitrary::Arbitrary<'a>, 89 | { 90 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 91 | let (x, y, z, w) = arbitrary::Arbitrary::arbitrary(u)?; 92 | Ok(HomogeneousVector { 93 | x, 94 | y, 95 | z, 96 | w, 97 | _unit: PhantomData, 98 | }) 99 | } 100 | } 101 | 102 | #[cfg(feature = "bytemuck")] 103 | unsafe impl Zeroable for HomogeneousVector {} 104 | 105 | #[cfg(feature = "bytemuck")] 106 | unsafe impl Pod for HomogeneousVector {} 107 | 108 | #[cfg(feature = "malloc_size_of")] 109 | impl MallocSizeOf for HomogeneousVector { 110 | fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { 111 | self.x.size_of(ops) + self.y.size_of(ops) + self.z.size_of(ops) + self.w.size_of(ops) 112 | } 113 | } 114 | 115 | impl Eq for HomogeneousVector where T: Eq {} 116 | 117 | impl PartialEq for HomogeneousVector 118 | where 119 | T: PartialEq, 120 | { 121 | fn eq(&self, other: &Self) -> bool { 122 | self.x == other.x && self.y == other.y && self.z == other.z && self.w == other.w 123 | } 124 | } 125 | 126 | impl Hash for HomogeneousVector 127 | where 128 | T: Hash, 129 | { 130 | fn hash(&self, h: &mut H) { 131 | self.x.hash(h); 132 | self.y.hash(h); 133 | self.z.hash(h); 134 | self.w.hash(h); 135 | } 136 | } 137 | 138 | impl HomogeneousVector { 139 | /// Constructor taking scalar values directly. 140 | #[inline] 141 | pub const fn new(x: T, y: T, z: T, w: T) -> Self { 142 | HomogeneousVector { 143 | x, 144 | y, 145 | z, 146 | w, 147 | _unit: PhantomData, 148 | } 149 | } 150 | } 151 | 152 | impl + Zero + PartialOrd, U> HomogeneousVector { 153 | /// Convert into Cartesian 2D point. 154 | /// 155 | /// Returns `None` if the point is on or behind the W=0 hemisphere. 156 | #[inline] 157 | pub fn to_point2d(self) -> Option> { 158 | if self.w > T::zero() { 159 | Some(Point2D::new(self.x / self.w, self.y / self.w)) 160 | } else { 161 | None 162 | } 163 | } 164 | 165 | /// Convert into Cartesian 3D point. 166 | /// 167 | /// Returns `None` if the point is on or behind the W=0 hemisphere. 168 | #[inline] 169 | pub fn to_point3d(self) -> Option> { 170 | if self.w > T::zero() { 171 | Some(Point3D::new( 172 | self.x / self.w, 173 | self.y / self.w, 174 | self.z / self.w, 175 | )) 176 | } else { 177 | None 178 | } 179 | } 180 | } 181 | 182 | impl From> for HomogeneousVector { 183 | #[inline] 184 | fn from(v: Vector2D) -> Self { 185 | HomogeneousVector::new(v.x, v.y, T::zero(), T::zero()) 186 | } 187 | } 188 | 189 | impl From> for HomogeneousVector { 190 | #[inline] 191 | fn from(v: Vector3D) -> Self { 192 | HomogeneousVector::new(v.x, v.y, v.z, T::zero()) 193 | } 194 | } 195 | 196 | impl From> for HomogeneousVector { 197 | #[inline] 198 | fn from(p: Point2D) -> Self { 199 | HomogeneousVector::new(p.x, p.y, T::zero(), T::one()) 200 | } 201 | } 202 | 203 | impl From> for HomogeneousVector { 204 | #[inline] 205 | fn from(p: Point3D) -> Self { 206 | HomogeneousVector::new(p.x, p.y, p.z, T::one()) 207 | } 208 | } 209 | 210 | impl fmt::Debug for HomogeneousVector { 211 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 212 | f.debug_tuple("") 213 | .field(&self.x) 214 | .field(&self.y) 215 | .field(&self.z) 216 | .field(&self.w) 217 | .finish() 218 | } 219 | } 220 | 221 | #[cfg(test)] 222 | mod homogeneous { 223 | use super::HomogeneousVector; 224 | use crate::default::{Point2D, Point3D}; 225 | 226 | #[test] 227 | fn roundtrip() { 228 | assert_eq!( 229 | Some(Point2D::new(1.0, 2.0)), 230 | HomogeneousVector::from(Point2D::new(1.0, 2.0)).to_point2d() 231 | ); 232 | assert_eq!( 233 | Some(Point3D::new(1.0, -2.0, 0.1)), 234 | HomogeneousVector::from(Point3D::new(1.0, -2.0, 0.1)).to_point3d() 235 | ); 236 | } 237 | 238 | #[test] 239 | fn negative() { 240 | assert_eq!( 241 | None, 242 | HomogeneousVector::::new(1.0, 2.0, 3.0, 0.0).to_point2d() 243 | ); 244 | assert_eq!( 245 | None, 246 | HomogeneousVector::::new(1.0, -2.0, -3.0, -2.0).to_point3d() 247 | ); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/length.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | //! A one-dimensional length, tagged with its units. 10 | 11 | use crate::approxeq::ApproxEq; 12 | use crate::approxord::{max, min}; 13 | use crate::num::Zero; 14 | use crate::scale::Scale; 15 | 16 | use crate::num::One; 17 | #[cfg(feature = "bytemuck")] 18 | use bytemuck::{Pod, Zeroable}; 19 | use core::cmp::Ordering; 20 | use core::fmt; 21 | use core::hash::{Hash, Hasher}; 22 | use core::iter::Sum; 23 | use core::marker::PhantomData; 24 | use core::ops::{Add, Div, Mul, Neg, Sub}; 25 | use core::ops::{AddAssign, DivAssign, MulAssign, SubAssign}; 26 | #[cfg(feature = "malloc_size_of")] 27 | use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 28 | use num_traits::{NumCast, Saturating}; 29 | #[cfg(feature = "serde")] 30 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 31 | 32 | /// A one-dimensional distance, with value represented by `T` and unit of measurement `Unit`. 33 | /// 34 | /// `T` can be any numeric type, for example a primitive type like `u64` or `f32`. 35 | /// 36 | /// `Unit` is not used in the representation of a `Length` value. It is used only at compile time 37 | /// to ensure that a `Length` stored with one unit is converted explicitly before being used in an 38 | /// expression that requires a different unit. It may be a type without values, such as an empty 39 | /// enum. 40 | /// 41 | /// You can multiply a `Length` by a [`Scale`] to convert it from one unit to 42 | /// another. See the [`Scale`] docs for an example. 43 | #[repr(C)] 44 | pub struct Length(pub T, #[doc(hidden)] pub PhantomData); 45 | 46 | impl Clone for Length { 47 | fn clone(&self) -> Self { 48 | Length(self.0.clone(), PhantomData) 49 | } 50 | } 51 | 52 | impl Copy for Length {} 53 | 54 | #[cfg(feature = "serde")] 55 | impl<'de, T, U> Deserialize<'de> for Length 56 | where 57 | T: Deserialize<'de>, 58 | { 59 | fn deserialize(deserializer: D) -> Result 60 | where 61 | D: Deserializer<'de>, 62 | { 63 | Ok(Length(Deserialize::deserialize(deserializer)?, PhantomData)) 64 | } 65 | } 66 | 67 | #[cfg(feature = "serde")] 68 | impl Serialize for Length 69 | where 70 | T: Serialize, 71 | { 72 | fn serialize(&self, serializer: S) -> Result 73 | where 74 | S: Serializer, 75 | { 76 | self.0.serialize(serializer) 77 | } 78 | } 79 | 80 | #[cfg(feature = "arbitrary")] 81 | impl<'a, T, U> arbitrary::Arbitrary<'a> for Length 82 | where 83 | T: arbitrary::Arbitrary<'a>, 84 | { 85 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 86 | Ok(Length(arbitrary::Arbitrary::arbitrary(u)?, PhantomData)) 87 | } 88 | } 89 | 90 | #[cfg(feature = "bytemuck")] 91 | unsafe impl Zeroable for Length {} 92 | 93 | #[cfg(feature = "bytemuck")] 94 | unsafe impl Pod for Length {} 95 | 96 | #[cfg(feature = "malloc_size_of")] 97 | impl MallocSizeOf for Length { 98 | fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { 99 | self.0.size_of(ops) 100 | } 101 | } 102 | 103 | impl Length { 104 | /// Associate a value with a unit of measure. 105 | #[inline] 106 | pub const fn new(x: T) -> Self { 107 | Length(x, PhantomData) 108 | } 109 | } 110 | 111 | impl Length { 112 | /// Unpack the underlying value from the wrapper. 113 | pub fn get(self) -> T { 114 | self.0 115 | } 116 | 117 | /// Cast the unit 118 | #[inline] 119 | pub fn cast_unit(self) -> Length { 120 | Length::new(self.0) 121 | } 122 | 123 | /// Linearly interpolate between this length and another length. 124 | /// 125 | /// # Example 126 | /// 127 | /// ```rust 128 | /// use euclid::default::Length; 129 | /// 130 | /// let from = Length::new(0.0); 131 | /// let to = Length::new(8.0); 132 | /// 133 | /// assert_eq!(from.lerp(to, -1.0), Length::new(-8.0)); 134 | /// assert_eq!(from.lerp(to, 0.0), Length::new( 0.0)); 135 | /// assert_eq!(from.lerp(to, 0.5), Length::new( 4.0)); 136 | /// assert_eq!(from.lerp(to, 1.0), Length::new( 8.0)); 137 | /// assert_eq!(from.lerp(to, 2.0), Length::new(16.0)); 138 | /// ``` 139 | #[inline] 140 | pub fn lerp(self, other: Self, t: T) -> Self 141 | where 142 | T: One + Sub + Mul + Add, 143 | { 144 | let one_t = T::one() - t.clone(); 145 | Length::new(one_t * self.0.clone() + t * other.0) 146 | } 147 | } 148 | 149 | impl Length { 150 | /// Returns minimum between this length and another length. 151 | #[inline] 152 | pub fn min(self, other: Self) -> Self { 153 | min(self, other) 154 | } 155 | 156 | /// Returns maximum between this length and another length. 157 | #[inline] 158 | pub fn max(self, other: Self) -> Self { 159 | max(self, other) 160 | } 161 | } 162 | 163 | impl Length { 164 | /// Cast from one numeric representation to another, preserving the units. 165 | #[inline] 166 | pub fn cast(self) -> Length { 167 | self.try_cast().unwrap() 168 | } 169 | 170 | /// Fallible cast from one numeric representation to another, preserving the units. 171 | pub fn try_cast(self) -> Option> { 172 | NumCast::from(self.0).map(Length::new) 173 | } 174 | } 175 | 176 | impl fmt::Debug for Length { 177 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 178 | self.0.fmt(f) 179 | } 180 | } 181 | 182 | impl Default for Length { 183 | #[inline] 184 | fn default() -> Self { 185 | Length::new(Default::default()) 186 | } 187 | } 188 | 189 | impl Hash for Length { 190 | fn hash(&self, h: &mut H) { 191 | self.0.hash(h); 192 | } 193 | } 194 | 195 | // length + length 196 | impl Add for Length { 197 | type Output = Length; 198 | 199 | fn add(self, other: Self) -> Self::Output { 200 | Length::new(self.0 + other.0) 201 | } 202 | } 203 | 204 | // length + &length 205 | impl Add<&Self> for Length { 206 | type Output = Length; 207 | 208 | fn add(self, other: &Self) -> Self::Output { 209 | Length::new(self.0 + other.0) 210 | } 211 | } 212 | 213 | // length_iter.copied().sum() 214 | impl + Zero, U> Sum for Length { 215 | fn sum>(iter: I) -> Self { 216 | iter.fold(Self::zero(), Add::add) 217 | } 218 | } 219 | 220 | // length_iter.sum() 221 | impl<'a, T: 'a + Add + Copy + Zero, U: 'a> Sum<&'a Self> for Length { 222 | fn sum>(iter: I) -> Self { 223 | iter.fold(Self::zero(), Add::add) 224 | } 225 | } 226 | 227 | // length += length 228 | impl AddAssign for Length { 229 | fn add_assign(&mut self, other: Self) { 230 | self.0 += other.0; 231 | } 232 | } 233 | 234 | // length - length 235 | impl Sub for Length { 236 | type Output = Length; 237 | 238 | fn sub(self, other: Length) -> Self::Output { 239 | Length::new(self.0 - other.0) 240 | } 241 | } 242 | 243 | // length -= length 244 | impl SubAssign for Length { 245 | fn sub_assign(&mut self, other: Self) { 246 | self.0 -= other.0; 247 | } 248 | } 249 | 250 | // Saturating length + length and length - length. 251 | impl Saturating for Length { 252 | fn saturating_add(self, other: Self) -> Self { 253 | Length::new(self.0.saturating_add(other.0)) 254 | } 255 | 256 | fn saturating_sub(self, other: Self) -> Self { 257 | Length::new(self.0.saturating_sub(other.0)) 258 | } 259 | } 260 | 261 | // length / length 262 | impl Div> for Length { 263 | type Output = Scale; 264 | 265 | #[inline] 266 | fn div(self, other: Length) -> Self::Output { 267 | Scale::new(self.0 / other.0) 268 | } 269 | } 270 | 271 | // length * scalar 272 | impl Mul for Length { 273 | type Output = Length; 274 | 275 | #[inline] 276 | fn mul(self, scale: T) -> Self::Output { 277 | Length::new(self.0 * scale) 278 | } 279 | } 280 | 281 | // length *= scalar 282 | impl, U> MulAssign for Length { 283 | #[inline] 284 | fn mul_assign(&mut self, scale: T) { 285 | *self = *self * scale; 286 | } 287 | } 288 | 289 | // length / scalar 290 | impl Div for Length { 291 | type Output = Length; 292 | 293 | #[inline] 294 | fn div(self, scale: T) -> Self::Output { 295 | Length::new(self.0 / scale) 296 | } 297 | } 298 | 299 | // length /= scalar 300 | impl, U> DivAssign for Length { 301 | #[inline] 302 | fn div_assign(&mut self, scale: T) { 303 | *self = *self / scale; 304 | } 305 | } 306 | 307 | // length * scaleFactor 308 | impl Mul> for Length { 309 | type Output = Length; 310 | 311 | #[inline] 312 | fn mul(self, scale: Scale) -> Self::Output { 313 | Length::new(self.0 * scale.0) 314 | } 315 | } 316 | 317 | // length / scaleFactor 318 | impl Div> for Length { 319 | type Output = Length; 320 | 321 | #[inline] 322 | fn div(self, scale: Scale) -> Self::Output { 323 | Length::new(self.0 / scale.0) 324 | } 325 | } 326 | 327 | // -length 328 | impl Neg for Length { 329 | type Output = Length; 330 | 331 | #[inline] 332 | fn neg(self) -> Self::Output { 333 | Length::new(-self.0) 334 | } 335 | } 336 | 337 | impl PartialEq for Length { 338 | fn eq(&self, other: &Self) -> bool { 339 | self.0.eq(&other.0) 340 | } 341 | } 342 | 343 | impl PartialOrd for Length { 344 | fn partial_cmp(&self, other: &Self) -> Option { 345 | self.0.partial_cmp(&other.0) 346 | } 347 | } 348 | 349 | impl Eq for Length {} 350 | 351 | impl Ord for Length { 352 | fn cmp(&self, other: &Self) -> Ordering { 353 | self.0.cmp(&other.0) 354 | } 355 | } 356 | 357 | impl Zero for Length { 358 | #[inline] 359 | fn zero() -> Self { 360 | Length::new(Zero::zero()) 361 | } 362 | } 363 | 364 | impl> ApproxEq for Length { 365 | #[inline] 366 | fn approx_epsilon() -> T { 367 | T::approx_epsilon() 368 | } 369 | 370 | #[inline] 371 | fn approx_eq_eps(&self, other: &Length, approx_epsilon: &T) -> bool { 372 | self.0.approx_eq_eps(&other.0, approx_epsilon) 373 | } 374 | } 375 | 376 | #[cfg(test)] 377 | mod tests { 378 | use super::Length; 379 | use crate::num::Zero; 380 | 381 | use crate::scale::Scale; 382 | use core::f32::INFINITY; 383 | use num_traits::Saturating; 384 | 385 | enum Inch {} 386 | enum Mm {} 387 | enum Cm {} 388 | enum Second {} 389 | 390 | #[cfg(feature = "serde")] 391 | mod serde { 392 | use super::*; 393 | 394 | extern crate serde_test; 395 | use self::serde_test::assert_tokens; 396 | use self::serde_test::Token; 397 | 398 | #[test] 399 | fn test_length_serde() { 400 | let one_cm: Length = Length::new(10.0); 401 | 402 | assert_tokens(&one_cm, &[Token::F32(10.0)]); 403 | } 404 | } 405 | 406 | #[test] 407 | fn test_clone() { 408 | // A cloned Length is a separate length with the state matching the 409 | // original Length at the point it was cloned. 410 | let mut variable_length: Length = Length::new(12.0); 411 | 412 | let one_foot = variable_length.clone(); 413 | variable_length.0 = 24.0; 414 | 415 | assert_eq!(one_foot.get(), 12.0); 416 | assert_eq!(variable_length.get(), 24.0); 417 | } 418 | 419 | #[test] 420 | fn test_add() { 421 | let length1: Length = Length::new(250); 422 | let length2: Length = Length::new(5); 423 | 424 | assert_eq!((length1 + length2).get(), 255); 425 | assert_eq!((length1 + &length2).get(), 255); 426 | } 427 | 428 | #[test] 429 | fn test_sum() { 430 | type L = Length; 431 | let lengths = [L::new(1.0), L::new(2.0), L::new(3.0)]; 432 | 433 | assert_eq!(lengths.iter().sum::(), L::new(6.0)); 434 | } 435 | 436 | #[test] 437 | fn test_addassign() { 438 | let one_cm: Length = Length::new(10.0); 439 | let mut measurement: Length = Length::new(5.0); 440 | 441 | measurement += one_cm; 442 | 443 | assert_eq!(measurement.get(), 15.0); 444 | } 445 | 446 | #[test] 447 | fn test_sub() { 448 | let length1: Length = Length::new(250); 449 | let length2: Length = Length::new(5); 450 | 451 | let result = length1 - length2; 452 | 453 | assert_eq!(result.get(), 245); 454 | } 455 | 456 | #[test] 457 | fn test_subassign() { 458 | let one_cm: Length = Length::new(10.0); 459 | let mut measurement: Length = Length::new(5.0); 460 | 461 | measurement -= one_cm; 462 | 463 | assert_eq!(measurement.get(), -5.0); 464 | } 465 | 466 | #[test] 467 | fn test_saturating_add() { 468 | let length1: Length = Length::new(250); 469 | let length2: Length = Length::new(6); 470 | 471 | let result = length1.saturating_add(length2); 472 | 473 | assert_eq!(result.get(), 255); 474 | } 475 | 476 | #[test] 477 | fn test_saturating_sub() { 478 | let length1: Length = Length::new(5); 479 | let length2: Length = Length::new(10); 480 | 481 | let result = length1.saturating_sub(length2); 482 | 483 | assert_eq!(result.get(), 0); 484 | } 485 | 486 | #[test] 487 | fn test_division_by_length() { 488 | // Division results in a Scale from denominator units 489 | // to numerator units. 490 | let length: Length = Length::new(5.0); 491 | let duration: Length = Length::new(10.0); 492 | 493 | let result = length / duration; 494 | 495 | let expected: Scale = Scale::new(0.5); 496 | assert_eq!(result, expected); 497 | } 498 | 499 | #[test] 500 | fn test_multiplication() { 501 | let length_mm: Length = Length::new(10.0); 502 | let cm_per_mm: Scale = Scale::new(0.1); 503 | 504 | let result = length_mm * cm_per_mm; 505 | 506 | let expected: Length = Length::new(1.0); 507 | assert_eq!(result, expected); 508 | } 509 | 510 | #[test] 511 | fn test_multiplication_with_scalar() { 512 | let length_mm: Length = Length::new(10.0); 513 | 514 | let result = length_mm * 2.0; 515 | 516 | let expected: Length = Length::new(20.0); 517 | assert_eq!(result, expected); 518 | } 519 | 520 | #[test] 521 | fn test_multiplication_assignment() { 522 | let mut length: Length = Length::new(10.0); 523 | 524 | length *= 2.0; 525 | 526 | let expected: Length = Length::new(20.0); 527 | assert_eq!(length, expected); 528 | } 529 | 530 | #[test] 531 | fn test_division_by_scalefactor() { 532 | let length: Length = Length::new(5.0); 533 | let cm_per_second: Scale = Scale::new(10.0); 534 | 535 | let result = length / cm_per_second; 536 | 537 | let expected: Length = Length::new(0.5); 538 | assert_eq!(result, expected); 539 | } 540 | 541 | #[test] 542 | fn test_division_by_scalar() { 543 | let length: Length = Length::new(5.0); 544 | 545 | let result = length / 2.0; 546 | 547 | let expected: Length = Length::new(2.5); 548 | assert_eq!(result, expected); 549 | } 550 | 551 | #[test] 552 | fn test_division_assignment() { 553 | let mut length: Length = Length::new(10.0); 554 | 555 | length /= 2.0; 556 | 557 | let expected: Length = Length::new(5.0); 558 | assert_eq!(length, expected); 559 | } 560 | 561 | #[test] 562 | fn test_negation() { 563 | let length: Length = Length::new(5.0); 564 | 565 | let result = -length; 566 | 567 | let expected: Length = Length::new(-5.0); 568 | assert_eq!(result, expected); 569 | } 570 | 571 | #[test] 572 | fn test_cast() { 573 | let length_as_i32: Length = Length::new(5); 574 | 575 | let result: Length = length_as_i32.cast(); 576 | 577 | let length_as_f32: Length = Length::new(5.0); 578 | assert_eq!(result, length_as_f32); 579 | } 580 | 581 | #[test] 582 | fn test_equality() { 583 | let length_5_point_0: Length = Length::new(5.0); 584 | let length_5_point_1: Length = Length::new(5.1); 585 | let length_0_point_1: Length = Length::new(0.1); 586 | 587 | assert!(length_5_point_0 == length_5_point_1 - length_0_point_1); 588 | assert!(length_5_point_0 != length_5_point_1); 589 | } 590 | 591 | #[test] 592 | fn test_order() { 593 | let length_5_point_0: Length = Length::new(5.0); 594 | let length_5_point_1: Length = Length::new(5.1); 595 | let length_0_point_1: Length = Length::new(0.1); 596 | 597 | assert!(length_5_point_0 < length_5_point_1); 598 | assert!(length_5_point_0 <= length_5_point_1); 599 | assert!(length_5_point_0 <= length_5_point_1 - length_0_point_1); 600 | assert!(length_5_point_1 > length_5_point_0); 601 | assert!(length_5_point_1 >= length_5_point_0); 602 | assert!(length_5_point_0 >= length_5_point_1 - length_0_point_1); 603 | } 604 | 605 | #[test] 606 | fn test_zero_add() { 607 | type LengthCm = Length; 608 | let length: LengthCm = Length::new(5.0); 609 | 610 | let result = length - LengthCm::zero(); 611 | 612 | assert_eq!(result, length); 613 | } 614 | 615 | #[test] 616 | fn test_zero_division() { 617 | type LengthCm = Length; 618 | let length: LengthCm = Length::new(5.0); 619 | let length_zero: LengthCm = Length::zero(); 620 | 621 | let result = length / length_zero; 622 | 623 | let expected: Scale = Scale::new(INFINITY); 624 | assert_eq!(result, expected); 625 | } 626 | } 627 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | #![cfg_attr(not(test), no_std)] 11 | 12 | //! A collection of strongly typed math tools for computer graphics with an inclination 13 | //! towards 2d graphics and layout. 14 | //! 15 | //! All types are generic over the scalar type of their component (`f32`, `i32`, etc.), 16 | //! and tagged with a generic Unit parameter which is useful to prevent mixing 17 | //! values from different spaces. For example it should not be legal to translate 18 | //! a screen-space position by a world-space vector and this can be expressed using 19 | //! the generic Unit parameter. 20 | //! 21 | //! This unit system is not mandatory and all structures have an alias 22 | //! with the default unit: `UnknownUnit`. 23 | //! for example ```default::Point2D``` is equivalent to ```Point2D```. 24 | //! Client code typically creates a set of aliases for each type and doesn't need 25 | //! to deal with the specifics of typed units further. For example: 26 | //! 27 | //! ```rust 28 | //! use euclid::*; 29 | //! pub struct ScreenSpace; 30 | //! pub type ScreenPoint = Point2D; 31 | //! pub type ScreenSize = Size2D; 32 | //! pub struct WorldSpace; 33 | //! pub type WorldPoint = Point3D; 34 | //! pub type ProjectionMatrix = Transform3D; 35 | //! // etc... 36 | //! ``` 37 | //! 38 | //! All euclid types are marked `#[repr(C)]` in order to facilitate exposing them to 39 | //! foreign function interfaces (provided the underlying scalar type is also `repr(C)`). 40 | //! 41 | #![deny(unconditional_recursion)] 42 | #![warn(clippy::semicolon_if_nothing_returned)] 43 | 44 | pub use crate::angle::Angle; 45 | pub use crate::box2d::Box2D; 46 | pub use crate::homogen::HomogeneousVector; 47 | pub use crate::length::Length; 48 | pub use crate::point::{point2, point3, Point2D, Point3D}; 49 | pub use crate::scale::Scale; 50 | pub use crate::transform2d::Transform2D; 51 | pub use crate::transform3d::Transform3D; 52 | pub use crate::vector::{bvec2, bvec3, BoolVector2D, BoolVector3D}; 53 | pub use crate::vector::{vec2, vec3, Vector2D, Vector3D}; 54 | 55 | pub use crate::box3d::{box3d, Box3D}; 56 | pub use crate::rect::{rect, Rect}; 57 | pub use crate::rigid::RigidTransform3D; 58 | pub use crate::rotation::{Rotation2D, Rotation3D}; 59 | pub use crate::side_offsets::SideOffsets2D; 60 | pub use crate::size::{size2, size3, Size2D, Size3D}; 61 | pub use crate::translation::{Translation2D, Translation3D}; 62 | pub use crate::trig::Trig; 63 | 64 | #[macro_use] 65 | mod macros; 66 | 67 | mod angle; 68 | pub mod approxeq; 69 | pub mod approxord; 70 | mod box2d; 71 | mod box3d; 72 | mod homogen; 73 | mod length; 74 | pub mod num; 75 | mod point; 76 | mod rect; 77 | mod rigid; 78 | mod rotation; 79 | mod scale; 80 | mod side_offsets; 81 | mod size; 82 | mod transform2d; 83 | mod transform3d; 84 | mod translation; 85 | mod trig; 86 | mod vector; 87 | 88 | /// The default unit. 89 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 90 | pub struct UnknownUnit; 91 | 92 | pub mod default { 93 | //! A set of aliases for all types, tagged with the default unknown unit. 94 | 95 | use super::UnknownUnit; 96 | pub type Length = super::Length; 97 | pub type Point2D = super::Point2D; 98 | pub type Point3D = super::Point3D; 99 | pub type Vector2D = super::Vector2D; 100 | pub type Vector3D = super::Vector3D; 101 | pub type HomogeneousVector = super::HomogeneousVector; 102 | pub type Size2D = super::Size2D; 103 | pub type Size3D = super::Size3D; 104 | pub type Rect = super::Rect; 105 | pub type Box2D = super::Box2D; 106 | pub type Box3D = super::Box3D; 107 | pub type SideOffsets2D = super::SideOffsets2D; 108 | pub type Transform2D = super::Transform2D; 109 | pub type Transform3D = super::Transform3D; 110 | pub type Rotation2D = super::Rotation2D; 111 | pub type Rotation3D = super::Rotation3D; 112 | pub type Translation2D = super::Translation2D; 113 | pub type Translation3D = super::Translation3D; 114 | pub type Scale = super::Scale; 115 | pub type RigidTransform3D = super::RigidTransform3D; 116 | } 117 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | macro_rules! mint_vec { 11 | ($name:ident [ $($field:ident),* ] = $std_name:ident) => { 12 | #[cfg(feature = "mint")] 13 | impl From> for $name { 14 | fn from(v: mint::$std_name) -> Self { 15 | $name { 16 | $( $field: v.$field, )* 17 | _unit: PhantomData, 18 | } 19 | } 20 | } 21 | #[cfg(feature = "mint")] 22 | impl From<$name> for mint::$std_name { 23 | fn from(v: $name) -> Self { 24 | mint::$std_name { 25 | $( $field: v.$field, )* 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/num.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | //! A one-dimensional length, tagged with its units. 10 | 11 | use num_traits; 12 | 13 | // Euclid has its own Zero and One traits instead of of using the num_traits equivalents. 14 | // Unfortunately, num_traits::Zero requires Add, which opens a bag of sad things: 15 | // - Most importantly, for Point2D to implement Zero it would need to implement Add which we 16 | // don't want (we allow "Point + Vector" and "Vector + Vector" semantics and purposefully disallow 17 | // "Point + Point". 18 | // - Some operations that require, say, One and Div (for example Scale::inv) currently return a 19 | // type parameterized over T::Output which is ambiguous with num_traits::One because it inherits 20 | // Mul which also has an Output associated type. To fix it need to complicate type signatures 21 | // by using ::Output which makes the code and documentation harder to read. 22 | // 23 | // On the other hand, euclid::num::Zero/One are automatically implemented for all types that 24 | // implement their num_traits counterpart. Euclid users never need to explicitly use 25 | // euclid::num::Zero/One and can/should only manipulate the num_traits equivalents without risk 26 | // of compatibility issues with euclid. 27 | 28 | pub trait Zero { 29 | fn zero() -> Self; 30 | } 31 | 32 | impl Zero for T { 33 | fn zero() -> T { 34 | num_traits::Zero::zero() 35 | } 36 | } 37 | 38 | pub trait One { 39 | fn one() -> Self; 40 | } 41 | 42 | impl One for T { 43 | fn one() -> T { 44 | num_traits::One::one() 45 | } 46 | } 47 | 48 | /// Defines the nearest integer value to the original value. 49 | pub trait Round: Copy { 50 | /// Rounds to the nearest integer value. 51 | /// 52 | /// This behavior is preserved for negative values (unlike the basic cast). 53 | #[must_use] 54 | fn round(self) -> Self; 55 | } 56 | /// Defines the biggest integer equal or lower than the original value. 57 | pub trait Floor: Copy { 58 | /// Rounds to the biggest integer equal or lower than the original value. 59 | /// 60 | /// This behavior is preserved for negative values (unlike the basic cast). 61 | #[must_use] 62 | fn floor(self) -> Self; 63 | } 64 | /// Defines the smallest integer equal or greater than the original value. 65 | pub trait Ceil: Copy { 66 | /// Rounds to the smallest integer equal or greater than the original value. 67 | /// 68 | /// This behavior is preserved for negative values (unlike the basic cast). 69 | #[must_use] 70 | fn ceil(self) -> Self; 71 | } 72 | 73 | macro_rules! num_int { 74 | ($ty:ty) => { 75 | impl Round for $ty { 76 | #[inline] 77 | fn round(self) -> $ty { 78 | self 79 | } 80 | } 81 | impl Floor for $ty { 82 | #[inline] 83 | fn floor(self) -> $ty { 84 | self 85 | } 86 | } 87 | impl Ceil for $ty { 88 | #[inline] 89 | fn ceil(self) -> $ty { 90 | self 91 | } 92 | } 93 | }; 94 | } 95 | 96 | macro_rules! num_float { 97 | ($ty:ty) => { 98 | impl Round for $ty { 99 | #[inline] 100 | fn round(self) -> $ty { 101 | (self + 0.5).floor() 102 | } 103 | } 104 | impl Floor for $ty { 105 | #[inline] 106 | fn floor(self) -> $ty { 107 | num_traits::Float::floor(self) 108 | } 109 | } 110 | impl Ceil for $ty { 111 | #[inline] 112 | fn ceil(self) -> $ty { 113 | num_traits::Float::ceil(self) 114 | } 115 | } 116 | }; 117 | } 118 | 119 | num_int!(i16); 120 | num_int!(u16); 121 | num_int!(i32); 122 | num_int!(u32); 123 | num_int!(i64); 124 | num_int!(u64); 125 | num_int!(isize); 126 | num_int!(usize); 127 | num_float!(f32); 128 | num_float!(f64); 129 | -------------------------------------------------------------------------------- /src/rect.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | use super::UnknownUnit; 11 | use crate::box2d::Box2D; 12 | use crate::num::*; 13 | use crate::point::Point2D; 14 | use crate::scale::Scale; 15 | use crate::side_offsets::SideOffsets2D; 16 | use crate::size::Size2D; 17 | use crate::vector::Vector2D; 18 | 19 | #[cfg(feature = "bytemuck")] 20 | use bytemuck::{Pod, Zeroable}; 21 | #[cfg(feature = "malloc_size_of")] 22 | use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 23 | use num_traits::{Float, NumCast}; 24 | #[cfg(feature = "serde")] 25 | use serde::{Deserialize, Serialize}; 26 | 27 | use core::borrow::Borrow; 28 | use core::cmp::PartialOrd; 29 | use core::fmt; 30 | use core::hash::{Hash, Hasher}; 31 | use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Range, Sub}; 32 | 33 | /// A 2d Rectangle optionally tagged with a unit. 34 | /// 35 | /// # Representation 36 | /// 37 | /// `Rect` is represented by an origin point and a size. 38 | /// 39 | /// See [`Box2D`] for a rectangle represented by two endpoints. 40 | /// 41 | /// # Empty rectangle 42 | /// 43 | /// A rectangle is considered empty (see [`is_empty`]) if any of the following is true: 44 | /// - it's area is empty, 45 | /// - it's area is negative (`size.x < 0` or `size.y < 0`), 46 | /// - it contains NaNs. 47 | /// 48 | /// [`is_empty`]: Self::is_empty 49 | #[repr(C)] 50 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 51 | #[cfg_attr( 52 | feature = "serde", 53 | serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")) 54 | )] 55 | pub struct Rect { 56 | pub origin: Point2D, 57 | pub size: Size2D, 58 | } 59 | 60 | #[cfg(feature = "arbitrary")] 61 | impl<'a, T, U> arbitrary::Arbitrary<'a> for Rect 62 | where 63 | T: arbitrary::Arbitrary<'a>, 64 | { 65 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 66 | let (origin, size) = arbitrary::Arbitrary::arbitrary(u)?; 67 | Ok(Rect { origin, size }) 68 | } 69 | } 70 | 71 | #[cfg(feature = "bytemuck")] 72 | unsafe impl Zeroable for Rect {} 73 | 74 | #[cfg(feature = "bytemuck")] 75 | unsafe impl Pod for Rect {} 76 | 77 | impl Hash for Rect { 78 | fn hash(&self, h: &mut H) { 79 | self.origin.hash(h); 80 | self.size.hash(h); 81 | } 82 | } 83 | 84 | #[cfg(feature = "malloc_size_of")] 85 | impl MallocSizeOf for Rect { 86 | fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { 87 | self.origin.size_of(ops) + self.size.size_of(ops) 88 | } 89 | } 90 | 91 | impl Copy for Rect {} 92 | 93 | impl Clone for Rect { 94 | fn clone(&self) -> Self { 95 | Self::new(self.origin.clone(), self.size.clone()) 96 | } 97 | } 98 | 99 | impl PartialEq for Rect { 100 | fn eq(&self, other: &Self) -> bool { 101 | self.origin.eq(&other.origin) && self.size.eq(&other.size) 102 | } 103 | } 104 | 105 | impl Eq for Rect {} 106 | 107 | impl fmt::Debug for Rect { 108 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 109 | write!(f, "Rect(")?; 110 | fmt::Debug::fmt(&self.size, f)?; 111 | write!(f, " at ")?; 112 | fmt::Debug::fmt(&self.origin, f)?; 113 | write!(f, ")") 114 | } 115 | } 116 | 117 | impl Default for Rect { 118 | fn default() -> Self { 119 | Rect::new(Default::default(), Default::default()) 120 | } 121 | } 122 | 123 | impl Rect { 124 | /// Constructor. 125 | #[inline] 126 | pub const fn new(origin: Point2D, size: Size2D) -> Self { 127 | Rect { origin, size } 128 | } 129 | } 130 | 131 | impl Rect 132 | where 133 | T: Zero, 134 | { 135 | /// Constructor, setting all sides to zero. 136 | #[inline] 137 | pub fn zero() -> Self { 138 | Rect::new(Point2D::origin(), Size2D::zero()) 139 | } 140 | 141 | /// Creates a rect of the given size, at offset zero. 142 | #[inline] 143 | pub fn from_size(size: Size2D) -> Self { 144 | Rect { 145 | origin: Point2D::zero(), 146 | size, 147 | } 148 | } 149 | } 150 | 151 | impl Rect 152 | where 153 | T: Copy + Add, 154 | { 155 | #[inline] 156 | pub fn min(&self) -> Point2D { 157 | self.origin 158 | } 159 | 160 | #[inline] 161 | pub fn max(&self) -> Point2D { 162 | self.origin + self.size 163 | } 164 | 165 | #[inline] 166 | pub fn max_x(&self) -> T { 167 | self.origin.x + self.size.width 168 | } 169 | 170 | #[inline] 171 | pub fn min_x(&self) -> T { 172 | self.origin.x 173 | } 174 | 175 | #[inline] 176 | pub fn max_y(&self) -> T { 177 | self.origin.y + self.size.height 178 | } 179 | 180 | #[inline] 181 | pub fn min_y(&self) -> T { 182 | self.origin.y 183 | } 184 | 185 | #[inline] 186 | pub fn width(&self) -> T { 187 | self.size.width 188 | } 189 | 190 | #[inline] 191 | pub fn height(&self) -> T { 192 | self.size.height 193 | } 194 | 195 | #[inline] 196 | pub fn x_range(&self) -> Range { 197 | self.min_x()..self.max_x() 198 | } 199 | 200 | #[inline] 201 | pub fn y_range(&self) -> Range { 202 | self.min_y()..self.max_y() 203 | } 204 | 205 | /// Returns the same rectangle, translated by a vector. 206 | #[inline] 207 | #[must_use] 208 | pub fn translate(&self, by: Vector2D) -> Self { 209 | Self::new(self.origin + by, self.size) 210 | } 211 | 212 | #[inline] 213 | pub fn to_box2d(&self) -> Box2D { 214 | Box2D { 215 | min: self.min(), 216 | max: self.max(), 217 | } 218 | } 219 | } 220 | 221 | impl Rect 222 | where 223 | T: Copy + PartialOrd + Add, 224 | { 225 | /// Returns `true` if this rectangle contains the point. Points are considered 226 | /// in the rectangle if they are on the left or top edge, but outside if they 227 | /// are on the right or bottom edge. 228 | #[inline] 229 | pub fn contains(&self, p: Point2D) -> bool { 230 | self.to_box2d().contains(p) 231 | } 232 | 233 | #[inline] 234 | pub fn intersects(&self, other: &Self) -> bool { 235 | self.to_box2d().intersects(&other.to_box2d()) 236 | } 237 | } 238 | 239 | impl Rect 240 | where 241 | T: Copy + PartialOrd + Add + Sub, 242 | { 243 | #[inline] 244 | pub fn intersection(&self, other: &Self) -> Option { 245 | let box2d = self.to_box2d().intersection_unchecked(&other.to_box2d()); 246 | 247 | if box2d.is_empty() { 248 | return None; 249 | } 250 | 251 | Some(box2d.to_rect()) 252 | } 253 | } 254 | 255 | impl Rect 256 | where 257 | T: Copy + Add + Sub, 258 | { 259 | #[inline] 260 | #[must_use] 261 | pub fn inflate(&self, width: T, height: T) -> Self { 262 | Rect::new( 263 | Point2D::new(self.origin.x - width, self.origin.y - height), 264 | Size2D::new( 265 | self.size.width + width + width, 266 | self.size.height + height + height, 267 | ), 268 | ) 269 | } 270 | } 271 | 272 | impl Rect 273 | where 274 | T: Copy + Zero + PartialOrd + Add, 275 | { 276 | /// Returns `true` if this rectangle contains the interior of `rect`. Always 277 | /// returns `true` if `rect` is empty, and always returns `false` if `rect` is 278 | /// nonempty but this rectangle is empty. 279 | #[inline] 280 | pub fn contains_rect(&self, rect: &Self) -> bool { 281 | rect.is_empty() 282 | || (self.min_x() <= rect.min_x() 283 | && rect.max_x() <= self.max_x() 284 | && self.min_y() <= rect.min_y() 285 | && rect.max_y() <= self.max_y()) 286 | } 287 | } 288 | 289 | impl Rect 290 | where 291 | T: Copy + Zero + PartialOrd + Add + Sub, 292 | { 293 | /// Calculate the size and position of an inner rectangle. 294 | /// 295 | /// Subtracts the side offsets from all sides. The horizontal and vertical 296 | /// offsets must not be larger than the original side length. 297 | /// This method assumes y oriented downward. 298 | pub fn inner_rect(&self, offsets: SideOffsets2D) -> Self { 299 | let rect = Rect::new( 300 | Point2D::new(self.origin.x + offsets.left, self.origin.y + offsets.top), 301 | Size2D::new( 302 | self.size.width - offsets.horizontal(), 303 | self.size.height - offsets.vertical(), 304 | ), 305 | ); 306 | debug_assert!(rect.size.width >= Zero::zero()); 307 | debug_assert!(rect.size.height >= Zero::zero()); 308 | rect 309 | } 310 | } 311 | 312 | impl Rect 313 | where 314 | T: Copy + Add + Sub, 315 | { 316 | /// Calculate the size and position of an outer rectangle. 317 | /// 318 | /// Add the offsets to all sides. The expanded rectangle is returned. 319 | /// This method assumes y oriented downward. 320 | pub fn outer_rect(&self, offsets: SideOffsets2D) -> Self { 321 | Rect::new( 322 | Point2D::new(self.origin.x - offsets.left, self.origin.y - offsets.top), 323 | Size2D::new( 324 | self.size.width + offsets.horizontal(), 325 | self.size.height + offsets.vertical(), 326 | ), 327 | ) 328 | } 329 | } 330 | 331 | impl Rect 332 | where 333 | T: Copy + Zero + PartialOrd + Sub, 334 | { 335 | /// Returns the smallest rectangle defined by the top/bottom/left/right-most 336 | /// points provided as parameter. 337 | /// 338 | /// Note: This function has a behavior that can be surprising because 339 | /// the right-most and bottom-most points are exactly on the edge 340 | /// of the rectangle while the [`Rect::contains`] function is has exclusive 341 | /// semantic on these edges. This means that the right-most and bottom-most 342 | /// points provided to [`Rect::from_points`] will count as not contained by the rect. 343 | /// This behavior may change in the future. 344 | /// 345 | /// See [`Box2D::from_points`] for more details. 346 | pub fn from_points(points: I) -> Self 347 | where 348 | I: IntoIterator, 349 | I::Item: Borrow>, 350 | { 351 | Box2D::from_points(points).to_rect() 352 | } 353 | } 354 | 355 | impl Rect 356 | where 357 | T: Copy + One + Add + Sub + Mul, 358 | { 359 | /// Linearly interpolate between this rectangle and another rectangle. 360 | #[inline] 361 | pub fn lerp(&self, other: Self, t: T) -> Self { 362 | Self::new( 363 | self.origin.lerp(other.origin, t), 364 | self.size.lerp(other.size, t), 365 | ) 366 | } 367 | } 368 | 369 | impl Rect 370 | where 371 | T: Copy + One + Add + Div, 372 | { 373 | pub fn center(&self) -> Point2D { 374 | let two = T::one() + T::one(); 375 | self.origin + self.size.to_vector() / two 376 | } 377 | } 378 | 379 | impl Rect 380 | where 381 | T: Copy + PartialOrd + Add + Sub + Zero, 382 | { 383 | #[inline] 384 | pub fn union(&self, other: &Self) -> Self { 385 | self.to_box2d().union(&other.to_box2d()).to_rect() 386 | } 387 | } 388 | 389 | impl Rect { 390 | #[inline] 391 | pub fn scale(&self, x: S, y: S) -> Self 392 | where 393 | T: Copy + Mul, 394 | { 395 | Rect::new( 396 | Point2D::new(self.origin.x * x, self.origin.y * y), 397 | Size2D::new(self.size.width * x, self.size.height * y), 398 | ) 399 | } 400 | } 401 | 402 | impl, U> Rect { 403 | #[inline] 404 | pub fn area(&self) -> T { 405 | self.size.area() 406 | } 407 | } 408 | 409 | impl Rect { 410 | #[inline] 411 | pub fn is_empty(&self) -> bool { 412 | self.size.is_empty() 413 | } 414 | } 415 | 416 | impl Rect { 417 | #[inline] 418 | pub fn to_non_empty(&self) -> Option { 419 | if self.is_empty() { 420 | return None; 421 | } 422 | 423 | Some(*self) 424 | } 425 | } 426 | 427 | impl Mul for Rect { 428 | type Output = Rect; 429 | 430 | #[inline] 431 | fn mul(self, scale: T) -> Self::Output { 432 | Rect::new(self.origin * scale, self.size * scale) 433 | } 434 | } 435 | 436 | impl MulAssign for Rect { 437 | #[inline] 438 | fn mul_assign(&mut self, scale: T) { 439 | *self *= Scale::new(scale); 440 | } 441 | } 442 | 443 | impl Div for Rect { 444 | type Output = Rect; 445 | 446 | #[inline] 447 | fn div(self, scale: T) -> Self::Output { 448 | Rect::new(self.origin / scale.clone(), self.size / scale) 449 | } 450 | } 451 | 452 | impl DivAssign for Rect { 453 | #[inline] 454 | fn div_assign(&mut self, scale: T) { 455 | *self /= Scale::new(scale); 456 | } 457 | } 458 | 459 | impl Mul> for Rect { 460 | type Output = Rect; 461 | 462 | #[inline] 463 | fn mul(self, scale: Scale) -> Self::Output { 464 | Rect::new(self.origin * scale.clone(), self.size * scale) 465 | } 466 | } 467 | 468 | impl MulAssign> for Rect { 469 | #[inline] 470 | fn mul_assign(&mut self, scale: Scale) { 471 | self.origin *= scale.clone(); 472 | self.size *= scale; 473 | } 474 | } 475 | 476 | impl Div> for Rect { 477 | type Output = Rect; 478 | 479 | #[inline] 480 | fn div(self, scale: Scale) -> Self::Output { 481 | Rect::new(self.origin / scale.clone(), self.size / scale) 482 | } 483 | } 484 | 485 | impl DivAssign> for Rect { 486 | #[inline] 487 | fn div_assign(&mut self, scale: Scale) { 488 | self.origin /= scale.clone(); 489 | self.size /= scale; 490 | } 491 | } 492 | 493 | impl Rect { 494 | /// Drop the units, preserving only the numeric value. 495 | #[inline] 496 | pub fn to_untyped(&self) -> Rect { 497 | Rect::new(self.origin.to_untyped(), self.size.to_untyped()) 498 | } 499 | 500 | /// Tag a unitless value with units. 501 | #[inline] 502 | pub fn from_untyped(r: &Rect) -> Rect { 503 | Rect::new( 504 | Point2D::from_untyped(r.origin), 505 | Size2D::from_untyped(r.size), 506 | ) 507 | } 508 | 509 | /// Cast the unit 510 | #[inline] 511 | pub fn cast_unit(&self) -> Rect { 512 | Rect::new(self.origin.cast_unit(), self.size.cast_unit()) 513 | } 514 | } 515 | 516 | impl Rect { 517 | /// Cast from one numeric representation to another, preserving the units. 518 | /// 519 | /// When casting from floating point to integer coordinates, the decimals are truncated 520 | /// as one would expect from a simple cast, but this behavior does not always make sense 521 | /// geometrically. Consider using [`round`], [`round_in`] or [`round_out`] before casting. 522 | /// 523 | /// [`round`]: Self::round 524 | /// [`round_in`]: Self::round_in 525 | /// [`round_out`]: Self::round_out 526 | #[inline] 527 | pub fn cast(&self) -> Rect { 528 | Rect::new(self.origin.cast(), self.size.cast()) 529 | } 530 | 531 | /// Fallible cast from one numeric representation to another, preserving the units. 532 | /// 533 | /// When casting from floating point to integer coordinates, the decimals are truncated 534 | /// as one would expect from a simple cast, but this behavior does not always make sense 535 | /// geometrically. Consider using [`round`], [`round_in`] or [`round_out` before casting. 536 | /// 537 | /// [`round`]: Self::round 538 | /// [`round_in`]: Self::round_in 539 | /// [`round_out`]: Self::round_out 540 | pub fn try_cast(&self) -> Option> { 541 | match (self.origin.try_cast(), self.size.try_cast()) { 542 | (Some(origin), Some(size)) => Some(Rect::new(origin, size)), 543 | _ => None, 544 | } 545 | } 546 | 547 | // Convenience functions for common casts 548 | 549 | /// Cast into an `f32` rectangle. 550 | #[inline] 551 | pub fn to_f32(&self) -> Rect { 552 | self.cast() 553 | } 554 | 555 | /// Cast into an `f64` rectangle. 556 | #[inline] 557 | pub fn to_f64(&self) -> Rect { 558 | self.cast() 559 | } 560 | 561 | /// Cast into an `usize` rectangle, truncating decimals if any. 562 | /// 563 | /// When casting from floating point rectangles, it is worth considering whether 564 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to 565 | /// obtain the desired conversion behavior. 566 | #[inline] 567 | pub fn to_usize(&self) -> Rect { 568 | self.cast() 569 | } 570 | 571 | /// Cast into an `u32` rectangle, truncating decimals if any. 572 | /// 573 | /// When casting from floating point rectangles, it is worth considering whether 574 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to 575 | /// obtain the desired conversion behavior. 576 | #[inline] 577 | pub fn to_u32(&self) -> Rect { 578 | self.cast() 579 | } 580 | 581 | /// Cast into an `u64` rectangle, truncating decimals if any. 582 | /// 583 | /// When casting from floating point rectangles, it is worth considering whether 584 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to 585 | /// obtain the desired conversion behavior. 586 | #[inline] 587 | pub fn to_u64(&self) -> Rect { 588 | self.cast() 589 | } 590 | 591 | /// Cast into an `i32` rectangle, truncating decimals if any. 592 | /// 593 | /// When casting from floating point rectangles, it is worth considering whether 594 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to 595 | /// obtain the desired conversion behavior. 596 | #[inline] 597 | pub fn to_i32(&self) -> Rect { 598 | self.cast() 599 | } 600 | 601 | /// Cast into an `i64` rectangle, truncating decimals if any. 602 | /// 603 | /// When casting from floating point rectangles, it is worth considering whether 604 | /// to `round()`, `round_in()` or `round_out()` before the cast in order to 605 | /// obtain the desired conversion behavior. 606 | #[inline] 607 | pub fn to_i64(&self) -> Rect { 608 | self.cast() 609 | } 610 | } 611 | 612 | impl Rect { 613 | /// Returns `true` if all members are finite. 614 | #[inline] 615 | pub fn is_finite(self) -> bool { 616 | self.origin.is_finite() && self.size.is_finite() 617 | } 618 | } 619 | 620 | impl + Sub, U> Rect { 621 | /// Return a rectangle with edges rounded to integer coordinates, such that 622 | /// the returned rectangle has the same set of pixel centers as the original 623 | /// one. 624 | /// Edges at offset 0.5 round up. 625 | /// Suitable for most places where integral device coordinates 626 | /// are needed, but note that any translation should be applied first to 627 | /// avoid pixel rounding errors. 628 | /// Note that this is *not* rounding to nearest integer if the values are negative. 629 | /// They are always rounding as floor(n + 0.5). 630 | /// 631 | /// # Usage notes 632 | /// Note, that when using with floating-point `T` types that method can significantly 633 | /// lose precision for large values, so if you need to call this method very often it 634 | /// is better to use [`Box2D`]. 635 | #[must_use] 636 | pub fn round(&self) -> Self { 637 | self.to_box2d().round().to_rect() 638 | } 639 | 640 | /// Return a rectangle with edges rounded to integer coordinates, such that 641 | /// the original rectangle contains the resulting rectangle. 642 | /// 643 | /// # Usage notes 644 | /// Note, that when using with floating-point `T` types that method can significantly 645 | /// lose precision for large values, so if you need to call this method very often it 646 | /// is better to use [`Box2D`]. 647 | #[must_use] 648 | pub fn round_in(&self) -> Self { 649 | self.to_box2d().round_in().to_rect() 650 | } 651 | 652 | /// Return a rectangle with edges rounded to integer coordinates, such that 653 | /// the original rectangle is contained in the resulting rectangle. 654 | /// 655 | /// # Usage notes 656 | /// Note, that when using with floating-point `T` types that method can significantly 657 | /// lose precision for large values, so if you need to call this method very often it 658 | /// is better to use [`Box2D`]. 659 | #[must_use] 660 | pub fn round_out(&self) -> Self { 661 | self.to_box2d().round_out().to_rect() 662 | } 663 | } 664 | 665 | impl From> for Rect 666 | where 667 | T: Zero, 668 | { 669 | fn from(size: Size2D) -> Self { 670 | Self::from_size(size) 671 | } 672 | } 673 | 674 | /// Shorthand for `Rect::new(Point2D::new(x, y), Size2D::new(w, h))`. 675 | pub const fn rect(x: T, y: T, w: T, h: T) -> Rect { 676 | Rect::new(Point2D::new(x, y), Size2D::new(w, h)) 677 | } 678 | 679 | #[cfg(test)] 680 | mod tests { 681 | use crate::default::{Point2D, Rect, Size2D}; 682 | use crate::side_offsets::SideOffsets2D; 683 | use crate::{point2, rect, size2, vec2}; 684 | 685 | #[test] 686 | fn test_translate() { 687 | let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32)); 688 | let pp = p.translate(vec2(10, 15)); 689 | 690 | assert!(pp.size.width == 50); 691 | assert!(pp.size.height == 40); 692 | assert!(pp.origin.x == 10); 693 | assert!(pp.origin.y == 15); 694 | 695 | let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40)); 696 | let rr = r.translate(vec2(0, -10)); 697 | 698 | assert!(rr.size.width == 50); 699 | assert!(rr.size.height == 40); 700 | assert!(rr.origin.x == -10); 701 | assert!(rr.origin.y == -15); 702 | } 703 | 704 | #[test] 705 | fn test_union() { 706 | let p = Rect::new(Point2D::new(0, 0), Size2D::new(50, 40)); 707 | let q = Rect::new(Point2D::new(20, 20), Size2D::new(5, 5)); 708 | let r = Rect::new(Point2D::new(-15, -30), Size2D::new(200, 15)); 709 | let s = Rect::new(Point2D::new(20, -15), Size2D::new(250, 200)); 710 | 711 | let pq = p.union(&q); 712 | assert!(pq.origin == Point2D::new(0, 0)); 713 | assert!(pq.size == Size2D::new(50, 40)); 714 | 715 | let pr = p.union(&r); 716 | assert!(pr.origin == Point2D::new(-15, -30)); 717 | assert!(pr.size == Size2D::new(200, 70)); 718 | 719 | let ps = p.union(&s); 720 | assert!(ps.origin == Point2D::new(0, -15)); 721 | assert!(ps.size == Size2D::new(270, 200)); 722 | } 723 | 724 | #[test] 725 | fn test_intersection() { 726 | let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20)); 727 | let q = Rect::new(Point2D::new(5, 15), Size2D::new(10, 10)); 728 | let r = Rect::new(Point2D::new(-5, -5), Size2D::new(8, 8)); 729 | 730 | let pq = p.intersection(&q); 731 | assert!(pq.is_some()); 732 | let pq = pq.unwrap(); 733 | assert!(pq.origin == Point2D::new(5, 15)); 734 | assert!(pq.size == Size2D::new(5, 5)); 735 | 736 | let pr = p.intersection(&r); 737 | assert!(pr.is_some()); 738 | let pr = pr.unwrap(); 739 | assert!(pr.origin == Point2D::new(0, 0)); 740 | assert!(pr.size == Size2D::new(3, 3)); 741 | 742 | let qr = q.intersection(&r); 743 | assert!(qr.is_none()); 744 | } 745 | 746 | #[test] 747 | fn test_intersection_overflow() { 748 | // test some scenarios where the intersection can overflow but 749 | // the min_x() and max_x() don't. Gecko currently fails these cases 750 | let p = Rect::new(Point2D::new(-2147483648, -2147483648), Size2D::new(0, 0)); 751 | let q = Rect::new( 752 | Point2D::new(2136893440, 2136893440), 753 | Size2D::new(279552, 279552), 754 | ); 755 | let r = Rect::new(Point2D::new(-2147483648, -2147483648), Size2D::new(1, 1)); 756 | 757 | assert!(p.is_empty()); 758 | let pq = p.intersection(&q); 759 | assert!(pq.is_none()); 760 | 761 | let qr = q.intersection(&r); 762 | assert!(qr.is_none()); 763 | } 764 | 765 | #[test] 766 | fn test_contains() { 767 | let r = Rect::new(Point2D::new(-20, 15), Size2D::new(100, 200)); 768 | 769 | assert!(r.contains(Point2D::new(0, 50))); 770 | assert!(r.contains(Point2D::new(-10, 200))); 771 | 772 | // The `contains` method is inclusive of the top/left edges, but not the 773 | // bottom/right edges. 774 | assert!(r.contains(Point2D::new(-20, 15))); 775 | assert!(!r.contains(Point2D::new(80, 15))); 776 | assert!(!r.contains(Point2D::new(80, 215))); 777 | assert!(!r.contains(Point2D::new(-20, 215))); 778 | 779 | // Points beyond the top-left corner. 780 | assert!(!r.contains(Point2D::new(-25, 15))); 781 | assert!(!r.contains(Point2D::new(-15, 10))); 782 | 783 | // Points beyond the top-right corner. 784 | assert!(!r.contains(Point2D::new(85, 20))); 785 | assert!(!r.contains(Point2D::new(75, 10))); 786 | 787 | // Points beyond the bottom-right corner. 788 | assert!(!r.contains(Point2D::new(85, 210))); 789 | assert!(!r.contains(Point2D::new(75, 220))); 790 | 791 | // Points beyond the bottom-left corner. 792 | assert!(!r.contains(Point2D::new(-25, 210))); 793 | assert!(!r.contains(Point2D::new(-15, 220))); 794 | 795 | let r = Rect::new(Point2D::new(-20.0, 15.0), Size2D::new(100.0, 200.0)); 796 | assert!(r.contains_rect(&r)); 797 | assert!(!r.contains_rect(&r.translate(vec2(0.1, 0.0)))); 798 | assert!(!r.contains_rect(&r.translate(vec2(-0.1, 0.0)))); 799 | assert!(!r.contains_rect(&r.translate(vec2(0.0, 0.1)))); 800 | assert!(!r.contains_rect(&r.translate(vec2(0.0, -0.1)))); 801 | // Empty rectangles are always considered as contained in other rectangles, 802 | // even if their origin is not. 803 | let p = Point2D::new(1.0, 1.0); 804 | assert!(!r.contains(p)); 805 | assert!(r.contains_rect(&Rect::new(p, Size2D::zero()))); 806 | } 807 | 808 | #[test] 809 | fn test_scale() { 810 | let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32)); 811 | let pp = p.scale(10, 15); 812 | 813 | assert!(pp.size.width == 500); 814 | assert!(pp.size.height == 600); 815 | assert!(pp.origin.x == 0); 816 | assert!(pp.origin.y == 0); 817 | 818 | let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40)); 819 | let rr = r.scale(1, 20); 820 | 821 | assert!(rr.size.width == 50); 822 | assert!(rr.size.height == 800); 823 | assert!(rr.origin.x == -10); 824 | assert!(rr.origin.y == -100); 825 | } 826 | 827 | #[test] 828 | fn test_inflate() { 829 | let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 10)); 830 | let pp = p.inflate(10, 20); 831 | 832 | assert!(pp.size.width == 30); 833 | assert!(pp.size.height == 50); 834 | assert!(pp.origin.x == -10); 835 | assert!(pp.origin.y == -20); 836 | 837 | let r = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20)); 838 | let rr = r.inflate(-2, -5); 839 | 840 | assert!(rr.size.width == 6); 841 | assert!(rr.size.height == 10); 842 | assert!(rr.origin.x == 2); 843 | assert!(rr.origin.y == 5); 844 | } 845 | 846 | #[test] 847 | fn test_inner_outer_rect() { 848 | let inner_rect = Rect::new(point2(20, 40), size2(80, 100)); 849 | let offsets = SideOffsets2D::new(20, 10, 10, 10); 850 | let outer_rect = inner_rect.outer_rect(offsets); 851 | assert_eq!(outer_rect.origin.x, 10); 852 | assert_eq!(outer_rect.origin.y, 20); 853 | assert_eq!(outer_rect.size.width, 100); 854 | assert_eq!(outer_rect.size.height, 130); 855 | assert_eq!(outer_rect.inner_rect(offsets), inner_rect); 856 | } 857 | 858 | #[test] 859 | fn test_min_max_x_y() { 860 | let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32)); 861 | assert!(p.max_y() == 40); 862 | assert!(p.min_y() == 0); 863 | assert!(p.max_x() == 50); 864 | assert!(p.min_x() == 0); 865 | 866 | let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40)); 867 | assert!(r.max_y() == 35); 868 | assert!(r.min_y() == -5); 869 | assert!(r.max_x() == 40); 870 | assert!(r.min_x() == -10); 871 | } 872 | 873 | #[test] 874 | fn test_width_height() { 875 | let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40)); 876 | assert!(r.width() == 50); 877 | assert!(r.height() == 40); 878 | } 879 | 880 | #[test] 881 | fn test_is_empty() { 882 | assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 0u32)).is_empty()); 883 | assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(10u32, 0u32)).is_empty()); 884 | assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 10u32)).is_empty()); 885 | assert!(!Rect::new(Point2D::new(0u32, 0u32), Size2D::new(1u32, 1u32)).is_empty()); 886 | assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 0u32)).is_empty()); 887 | assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(10u32, 0u32)).is_empty()); 888 | assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 10u32)).is_empty()); 889 | assert!(!Rect::new(Point2D::new(10u32, 10u32), Size2D::new(1u32, 1u32)).is_empty()); 890 | } 891 | 892 | #[test] 893 | fn test_round() { 894 | let mut x = -2.0; 895 | let mut y = -2.0; 896 | let mut w = -2.0; 897 | let mut h = -2.0; 898 | while x < 2.0 { 899 | while y < 2.0 { 900 | while w < 2.0 { 901 | while h < 2.0 { 902 | let rect = Rect::new(Point2D::new(x, y), Size2D::new(w, h)); 903 | 904 | assert!(rect.contains_rect(&rect.round_in())); 905 | assert!(rect.round_in().inflate(1.0, 1.0).contains_rect(&rect)); 906 | 907 | assert!(rect.round_out().contains_rect(&rect)); 908 | assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round_out())); 909 | 910 | assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round())); 911 | assert!(rect.round().inflate(1.0, 1.0).contains_rect(&rect)); 912 | 913 | h += 0.1; 914 | } 915 | w += 0.1; 916 | } 917 | y += 0.1; 918 | } 919 | x += 0.1; 920 | } 921 | } 922 | 923 | #[test] 924 | fn test_center() { 925 | let r: Rect = rect(-2, 5, 4, 10); 926 | assert_eq!(r.center(), point2(0, 10)); 927 | 928 | let r: Rect = rect(1.0, 2.0, 3.0, 4.0); 929 | assert_eq!(r.center(), point2(2.5, 4.0)); 930 | } 931 | 932 | #[test] 933 | fn test_nan() { 934 | let r1: Rect = rect(-2.0, 5.0, 4.0, std::f32::NAN); 935 | let r2: Rect = rect(std::f32::NAN, -1.0, 3.0, 10.0); 936 | 937 | assert_eq!(r1.intersection(&r2), None); 938 | } 939 | } 940 | -------------------------------------------------------------------------------- /src/rigid.rs: -------------------------------------------------------------------------------- 1 | //! All matrix multiplication in this module is in row-vector notation, 2 | //! i.e. a vector `v` is transformed with `v * T`, and if you want to apply `T1` 3 | //! before `T2` you use `T1 * T2` 4 | 5 | use crate::approxeq::ApproxEq; 6 | use crate::trig::Trig; 7 | use crate::{Rotation3D, Transform3D, UnknownUnit, Vector3D}; 8 | 9 | use core::{fmt, hash}; 10 | 11 | #[cfg(feature = "bytemuck")] 12 | use bytemuck::{Pod, Zeroable}; 13 | #[cfg(feature = "malloc_size_of")] 14 | use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 15 | use num_traits::real::Real; 16 | #[cfg(feature = "serde")] 17 | use serde::{Deserialize, Serialize}; 18 | 19 | /// A rigid transformation. All lengths are preserved under such a transformation. 20 | /// 21 | /// 22 | /// Internally, this is a rotation and a translation, with the rotation 23 | /// applied first (i.e. `Rotation * Translation`, in row-vector notation) 24 | /// 25 | /// This can be more efficient to use over full matrices, especially if you 26 | /// have to deal with the decomposed quantities often. 27 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 28 | #[repr(C)] 29 | pub struct RigidTransform3D { 30 | pub rotation: Rotation3D, 31 | pub translation: Vector3D, 32 | } 33 | 34 | impl RigidTransform3D { 35 | /// Construct a new rigid transformation, where the `rotation` applies first 36 | #[inline] 37 | pub const fn new(rotation: Rotation3D, translation: Vector3D) -> Self { 38 | Self { 39 | rotation, 40 | translation, 41 | } 42 | } 43 | } 44 | 45 | impl RigidTransform3D { 46 | pub fn cast_unit(&self) -> RigidTransform3D { 47 | RigidTransform3D { 48 | rotation: self.rotation.cast_unit(), 49 | translation: self.translation.cast_unit(), 50 | } 51 | } 52 | } 53 | 54 | impl, Src, Dst> RigidTransform3D { 55 | /// Construct an identity transform 56 | #[inline] 57 | pub fn identity() -> Self { 58 | Self { 59 | rotation: Rotation3D::identity(), 60 | translation: Vector3D::zero(), 61 | } 62 | } 63 | 64 | /// Construct a new rigid transformation, where the `translation` applies first 65 | #[inline] 66 | pub fn new_from_reversed( 67 | translation: Vector3D, 68 | rotation: Rotation3D, 69 | ) -> Self { 70 | // T * R 71 | // = (R * R^-1) * T * R 72 | // = R * (R^-1 * T * R) 73 | // = R * T' 74 | // 75 | // T' = (R^-1 * T * R) is also a translation matrix 76 | // It is equivalent to the translation matrix obtained by rotating the 77 | // translation by R 78 | 79 | let translation = rotation.transform_vector3d(translation); 80 | Self { 81 | rotation, 82 | translation, 83 | } 84 | } 85 | 86 | #[inline] 87 | pub fn from_rotation(rotation: Rotation3D) -> Self { 88 | Self { 89 | rotation, 90 | translation: Vector3D::zero(), 91 | } 92 | } 93 | 94 | #[inline] 95 | pub fn from_translation(translation: Vector3D) -> Self { 96 | Self { 97 | translation, 98 | rotation: Rotation3D::identity(), 99 | } 100 | } 101 | 102 | /// Decompose this into a translation and an rotation to be applied in the opposite order 103 | /// 104 | /// i.e., the translation is applied _first_ 105 | #[inline] 106 | pub fn decompose_reversed(&self) -> (Vector3D, Rotation3D) { 107 | // self = R * T 108 | // = R * T * (R^-1 * R) 109 | // = (R * T * R^-1) * R) 110 | // = T' * R 111 | // 112 | // T' = (R^ * T * R^-1) is T rotated by R^-1 113 | 114 | let translation = self.rotation.inverse().transform_vector3d(self.translation); 115 | (translation, self.rotation) 116 | } 117 | 118 | /// Returns the multiplication of the two transforms such that 119 | /// other's transformation applies after self's transformation. 120 | /// 121 | /// i.e., this produces `self * other` in row-vector notation 122 | #[inline] 123 | pub fn then( 124 | &self, 125 | other: &RigidTransform3D, 126 | ) -> RigidTransform3D { 127 | // self = R1 * T1 128 | // other = R2 * T2 129 | // result = R1 * T1 * R2 * T2 130 | // = R1 * (R2 * R2^-1) * T1 * R2 * T2 131 | // = (R1 * R2) * (R2^-1 * T1 * R2) * T2 132 | // = R' * T' * T2 133 | // = R' * T'' 134 | // 135 | // (R2^-1 * T2 * R2^) = T' = T2 rotated by R2 136 | // R1 * R2 = R' 137 | // T' * T2 = T'' = vector addition of translations T2 and T' 138 | 139 | let t_prime = other.rotation.transform_vector3d(self.translation); 140 | let r_prime = self.rotation.then(&other.rotation); 141 | let t_prime2 = t_prime + other.translation; 142 | RigidTransform3D { 143 | rotation: r_prime, 144 | translation: t_prime2, 145 | } 146 | } 147 | 148 | /// Inverts the transformation 149 | #[inline] 150 | pub fn inverse(&self) -> RigidTransform3D { 151 | // result = (self)^-1 152 | // = (R * T)^-1 153 | // = T^-1 * R^-1 154 | // = (R^-1 * R) * T^-1 * R^-1 155 | // = R^-1 * (R * T^-1 * R^-1) 156 | // = R' * T' 157 | // 158 | // T' = (R * T^-1 * R^-1) = (-T) rotated by R^-1 159 | // R' = R^-1 160 | // 161 | // An easier way of writing this is to use new_from_reversed() with R^-1 and T^-1 162 | 163 | RigidTransform3D::new_from_reversed(-self.translation, self.rotation.inverse()) 164 | } 165 | 166 | pub fn to_transform(&self) -> Transform3D 167 | where 168 | T: Trig, 169 | { 170 | self.rotation 171 | .to_transform() 172 | .then(&self.translation.to_transform()) 173 | } 174 | 175 | /// Drop the units, preserving only the numeric value. 176 | #[inline] 177 | pub fn to_untyped(&self) -> RigidTransform3D { 178 | RigidTransform3D { 179 | rotation: self.rotation.to_untyped(), 180 | translation: self.translation.to_untyped(), 181 | } 182 | } 183 | 184 | /// Tag a unitless value with units. 185 | #[inline] 186 | pub fn from_untyped(transform: &RigidTransform3D) -> Self { 187 | RigidTransform3D { 188 | rotation: Rotation3D::from_untyped(&transform.rotation), 189 | translation: Vector3D::from_untyped(transform.translation), 190 | } 191 | } 192 | } 193 | 194 | impl fmt::Debug for RigidTransform3D { 195 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 196 | f.debug_struct("RigidTransform3D") 197 | .field("rotation", &self.rotation) 198 | .field("translation", &self.translation) 199 | .finish() 200 | } 201 | } 202 | 203 | impl PartialEq for RigidTransform3D { 204 | fn eq(&self, other: &Self) -> bool { 205 | self.rotation == other.rotation && self.translation == other.translation 206 | } 207 | } 208 | impl Eq for RigidTransform3D {} 209 | 210 | impl hash::Hash for RigidTransform3D { 211 | fn hash(&self, state: &mut H) { 212 | self.rotation.hash(state); 213 | self.translation.hash(state); 214 | } 215 | } 216 | 217 | impl Copy for RigidTransform3D {} 218 | 219 | impl Clone for RigidTransform3D { 220 | fn clone(&self) -> Self { 221 | RigidTransform3D { 222 | rotation: self.rotation.clone(), 223 | translation: self.translation.clone(), 224 | } 225 | } 226 | } 227 | 228 | #[cfg(feature = "arbitrary")] 229 | impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for RigidTransform3D 230 | where 231 | T: arbitrary::Arbitrary<'a>, 232 | { 233 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 234 | Ok(RigidTransform3D { 235 | rotation: arbitrary::Arbitrary::arbitrary(u)?, 236 | translation: arbitrary::Arbitrary::arbitrary(u)?, 237 | }) 238 | } 239 | } 240 | 241 | #[cfg(feature = "bytemuck")] 242 | unsafe impl Zeroable for RigidTransform3D {} 243 | 244 | #[cfg(feature = "bytemuck")] 245 | unsafe impl Pod for RigidTransform3D {} 246 | 247 | #[cfg(feature = "malloc_size_of")] 248 | impl MallocSizeOf for RigidTransform3D { 249 | fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { 250 | self.rotation.size_of(ops) + self.translation.size_of(ops) 251 | } 252 | } 253 | 254 | impl, Src, Dst> From> 255 | for RigidTransform3D 256 | { 257 | fn from(rot: Rotation3D) -> Self { 258 | Self::from_rotation(rot) 259 | } 260 | } 261 | 262 | impl, Src, Dst> From> for RigidTransform3D { 263 | fn from(t: Vector3D) -> Self { 264 | Self::from_translation(t) 265 | } 266 | } 267 | 268 | #[cfg(test)] 269 | mod test { 270 | use super::RigidTransform3D; 271 | use crate::default::{Rotation3D, Transform3D, Vector3D}; 272 | 273 | #[test] 274 | fn test_rigid_construction() { 275 | let translation = Vector3D::new(12.1, 17.8, -5.5); 276 | let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3); 277 | 278 | let rigid = RigidTransform3D::new(rotation, translation); 279 | assert!(rigid 280 | .to_transform() 281 | .approx_eq(&rotation.to_transform().then(&translation.to_transform()))); 282 | 283 | let rigid = RigidTransform3D::new_from_reversed(translation, rotation); 284 | assert!(rigid 285 | .to_transform() 286 | .approx_eq(&translation.to_transform().then(&rotation.to_transform()))); 287 | } 288 | 289 | #[test] 290 | fn test_rigid_decomposition() { 291 | let translation = Vector3D::new(12.1, 17.8, -5.5); 292 | let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3); 293 | 294 | let rigid = RigidTransform3D::new(rotation, translation); 295 | let (t2, r2) = rigid.decompose_reversed(); 296 | assert!(rigid 297 | .to_transform() 298 | .approx_eq(&t2.to_transform().then(&r2.to_transform()))); 299 | } 300 | 301 | #[test] 302 | fn test_rigid_inverse() { 303 | let translation = Vector3D::new(12.1, 17.8, -5.5); 304 | let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3); 305 | 306 | let rigid = RigidTransform3D::new(rotation, translation); 307 | let inverse = rigid.inverse(); 308 | assert!(rigid 309 | .then(&inverse) 310 | .to_transform() 311 | .approx_eq(&Transform3D::identity())); 312 | assert!(inverse 313 | .to_transform() 314 | .approx_eq(&rigid.to_transform().inverse().unwrap())); 315 | } 316 | 317 | #[test] 318 | fn test_rigid_multiply() { 319 | let translation = Vector3D::new(12.1, 17.8, -5.5); 320 | let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3); 321 | let translation2 = Vector3D::new(9.3, -3.9, 1.1); 322 | let rotation2 = Rotation3D::unit_quaternion(0.1, 0.2, 0.3, -0.4); 323 | let rigid = RigidTransform3D::new(rotation, translation); 324 | let rigid2 = RigidTransform3D::new(rotation2, translation2); 325 | 326 | assert!(rigid 327 | .then(&rigid2) 328 | .to_transform() 329 | .approx_eq(&rigid.to_transform().then(&rigid2.to_transform()))); 330 | assert!(rigid2 331 | .then(&rigid) 332 | .to_transform() 333 | .approx_eq(&rigid2.to_transform().then(&rigid.to_transform()))); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/scale.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | //! A type-checked scaling factor between units. 10 | 11 | use crate::num::One; 12 | 13 | use crate::approxord::{max, min}; 14 | use crate::{Box2D, Box3D, Point2D, Point3D, Rect, Size2D, Vector2D}; 15 | 16 | use core::cmp::Ordering; 17 | use core::fmt; 18 | use core::hash::{Hash, Hasher}; 19 | use core::marker::PhantomData; 20 | use core::ops::{Add, Div, Mul, Sub}; 21 | 22 | #[cfg(feature = "bytemuck")] 23 | use bytemuck::{Pod, Zeroable}; 24 | #[cfg(feature = "malloc_size_of")] 25 | use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 26 | use num_traits::NumCast; 27 | #[cfg(feature = "serde")] 28 | use serde::{Deserialize, Serialize}; 29 | 30 | /// A scaling factor between two different units of measurement. 31 | /// 32 | /// This is effectively a type-safe float, intended to be used in combination with other types like 33 | /// `length::Length` to enforce conversion between systems of measurement at compile time. 34 | /// 35 | /// `Src` and `Dst` represent the units before and after multiplying a value by a `Scale`. They 36 | /// may be types without values, such as empty enums. For example: 37 | /// 38 | /// ```rust 39 | /// use euclid::Scale; 40 | /// use euclid::Length; 41 | /// enum Mm {}; 42 | /// enum Inch {}; 43 | /// 44 | /// let mm_per_inch: Scale = Scale::new(25.4); 45 | /// 46 | /// let one_foot: Length = Length::new(12.0); 47 | /// let one_foot_in_mm: Length = one_foot * mm_per_inch; 48 | /// ``` 49 | #[repr(C)] 50 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 51 | #[cfg_attr( 52 | feature = "serde", 53 | serde(bound( 54 | serialize = "T: serde::Serialize", 55 | deserialize = "T: serde::Deserialize<'de>" 56 | )) 57 | )] 58 | pub struct Scale(pub T, #[doc(hidden)] pub PhantomData<(Src, Dst)>); 59 | 60 | impl Scale { 61 | #[inline] 62 | pub const fn new(x: T) -> Self { 63 | Scale(x, PhantomData) 64 | } 65 | 66 | /// Creates an identity scale (1.0). 67 | #[inline] 68 | pub fn identity() -> Self 69 | where 70 | T: One, 71 | { 72 | Scale::new(T::one()) 73 | } 74 | 75 | /// Returns the given point transformed by this scale. 76 | /// 77 | /// # Example 78 | /// 79 | /// ```rust 80 | /// use euclid::{Scale, point2}; 81 | /// enum Mm {}; 82 | /// enum Cm {}; 83 | /// 84 | /// let to_mm: Scale = Scale::new(10); 85 | /// 86 | /// assert_eq!(to_mm.transform_point(point2(42, -42)), point2(420, -420)); 87 | /// ``` 88 | #[inline] 89 | pub fn transform_point(self, point: Point2D) -> Point2D 90 | where 91 | T: Copy + Mul, 92 | { 93 | Point2D::new(point.x * self.0, point.y * self.0) 94 | } 95 | 96 | /// Returns the given point transformed by this scale. 97 | #[inline] 98 | pub fn transform_point3d(self, point: Point3D) -> Point3D 99 | where 100 | T: Copy + Mul, 101 | { 102 | Point3D::new(point.x * self.0, point.y * self.0, point.z * self.0) 103 | } 104 | 105 | /// Returns the given vector transformed by this scale. 106 | /// 107 | /// # Example 108 | /// 109 | /// ```rust 110 | /// use euclid::{Scale, vec2}; 111 | /// enum Mm {}; 112 | /// enum Cm {}; 113 | /// 114 | /// let to_mm: Scale = Scale::new(10); 115 | /// 116 | /// assert_eq!(to_mm.transform_vector(vec2(42, -42)), vec2(420, -420)); 117 | /// ``` 118 | #[inline] 119 | pub fn transform_vector(self, vec: Vector2D) -> Vector2D 120 | where 121 | T: Copy + Mul, 122 | { 123 | Vector2D::new(vec.x * self.0, vec.y * self.0) 124 | } 125 | 126 | /// Returns the given size transformed by this scale. 127 | /// 128 | /// # Example 129 | /// 130 | /// ```rust 131 | /// use euclid::{Scale, size2}; 132 | /// enum Mm {}; 133 | /// enum Cm {}; 134 | /// 135 | /// let to_mm: Scale = Scale::new(10); 136 | /// 137 | /// assert_eq!(to_mm.transform_size(size2(42, -42)), size2(420, -420)); 138 | /// ``` 139 | #[inline] 140 | pub fn transform_size(self, size: Size2D) -> Size2D 141 | where 142 | T: Copy + Mul, 143 | { 144 | Size2D::new(size.width * self.0, size.height * self.0) 145 | } 146 | 147 | /// Returns the given rect transformed by this scale. 148 | /// 149 | /// # Example 150 | /// 151 | /// ```rust 152 | /// use euclid::{Scale, rect}; 153 | /// enum Mm {}; 154 | /// enum Cm {}; 155 | /// 156 | /// let to_mm: Scale = Scale::new(10); 157 | /// 158 | /// assert_eq!(to_mm.transform_rect(&rect(1, 2, 42, -42)), rect(10, 20, 420, -420)); 159 | /// ``` 160 | #[inline] 161 | pub fn transform_rect(self, rect: &Rect) -> Rect 162 | where 163 | T: Copy + Mul, 164 | { 165 | Rect::new( 166 | self.transform_point(rect.origin), 167 | self.transform_size(rect.size), 168 | ) 169 | } 170 | 171 | /// Returns the given box transformed by this scale. 172 | #[inline] 173 | pub fn transform_box2d(self, b: &Box2D) -> Box2D 174 | where 175 | T: Copy + Mul, 176 | { 177 | Box2D { 178 | min: self.transform_point(b.min), 179 | max: self.transform_point(b.max), 180 | } 181 | } 182 | 183 | /// Returns the given box transformed by this scale. 184 | #[inline] 185 | pub fn transform_box3d(self, b: &Box3D) -> Box3D 186 | where 187 | T: Copy + Mul, 188 | { 189 | Box3D { 190 | min: self.transform_point3d(b.min), 191 | max: self.transform_point3d(b.max), 192 | } 193 | } 194 | 195 | /// Returns `true` if this scale has no effect. 196 | /// 197 | /// # Example 198 | /// 199 | /// ```rust 200 | /// use euclid::Scale; 201 | /// use euclid::num::One; 202 | /// enum Mm {}; 203 | /// enum Cm {}; 204 | /// 205 | /// let cm_per_mm: Scale = Scale::new(0.1); 206 | /// let mm_per_mm: Scale = Scale::new(1.0); 207 | /// 208 | /// assert_eq!(cm_per_mm.is_identity(), false); 209 | /// assert_eq!(mm_per_mm.is_identity(), true); 210 | /// assert_eq!(mm_per_mm, Scale::one()); 211 | /// ``` 212 | #[inline] 213 | pub fn is_identity(self) -> bool 214 | where 215 | T: PartialEq + One, 216 | { 217 | self.0 == T::one() 218 | } 219 | 220 | /// Returns the underlying scalar scale factor. 221 | #[inline] 222 | pub fn get(self) -> T { 223 | self.0 224 | } 225 | 226 | /// The inverse Scale (1.0 / self). 227 | /// 228 | /// # Example 229 | /// 230 | /// ```rust 231 | /// use euclid::Scale; 232 | /// enum Mm {}; 233 | /// enum Cm {}; 234 | /// 235 | /// let cm_per_mm: Scale = Scale::new(0.1); 236 | /// 237 | /// assert_eq!(cm_per_mm.inverse(), Scale::new(10.0)); 238 | /// ``` 239 | pub fn inverse(self) -> Scale 240 | where 241 | T: One + Div, 242 | { 243 | let one: T = One::one(); 244 | Scale::new(one / self.0) 245 | } 246 | } 247 | 248 | impl Scale { 249 | #[inline] 250 | pub fn min(self, other: Self) -> Self { 251 | Self::new(min(self.0, other.0)) 252 | } 253 | 254 | #[inline] 255 | pub fn max(self, other: Self) -> Self { 256 | Self::new(max(self.0, other.0)) 257 | } 258 | 259 | /// Returns the point each component of which clamped by corresponding 260 | /// components of `start` and `end`. 261 | /// 262 | /// Shortcut for `self.max(start).min(end)`. 263 | #[inline] 264 | pub fn clamp(self, start: Self, end: Self) -> Self 265 | where 266 | T: Copy, 267 | { 268 | self.max(start).min(end) 269 | } 270 | } 271 | 272 | impl Scale { 273 | /// Cast from one numeric representation to another, preserving the units. 274 | /// 275 | /// # Panics 276 | /// 277 | /// If the source value cannot be represented by the target type `NewT`, then 278 | /// method panics. Use `try_cast` if that must be case. 279 | /// 280 | /// # Example 281 | /// 282 | /// ```rust 283 | /// use euclid::Scale; 284 | /// enum Mm {}; 285 | /// enum Cm {}; 286 | /// 287 | /// let to_mm: Scale = Scale::new(10); 288 | /// 289 | /// assert_eq!(to_mm.cast::(), Scale::new(10.0)); 290 | /// ``` 291 | /// That conversion will panic, because `i32` not enough to store such big numbers: 292 | /// ```rust,should_panic 293 | /// use euclid::Scale; 294 | /// enum Mm {};// millimeter = 10^-2 meters 295 | /// enum Em {};// exameter = 10^18 meters 296 | /// 297 | /// // Panics 298 | /// let to_em: Scale = Scale::new(10e20).cast(); 299 | /// ``` 300 | #[inline] 301 | pub fn cast(self) -> Scale { 302 | self.try_cast().unwrap() 303 | } 304 | 305 | /// Fallible cast from one numeric representation to another, preserving the units. 306 | /// If the source value cannot be represented by the target type `NewT`, then `None` 307 | /// is returned. 308 | /// 309 | /// # Example 310 | /// 311 | /// ```rust 312 | /// use euclid::Scale; 313 | /// enum Mm {}; 314 | /// enum Cm {}; 315 | /// enum Em {};// Exameter = 10^18 meters 316 | /// 317 | /// let to_mm: Scale = Scale::new(10); 318 | /// let to_em: Scale = Scale::new(10e20); 319 | /// 320 | /// assert_eq!(to_mm.try_cast::(), Some(Scale::new(10.0))); 321 | /// // Integer to small to store that number 322 | /// assert_eq!(to_em.try_cast::(), None); 323 | /// ``` 324 | pub fn try_cast(self) -> Option> { 325 | NumCast::from(self.0).map(Scale::new) 326 | } 327 | } 328 | 329 | #[cfg(feature = "arbitrary")] 330 | impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Scale 331 | where 332 | T: arbitrary::Arbitrary<'a>, 333 | { 334 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 335 | Ok(Scale::new(arbitrary::Arbitrary::arbitrary(u)?)) 336 | } 337 | } 338 | 339 | #[cfg(feature = "bytemuck")] 340 | unsafe impl Zeroable for Scale {} 341 | 342 | #[cfg(feature = "bytemuck")] 343 | unsafe impl Pod for Scale {} 344 | 345 | #[cfg(feature = "malloc_size_of")] 346 | impl MallocSizeOf for Scale { 347 | fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { 348 | self.0.size_of(ops) 349 | } 350 | } 351 | 352 | // scale0 * scale1 353 | // (A,B) * (B,C) = (A,C) 354 | impl Mul> for Scale { 355 | type Output = Scale; 356 | 357 | #[inline] 358 | fn mul(self, other: Scale) -> Self::Output { 359 | Scale::new(self.0 * other.0) 360 | } 361 | } 362 | 363 | // scale0 + scale1 364 | impl Add for Scale { 365 | type Output = Scale; 366 | 367 | #[inline] 368 | fn add(self, other: Scale) -> Self::Output { 369 | Scale::new(self.0 + other.0) 370 | } 371 | } 372 | 373 | // scale0 - scale1 374 | impl Sub for Scale { 375 | type Output = Scale; 376 | 377 | #[inline] 378 | fn sub(self, other: Scale) -> Self::Output { 379 | Scale::new(self.0 - other.0) 380 | } 381 | } 382 | 383 | // FIXME: Switch to `derive(PartialEq, Clone)` after this Rust issue is fixed: 384 | // https://github.com/rust-lang/rust/issues/26925 385 | 386 | impl PartialEq for Scale { 387 | fn eq(&self, other: &Scale) -> bool { 388 | self.0 == other.0 389 | } 390 | } 391 | 392 | impl Eq for Scale {} 393 | 394 | impl PartialOrd for Scale { 395 | fn partial_cmp(&self, other: &Self) -> Option { 396 | self.0.partial_cmp(&other.0) 397 | } 398 | } 399 | 400 | impl Ord for Scale { 401 | fn cmp(&self, other: &Self) -> Ordering { 402 | self.0.cmp(&other.0) 403 | } 404 | } 405 | 406 | impl Clone for Scale { 407 | fn clone(&self) -> Scale { 408 | Scale::new(self.0.clone()) 409 | } 410 | } 411 | 412 | impl Copy for Scale {} 413 | 414 | impl fmt::Debug for Scale { 415 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 416 | self.0.fmt(f) 417 | } 418 | } 419 | 420 | impl Default for Scale { 421 | fn default() -> Self { 422 | Self::new(T::default()) 423 | } 424 | } 425 | 426 | impl Hash for Scale { 427 | fn hash(&self, state: &mut H) { 428 | self.0.hash(state); 429 | } 430 | } 431 | 432 | impl One for Scale { 433 | #[inline] 434 | fn one() -> Self { 435 | Scale::new(T::one()) 436 | } 437 | } 438 | 439 | #[cfg(test)] 440 | mod tests { 441 | use super::Scale; 442 | 443 | enum Inch {} 444 | enum Cm {} 445 | enum Mm {} 446 | 447 | #[test] 448 | fn test_scale() { 449 | let mm_per_inch: Scale = Scale::new(25.4); 450 | let cm_per_mm: Scale = Scale::new(0.1); 451 | 452 | let mm_per_cm: Scale = cm_per_mm.inverse(); 453 | assert_eq!(mm_per_cm.get(), 10.0); 454 | 455 | let one: Scale = cm_per_mm * mm_per_cm; 456 | assert_eq!(one.get(), 1.0); 457 | 458 | let one: Scale = mm_per_cm * cm_per_mm; 459 | assert_eq!(one.get(), 1.0); 460 | 461 | let cm_per_inch: Scale = mm_per_inch * cm_per_mm; 462 | // mm cm cm 463 | // ---- x ---- = ---- 464 | // inch mm inch 465 | assert_eq!(cm_per_inch, Scale::new(2.54)); 466 | 467 | let a: Scale = Scale::new(2); 468 | let b: Scale = Scale::new(3); 469 | assert_ne!(a, b); 470 | assert_eq!(a, a.clone()); 471 | assert_eq!(a.clone() + b.clone(), Scale::new(5)); 472 | assert_eq!(a - b, Scale::new(-1)); 473 | 474 | // Clamp 475 | assert_eq!(Scale::identity().clamp(a, b), a); 476 | assert_eq!(Scale::new(5).clamp(a, b), b); 477 | let a = Scale::::new(2.0); 478 | let b = Scale::::new(3.0); 479 | let c = Scale::::new(2.5); 480 | assert_eq!(c.clamp(a, b), c); 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /src/side_offsets.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | //! A group of side offsets, which correspond to top/left/bottom/right for borders, padding, 11 | //! and margins in CSS. 12 | 13 | use crate::length::Length; 14 | use crate::num::Zero; 15 | use crate::scale::Scale; 16 | use crate::Vector2D; 17 | 18 | use core::cmp::{Eq, PartialEq}; 19 | use core::fmt; 20 | use core::hash::Hash; 21 | use core::marker::PhantomData; 22 | use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; 23 | 24 | #[cfg(feature = "bytemuck")] 25 | use bytemuck::{Pod, Zeroable}; 26 | #[cfg(feature = "malloc_size_of")] 27 | use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 28 | #[cfg(feature = "serde")] 29 | use serde::{Deserialize, Serialize}; 30 | 31 | /// A group of 2D side offsets, which correspond to top/right/bottom/left for borders, padding, 32 | /// and margins in CSS, optionally tagged with a unit. 33 | #[repr(C)] 34 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 35 | #[cfg_attr( 36 | feature = "serde", 37 | serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")) 38 | )] 39 | pub struct SideOffsets2D { 40 | pub top: T, 41 | pub right: T, 42 | pub bottom: T, 43 | pub left: T, 44 | #[doc(hidden)] 45 | pub _unit: PhantomData, 46 | } 47 | 48 | #[cfg(feature = "arbitrary")] 49 | impl<'a, T, U> arbitrary::Arbitrary<'a> for SideOffsets2D 50 | where 51 | T: arbitrary::Arbitrary<'a>, 52 | { 53 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 54 | let (top, right, bottom, left) = arbitrary::Arbitrary::arbitrary(u)?; 55 | Ok(SideOffsets2D { 56 | top, 57 | right, 58 | bottom, 59 | left, 60 | _unit: PhantomData, 61 | }) 62 | } 63 | } 64 | 65 | #[cfg(feature = "bytemuck")] 66 | unsafe impl Zeroable for SideOffsets2D {} 67 | 68 | #[cfg(feature = "bytemuck")] 69 | unsafe impl Pod for SideOffsets2D {} 70 | 71 | #[cfg(feature = "malloc_size_of")] 72 | impl MallocSizeOf for SideOffsets2D { 73 | fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { 74 | self.top.size_of(ops) 75 | + self.right.size_of(ops) 76 | + self.bottom.size_of(ops) 77 | + self.left.size_of(ops) 78 | } 79 | } 80 | 81 | impl Copy for SideOffsets2D {} 82 | 83 | impl Clone for SideOffsets2D { 84 | fn clone(&self) -> Self { 85 | SideOffsets2D { 86 | top: self.top.clone(), 87 | right: self.right.clone(), 88 | bottom: self.bottom.clone(), 89 | left: self.left.clone(), 90 | _unit: PhantomData, 91 | } 92 | } 93 | } 94 | 95 | impl Eq for SideOffsets2D where T: Eq {} 96 | 97 | impl PartialEq for SideOffsets2D 98 | where 99 | T: PartialEq, 100 | { 101 | fn eq(&self, other: &Self) -> bool { 102 | self.top == other.top 103 | && self.right == other.right 104 | && self.bottom == other.bottom 105 | && self.left == other.left 106 | } 107 | } 108 | 109 | impl Hash for SideOffsets2D 110 | where 111 | T: Hash, 112 | { 113 | fn hash(&self, h: &mut H) { 114 | self.top.hash(h); 115 | self.right.hash(h); 116 | self.bottom.hash(h); 117 | self.left.hash(h); 118 | } 119 | } 120 | 121 | impl fmt::Debug for SideOffsets2D { 122 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 123 | write!( 124 | f, 125 | "({:?},{:?},{:?},{:?})", 126 | self.top, self.right, self.bottom, self.left 127 | ) 128 | } 129 | } 130 | 131 | impl Default for SideOffsets2D { 132 | fn default() -> Self { 133 | SideOffsets2D { 134 | top: Default::default(), 135 | right: Default::default(), 136 | bottom: Default::default(), 137 | left: Default::default(), 138 | _unit: PhantomData, 139 | } 140 | } 141 | } 142 | 143 | impl SideOffsets2D { 144 | /// Constructor taking a scalar for each side. 145 | /// 146 | /// Sides are specified in top-right-bottom-left order following 147 | /// CSS's convention. 148 | pub const fn new(top: T, right: T, bottom: T, left: T) -> Self { 149 | SideOffsets2D { 150 | top, 151 | right, 152 | bottom, 153 | left, 154 | _unit: PhantomData, 155 | } 156 | } 157 | 158 | /// Constructor taking a typed Length for each side. 159 | /// 160 | /// Sides are specified in top-right-bottom-left order following 161 | /// CSS's convention. 162 | pub fn from_lengths( 163 | top: Length, 164 | right: Length, 165 | bottom: Length, 166 | left: Length, 167 | ) -> Self { 168 | SideOffsets2D::new(top.0, right.0, bottom.0, left.0) 169 | } 170 | 171 | /// Construct side offsets from min and a max vector offsets. 172 | /// 173 | /// The outer rect of the resulting side offsets is equivalent to translating 174 | /// a rectangle's upper-left corner with the min vector and translating the 175 | /// bottom-right corner with the max vector. 176 | pub fn from_vectors_outer(min: Vector2D, max: Vector2D) -> Self 177 | where 178 | T: Neg, 179 | { 180 | SideOffsets2D { 181 | left: -min.x, 182 | top: -min.y, 183 | right: max.x, 184 | bottom: max.y, 185 | _unit: PhantomData, 186 | } 187 | } 188 | 189 | /// Construct side offsets from min and a max vector offsets. 190 | /// 191 | /// The inner rect of the resulting side offsets is equivalent to translating 192 | /// a rectangle's upper-left corner with the min vector and translating the 193 | /// bottom-right corner with the max vector. 194 | pub fn from_vectors_inner(min: Vector2D, max: Vector2D) -> Self 195 | where 196 | T: Neg, 197 | { 198 | SideOffsets2D { 199 | left: min.x, 200 | top: min.y, 201 | right: -max.x, 202 | bottom: -max.y, 203 | _unit: PhantomData, 204 | } 205 | } 206 | 207 | /// Constructor, setting all sides to zero. 208 | pub fn zero() -> Self 209 | where 210 | T: Zero, 211 | { 212 | SideOffsets2D::new(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero()) 213 | } 214 | 215 | /// Returns `true` if all side offsets are zero. 216 | pub fn is_zero(&self) -> bool 217 | where 218 | T: Zero + PartialEq, 219 | { 220 | let zero = T::zero(); 221 | self.top == zero && self.right == zero && self.bottom == zero && self.left == zero 222 | } 223 | 224 | /// Constructor setting the same value to all sides, taking a scalar value directly. 225 | pub fn new_all_same(all: T) -> Self 226 | where 227 | T: Copy, 228 | { 229 | SideOffsets2D::new(all, all, all, all) 230 | } 231 | 232 | /// Constructor setting the same value to all sides, taking a typed Length. 233 | pub fn from_length_all_same(all: Length) -> Self 234 | where 235 | T: Copy, 236 | { 237 | SideOffsets2D::new_all_same(all.0) 238 | } 239 | 240 | pub fn horizontal(&self) -> T 241 | where 242 | T: Copy + Add, 243 | { 244 | self.left + self.right 245 | } 246 | 247 | pub fn vertical(&self) -> T 248 | where 249 | T: Copy + Add, 250 | { 251 | self.top + self.bottom 252 | } 253 | } 254 | 255 | impl Add for SideOffsets2D 256 | where 257 | T: Add, 258 | { 259 | type Output = Self; 260 | fn add(self, other: Self) -> Self { 261 | SideOffsets2D::new( 262 | self.top + other.top, 263 | self.right + other.right, 264 | self.bottom + other.bottom, 265 | self.left + other.left, 266 | ) 267 | } 268 | } 269 | 270 | impl AddAssign for SideOffsets2D 271 | where 272 | T: AddAssign, 273 | { 274 | fn add_assign(&mut self, other: Self) { 275 | self.top += other.top; 276 | self.right += other.right; 277 | self.bottom += other.bottom; 278 | self.left += other.left; 279 | } 280 | } 281 | 282 | impl Sub for SideOffsets2D 283 | where 284 | T: Sub, 285 | { 286 | type Output = Self; 287 | fn sub(self, other: Self) -> Self { 288 | SideOffsets2D::new( 289 | self.top - other.top, 290 | self.right - other.right, 291 | self.bottom - other.bottom, 292 | self.left - other.left, 293 | ) 294 | } 295 | } 296 | 297 | impl SubAssign for SideOffsets2D 298 | where 299 | T: SubAssign, 300 | { 301 | fn sub_assign(&mut self, other: Self) { 302 | self.top -= other.top; 303 | self.right -= other.right; 304 | self.bottom -= other.bottom; 305 | self.left -= other.left; 306 | } 307 | } 308 | 309 | impl Neg for SideOffsets2D 310 | where 311 | T: Neg, 312 | { 313 | type Output = Self; 314 | fn neg(self) -> Self { 315 | SideOffsets2D { 316 | top: -self.top, 317 | right: -self.right, 318 | bottom: -self.bottom, 319 | left: -self.left, 320 | _unit: PhantomData, 321 | } 322 | } 323 | } 324 | 325 | impl Mul for SideOffsets2D { 326 | type Output = SideOffsets2D; 327 | 328 | #[inline] 329 | fn mul(self, scale: T) -> Self::Output { 330 | SideOffsets2D::new( 331 | self.top * scale, 332 | self.right * scale, 333 | self.bottom * scale, 334 | self.left * scale, 335 | ) 336 | } 337 | } 338 | 339 | impl MulAssign for SideOffsets2D { 340 | #[inline] 341 | fn mul_assign(&mut self, other: T) { 342 | self.top *= other; 343 | self.right *= other; 344 | self.bottom *= other; 345 | self.left *= other; 346 | } 347 | } 348 | 349 | impl Mul> for SideOffsets2D { 350 | type Output = SideOffsets2D; 351 | 352 | #[inline] 353 | fn mul(self, scale: Scale) -> Self::Output { 354 | SideOffsets2D::new( 355 | self.top * scale.0, 356 | self.right * scale.0, 357 | self.bottom * scale.0, 358 | self.left * scale.0, 359 | ) 360 | } 361 | } 362 | 363 | impl MulAssign> for SideOffsets2D { 364 | #[inline] 365 | fn mul_assign(&mut self, other: Scale) { 366 | *self *= other.0; 367 | } 368 | } 369 | 370 | impl Div for SideOffsets2D { 371 | type Output = SideOffsets2D; 372 | 373 | #[inline] 374 | fn div(self, scale: T) -> Self::Output { 375 | SideOffsets2D::new( 376 | self.top / scale, 377 | self.right / scale, 378 | self.bottom / scale, 379 | self.left / scale, 380 | ) 381 | } 382 | } 383 | 384 | impl DivAssign for SideOffsets2D { 385 | #[inline] 386 | fn div_assign(&mut self, other: T) { 387 | self.top /= other; 388 | self.right /= other; 389 | self.bottom /= other; 390 | self.left /= other; 391 | } 392 | } 393 | 394 | impl Div> for SideOffsets2D { 395 | type Output = SideOffsets2D; 396 | 397 | #[inline] 398 | fn div(self, scale: Scale) -> Self::Output { 399 | SideOffsets2D::new( 400 | self.top / scale.0, 401 | self.right / scale.0, 402 | self.bottom / scale.0, 403 | self.left / scale.0, 404 | ) 405 | } 406 | } 407 | 408 | impl DivAssign> for SideOffsets2D { 409 | fn div_assign(&mut self, other: Scale) { 410 | *self /= other.0; 411 | } 412 | } 413 | 414 | #[test] 415 | fn from_vectors() { 416 | use crate::{point2, vec2}; 417 | type Box2D = crate::default::Box2D; 418 | 419 | let b = Box2D { 420 | min: point2(10, 10), 421 | max: point2(20, 20), 422 | }; 423 | 424 | let outer = b.outer_box(SideOffsets2D::from_vectors_outer(vec2(-1, -2), vec2(3, 4))); 425 | let inner = b.inner_box(SideOffsets2D::from_vectors_inner(vec2(1, 2), vec2(-3, -4))); 426 | 427 | assert_eq!( 428 | outer, 429 | Box2D { 430 | min: point2(9, 8), 431 | max: point2(23, 24) 432 | } 433 | ); 434 | assert_eq!( 435 | inner, 436 | Box2D { 437 | min: point2(11, 12), 438 | max: point2(17, 16) 439 | } 440 | ); 441 | } 442 | 443 | #[test] 444 | fn test_is_zero() { 445 | let s1: SideOffsets2D = SideOffsets2D::new_all_same(0.0); 446 | assert!(s1.is_zero()); 447 | 448 | let s2: SideOffsets2D = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0); 449 | assert!(!s2.is_zero()); 450 | } 451 | 452 | #[cfg(test)] 453 | mod ops { 454 | use crate::Scale; 455 | 456 | pub enum Mm {} 457 | pub enum Cm {} 458 | 459 | type SideOffsets2D = crate::default::SideOffsets2D; 460 | type SideOffsets2DMm = crate::SideOffsets2D; 461 | type SideOffsets2DCm = crate::SideOffsets2D; 462 | 463 | #[test] 464 | fn test_mul_scalar() { 465 | let s = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0); 466 | 467 | let result = s * 3.0; 468 | 469 | assert_eq!(result, SideOffsets2D::new(3.0, 6.0, 9.0, 12.0)); 470 | } 471 | 472 | #[test] 473 | fn test_mul_assign_scalar() { 474 | let mut s = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0); 475 | 476 | s *= 2.0; 477 | 478 | assert_eq!(s, SideOffsets2D::new(2.0, 4.0, 6.0, 8.0)); 479 | } 480 | 481 | #[test] 482 | fn test_mul_scale() { 483 | let s = SideOffsets2DMm::new(0.0, 1.0, 3.0, 2.0); 484 | let cm_per_mm: Scale = Scale::new(0.1); 485 | 486 | let result = s * cm_per_mm; 487 | 488 | assert_eq!(result, SideOffsets2DCm::new(0.0, 0.1, 0.3, 0.2)); 489 | } 490 | 491 | #[test] 492 | fn test_mul_assign_scale() { 493 | let mut s = SideOffsets2DMm::new(2.0, 4.0, 6.0, 8.0); 494 | let scale: Scale = Scale::new(0.1); 495 | 496 | s *= scale; 497 | 498 | assert_eq!(s, SideOffsets2DMm::new(0.2, 0.4, 0.6, 0.8)); 499 | } 500 | 501 | #[test] 502 | fn test_div_scalar() { 503 | let s = SideOffsets2D::new(10.0, 20.0, 30.0, 40.0); 504 | 505 | let result = s / 10.0; 506 | 507 | assert_eq!(result, SideOffsets2D::new(1.0, 2.0, 3.0, 4.0)); 508 | } 509 | 510 | #[test] 511 | fn test_div_assign_scalar() { 512 | let mut s = SideOffsets2D::new(10.0, 20.0, 30.0, 40.0); 513 | 514 | s /= 10.0; 515 | 516 | assert_eq!(s, SideOffsets2D::new(1.0, 2.0, 3.0, 4.0)); 517 | } 518 | 519 | #[test] 520 | fn test_div_scale() { 521 | let s = SideOffsets2DCm::new(0.1, 0.2, 0.3, 0.4); 522 | let cm_per_mm: Scale = Scale::new(0.1); 523 | 524 | let result = s / cm_per_mm; 525 | 526 | assert_eq!(result, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0)); 527 | } 528 | 529 | #[test] 530 | fn test_div_assign_scale() { 531 | let mut s = SideOffsets2DMm::new(0.1, 0.2, 0.3, 0.4); 532 | let scale: Scale = Scale::new(0.1); 533 | 534 | s /= scale; 535 | 536 | assert_eq!(s, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0)); 537 | } 538 | } 539 | -------------------------------------------------------------------------------- /src/transform2d.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | #![allow(clippy::just_underscores_and_digits)] 11 | 12 | use super::{Angle, UnknownUnit}; 13 | use crate::approxeq::ApproxEq; 14 | use crate::box2d::Box2D; 15 | use crate::num::{One, Zero}; 16 | use crate::point::{point2, Point2D}; 17 | use crate::rect::Rect; 18 | use crate::transform3d::Transform3D; 19 | use crate::trig::Trig; 20 | use crate::vector::{vec2, Vector2D}; 21 | use core::cmp::{Eq, PartialEq}; 22 | use core::fmt; 23 | use core::hash::Hash; 24 | use core::marker::PhantomData; 25 | use core::ops::{Add, Div, Mul, Sub}; 26 | 27 | #[cfg(feature = "bytemuck")] 28 | use bytemuck::{Pod, Zeroable}; 29 | #[cfg(feature = "malloc_size_of")] 30 | use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 31 | #[cfg(feature = "mint")] 32 | use mint; 33 | use num_traits::NumCast; 34 | #[cfg(feature = "serde")] 35 | use serde::{Deserialize, Serialize}; 36 | 37 | /// A 2d transform represented by a column-major 3 by 3 matrix, compressed down to 3 by 2. 38 | /// 39 | /// Transforms can be parametrized over the source and destination units, to describe a 40 | /// transformation from a space to another. 41 | /// For example, `Transform2D::transform_point4d` 42 | /// takes a `Point2D` and returns a `Point2D`. 43 | /// 44 | /// Transforms expose a set of convenience methods for pre- and post-transformations. 45 | /// Pre-transformations (`pre_*` methods) correspond to adding an operation that is 46 | /// applied before the rest of the transformation, while post-transformations (`then_*` 47 | /// methods) add an operation that is applied after. 48 | /// 49 | /// The matrix representation is conceptually equivalent to a 3 by 3 matrix transformation 50 | /// compressed to 3 by 2 with the components that aren't needed to describe the set of 2d 51 | /// transformations we are interested in implicitly defined: 52 | /// 53 | /// ```text 54 | /// | m11 m21 m31 | |x| |x'| 55 | /// | m12 m22 m32 | x |y| = |y'| 56 | /// | 0 0 1 | |1| |1 | 57 | /// ``` 58 | /// 59 | /// When translating `Transform2D` into general matrix representations, consider that the 60 | /// representation follows the column-major notation with column vectors. 61 | /// 62 | /// The translation terms are `m31` and `m32`. 63 | #[repr(C)] 64 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 65 | #[cfg_attr( 66 | feature = "serde", 67 | serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")) 68 | )] 69 | #[rustfmt::skip] 70 | pub struct Transform2D { 71 | pub m11: T, pub m12: T, 72 | pub m21: T, pub m22: T, 73 | pub m31: T, pub m32: T, 74 | #[doc(hidden)] 75 | pub _unit: PhantomData<(Src, Dst)>, 76 | } 77 | 78 | #[cfg(feature = "arbitrary")] 79 | impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Transform2D 80 | where 81 | T: arbitrary::Arbitrary<'a>, 82 | { 83 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 84 | let (m11, m12, m21, m22, m31, m32) = arbitrary::Arbitrary::arbitrary(u)?; 85 | Ok(Transform2D { 86 | m11, 87 | m12, 88 | m21, 89 | m22, 90 | m31, 91 | m32, 92 | _unit: PhantomData, 93 | }) 94 | } 95 | } 96 | 97 | #[cfg(feature = "bytemuck")] 98 | unsafe impl Zeroable for Transform2D {} 99 | 100 | #[cfg(feature = "bytemuck")] 101 | unsafe impl Pod for Transform2D {} 102 | 103 | #[cfg(feature = "malloc_size_of")] 104 | impl MallocSizeOf for Transform2D { 105 | fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { 106 | self.m11.size_of(ops) 107 | + self.m12.size_of(ops) 108 | + self.m21.size_of(ops) 109 | + self.m22.size_of(ops) 110 | + self.m31.size_of(ops) 111 | + self.m32.size_of(ops) 112 | } 113 | } 114 | 115 | impl Copy for Transform2D {} 116 | 117 | impl Clone for Transform2D { 118 | fn clone(&self) -> Self { 119 | Transform2D { 120 | m11: self.m11.clone(), 121 | m12: self.m12.clone(), 122 | m21: self.m21.clone(), 123 | m22: self.m22.clone(), 124 | m31: self.m31.clone(), 125 | m32: self.m32.clone(), 126 | _unit: PhantomData, 127 | } 128 | } 129 | } 130 | 131 | impl Eq for Transform2D where T: Eq {} 132 | 133 | impl PartialEq for Transform2D 134 | where 135 | T: PartialEq, 136 | { 137 | fn eq(&self, other: &Self) -> bool { 138 | self.m11 == other.m11 139 | && self.m12 == other.m12 140 | && self.m21 == other.m21 141 | && self.m22 == other.m22 142 | && self.m31 == other.m31 143 | && self.m32 == other.m32 144 | } 145 | } 146 | 147 | impl Hash for Transform2D 148 | where 149 | T: Hash, 150 | { 151 | fn hash(&self, h: &mut H) { 152 | self.m11.hash(h); 153 | self.m12.hash(h); 154 | self.m21.hash(h); 155 | self.m22.hash(h); 156 | self.m31.hash(h); 157 | self.m32.hash(h); 158 | } 159 | } 160 | 161 | impl Transform2D { 162 | /// Create a transform specifying its components in using the column-major-column-vector 163 | /// matrix notation. 164 | /// 165 | /// For example, the translation terms m31 and m32 are the last two parameters parameters. 166 | /// 167 | /// ``` 168 | /// use euclid::default::Transform2D; 169 | /// let tx = 1.0; 170 | /// let ty = 2.0; 171 | /// let translation = Transform2D::new( 172 | /// 1.0, 0.0, 173 | /// 0.0, 1.0, 174 | /// tx, ty, 175 | /// ); 176 | /// ``` 177 | #[rustfmt::skip] 178 | pub const fn new(m11: T, m12: T, m21: T, m22: T, m31: T, m32: T) -> Self { 179 | Transform2D { 180 | m11, m12, 181 | m21, m22, 182 | m31, m32, 183 | _unit: PhantomData, 184 | } 185 | } 186 | 187 | /// Returns `true` if this transform is approximately equal to the other one, using 188 | /// `T`'s default epsilon value. 189 | /// 190 | /// The same as [`ApproxEq::approx_eq`] but available without importing trait. 191 | #[inline] 192 | pub fn approx_eq(&self, other: &Self) -> bool 193 | where 194 | T: ApproxEq, 195 | { 196 | >::approx_eq(self, other) 197 | } 198 | 199 | /// Returns `true` if this transform is approximately equal to the other one, using 200 | /// a provided epsilon value. 201 | /// 202 | /// The same as [`ApproxEq::approx_eq_eps`] but available without importing trait. 203 | #[inline] 204 | pub fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool 205 | where 206 | T: ApproxEq, 207 | { 208 | >::approx_eq_eps(self, other, eps) 209 | } 210 | } 211 | 212 | impl Transform2D { 213 | /// Returns an array containing this transform's terms. 214 | /// 215 | /// The terms are laid out in the same order as they are 216 | /// specified in [`Transform2D::new`], that is following the 217 | /// column-major-column-vector matrix notation. 218 | /// 219 | /// For example the translation terms are found in the 220 | /// last two slots of the array. 221 | #[inline] 222 | #[rustfmt::skip] 223 | pub fn to_array(&self) -> [T; 6] { 224 | [ 225 | self.m11, self.m12, 226 | self.m21, self.m22, 227 | self.m31, self.m32 228 | ] 229 | } 230 | 231 | /// Returns an array containing this transform's terms transposed. 232 | /// 233 | /// The terms are laid out in transposed order from the same order of 234 | /// `Transform3D::new` and `Transform3D::to_array`, that is following 235 | /// the row-major-column-vector matrix notation. 236 | /// 237 | /// For example the translation terms are found at indices 2 and 5 238 | /// in the array. 239 | #[inline] 240 | #[rustfmt::skip] 241 | pub fn to_array_transposed(&self) -> [T; 6] { 242 | [ 243 | self.m11, self.m21, self.m31, 244 | self.m12, self.m22, self.m32 245 | ] 246 | } 247 | 248 | /// Equivalent to `to_array` with elements packed two at a time 249 | /// in an array of arrays. 250 | #[inline] 251 | pub fn to_arrays(&self) -> [[T; 2]; 3] { 252 | [ 253 | [self.m11, self.m12], 254 | [self.m21, self.m22], 255 | [self.m31, self.m32], 256 | ] 257 | } 258 | 259 | /// Create a transform providing its components via an array 260 | /// of 6 elements instead of as individual parameters. 261 | /// 262 | /// The order of the components corresponds to the 263 | /// column-major-column-vector matrix notation (the same order 264 | /// as `Transform2D::new`). 265 | #[inline] 266 | #[rustfmt::skip] 267 | pub fn from_array(array: [T; 6]) -> Self { 268 | Self::new( 269 | array[0], array[1], 270 | array[2], array[3], 271 | array[4], array[5], 272 | ) 273 | } 274 | 275 | /// Equivalent to `from_array` with elements packed two at a time 276 | /// in an array of arrays. 277 | /// 278 | /// The order of the components corresponds to the 279 | /// column-major-column-vector matrix notation (the same order 280 | /// as `Transform3D::new`). 281 | #[inline] 282 | #[rustfmt::skip] 283 | pub fn from_arrays(array: [[T; 2]; 3]) -> Self { 284 | Self::new( 285 | array[0][0], array[0][1], 286 | array[1][0], array[1][1], 287 | array[2][0], array[2][1], 288 | ) 289 | } 290 | 291 | /// Drop the units, preserving only the numeric value. 292 | #[inline] 293 | #[rustfmt::skip] 294 | pub fn to_untyped(&self) -> Transform2D { 295 | Transform2D::new( 296 | self.m11, self.m12, 297 | self.m21, self.m22, 298 | self.m31, self.m32 299 | ) 300 | } 301 | 302 | /// Tag a unitless value with units. 303 | #[inline] 304 | #[rustfmt::skip] 305 | pub fn from_untyped(p: &Transform2D) -> Self { 306 | Transform2D::new( 307 | p.m11, p.m12, 308 | p.m21, p.m22, 309 | p.m31, p.m32 310 | ) 311 | } 312 | 313 | /// Returns the same transform with a different source unit. 314 | #[inline] 315 | #[rustfmt::skip] 316 | pub fn with_source(&self) -> Transform2D { 317 | Transform2D::new( 318 | self.m11, self.m12, 319 | self.m21, self.m22, 320 | self.m31, self.m32, 321 | ) 322 | } 323 | 324 | /// Returns the same transform with a different destination unit. 325 | #[inline] 326 | #[rustfmt::skip] 327 | pub fn with_destination(&self) -> Transform2D { 328 | Transform2D::new( 329 | self.m11, self.m12, 330 | self.m21, self.m22, 331 | self.m31, self.m32, 332 | ) 333 | } 334 | 335 | /// Create a 3D transform from the current transform 336 | pub fn to_3d(&self) -> Transform3D 337 | where 338 | T: Zero + One, 339 | { 340 | Transform3D::new_2d(self.m11, self.m12, self.m21, self.m22, self.m31, self.m32) 341 | } 342 | } 343 | 344 | impl Transform2D { 345 | /// Cast from one numeric representation to another, preserving the units. 346 | #[inline] 347 | pub fn cast(&self) -> Transform2D { 348 | self.try_cast().unwrap() 349 | } 350 | 351 | /// Fallible cast from one numeric representation to another, preserving the units. 352 | #[rustfmt::skip] 353 | pub fn try_cast(&self) -> Option> { 354 | match (NumCast::from(self.m11), NumCast::from(self.m12), 355 | NumCast::from(self.m21), NumCast::from(self.m22), 356 | NumCast::from(self.m31), NumCast::from(self.m32)) { 357 | (Some(m11), Some(m12), 358 | Some(m21), Some(m22), 359 | Some(m31), Some(m32)) => { 360 | Some(Transform2D::new( 361 | m11, m12, 362 | m21, m22, 363 | m31, m32 364 | )) 365 | }, 366 | _ => None 367 | } 368 | } 369 | } 370 | 371 | impl Transform2D 372 | where 373 | T: Zero + One, 374 | { 375 | /// Create an identity matrix: 376 | /// 377 | /// ```text 378 | /// 1 0 379 | /// 0 1 380 | /// 0 0 381 | /// ``` 382 | #[inline] 383 | pub fn identity() -> Self { 384 | Self::translation(T::zero(), T::zero()) 385 | } 386 | 387 | /// Intentional not public, because it checks for exact equivalence 388 | /// while most consumers will probably want some sort of approximate 389 | /// equivalence to deal with floating-point errors. 390 | fn is_identity(&self) -> bool 391 | where 392 | T: PartialEq, 393 | { 394 | *self == Self::identity() 395 | } 396 | } 397 | 398 | /// Methods for combining generic transformations 399 | impl Transform2D 400 | where 401 | T: Copy + Add + Mul, 402 | { 403 | /// Returns the multiplication of the two matrices such that mat's transformation 404 | /// applies after self's transformation. 405 | #[must_use] 406 | #[rustfmt::skip] 407 | pub fn then(&self, mat: &Transform2D) -> Transform2D { 408 | Transform2D::new( 409 | self.m11 * mat.m11 + self.m12 * mat.m21, 410 | self.m11 * mat.m12 + self.m12 * mat.m22, 411 | 412 | self.m21 * mat.m11 + self.m22 * mat.m21, 413 | self.m21 * mat.m12 + self.m22 * mat.m22, 414 | 415 | self.m31 * mat.m11 + self.m32 * mat.m21 + mat.m31, 416 | self.m31 * mat.m12 + self.m32 * mat.m22 + mat.m32, 417 | ) 418 | } 419 | } 420 | 421 | /// Methods for creating and combining translation transformations 422 | impl Transform2D 423 | where 424 | T: Zero + One, 425 | { 426 | /// Create a 2d translation transform: 427 | /// 428 | /// ```text 429 | /// 1 0 430 | /// 0 1 431 | /// x y 432 | /// ``` 433 | #[inline] 434 | #[rustfmt::skip] 435 | pub fn translation(x: T, y: T) -> Self { 436 | let _0 = || T::zero(); 437 | let _1 = || T::one(); 438 | 439 | Self::new( 440 | _1(), _0(), 441 | _0(), _1(), 442 | x, y, 443 | ) 444 | } 445 | 446 | /// Applies a translation after self's transformation and returns the resulting transform. 447 | #[inline] 448 | #[must_use] 449 | pub fn then_translate(&self, v: Vector2D) -> Self 450 | where 451 | T: Copy + Add + Mul, 452 | { 453 | self.then(&Transform2D::translation(v.x, v.y)) 454 | } 455 | 456 | /// Applies a translation before self's transformation and returns the resulting transform. 457 | #[inline] 458 | #[must_use] 459 | pub fn pre_translate(&self, v: Vector2D) -> Self 460 | where 461 | T: Copy + Add + Mul, 462 | { 463 | Transform2D::translation(v.x, v.y).then(self) 464 | } 465 | } 466 | 467 | /// Methods for creating and combining rotation transformations 468 | impl Transform2D 469 | where 470 | T: Copy + Add + Sub + Mul + Zero + Trig, 471 | { 472 | /// Returns a rotation transform. 473 | #[inline] 474 | #[rustfmt::skip] 475 | pub fn rotation(theta: Angle) -> Self { 476 | let _0 = Zero::zero(); 477 | let cos = theta.get().cos(); 478 | let sin = theta.get().sin(); 479 | Transform2D::new( 480 | cos, sin, 481 | _0 - sin, cos, 482 | _0, _0 483 | ) 484 | } 485 | 486 | /// Applies a rotation after self's transformation and returns the resulting transform. 487 | #[inline] 488 | #[must_use] 489 | pub fn then_rotate(&self, theta: Angle) -> Self { 490 | self.then(&Transform2D::rotation(theta)) 491 | } 492 | 493 | /// Applies a rotation before self's transformation and returns the resulting transform. 494 | #[inline] 495 | #[must_use] 496 | pub fn pre_rotate(&self, theta: Angle) -> Self { 497 | Transform2D::rotation(theta).then(self) 498 | } 499 | } 500 | 501 | /// Methods for creating and combining scale transformations 502 | impl Transform2D { 503 | /// Create a 2d scale transform: 504 | /// 505 | /// ```text 506 | /// x 0 507 | /// 0 y 508 | /// 0 0 509 | /// ``` 510 | #[inline] 511 | #[rustfmt::skip] 512 | pub fn scale(x: T, y: T) -> Self 513 | where 514 | T: Zero, 515 | { 516 | let _0 = || Zero::zero(); 517 | 518 | Self::new( 519 | x, _0(), 520 | _0(), y, 521 | _0(), _0(), 522 | ) 523 | } 524 | 525 | /// Applies a scale after self's transformation and returns the resulting transform. 526 | #[inline] 527 | #[must_use] 528 | pub fn then_scale(&self, x: T, y: T) -> Self 529 | where 530 | T: Copy + Add + Mul + Zero, 531 | { 532 | self.then(&Transform2D::scale(x, y)) 533 | } 534 | 535 | /// Applies a scale before self's transformation and returns the resulting transform. 536 | #[inline] 537 | #[must_use] 538 | #[rustfmt::skip] 539 | pub fn pre_scale(&self, x: T, y: T) -> Self 540 | where 541 | T: Copy + Mul, 542 | { 543 | Transform2D::new( 544 | self.m11 * x, self.m12 * x, 545 | self.m21 * y, self.m22 * y, 546 | self.m31, self.m32 547 | ) 548 | } 549 | } 550 | 551 | /// Methods for apply transformations to objects 552 | impl Transform2D 553 | where 554 | T: Copy + Add + Mul, 555 | { 556 | /// Returns the given point transformed by this transform. 557 | #[inline] 558 | #[must_use] 559 | pub fn transform_point(&self, point: Point2D) -> Point2D { 560 | Point2D::new( 561 | point.x * self.m11 + point.y * self.m21 + self.m31, 562 | point.x * self.m12 + point.y * self.m22 + self.m32, 563 | ) 564 | } 565 | 566 | /// Returns the given vector transformed by this matrix. 567 | #[inline] 568 | #[must_use] 569 | pub fn transform_vector(&self, vec: Vector2D) -> Vector2D { 570 | vec2( 571 | vec.x * self.m11 + vec.y * self.m21, 572 | vec.x * self.m12 + vec.y * self.m22, 573 | ) 574 | } 575 | 576 | /// Returns a rectangle that encompasses the result of transforming the given rectangle by this 577 | /// transform. 578 | #[inline] 579 | #[must_use] 580 | pub fn outer_transformed_rect(&self, rect: &Rect) -> Rect 581 | where 582 | T: Sub + Zero + PartialOrd, 583 | { 584 | let min = rect.min(); 585 | let max = rect.max(); 586 | Rect::from_points(&[ 587 | self.transform_point(min), 588 | self.transform_point(max), 589 | self.transform_point(point2(max.x, min.y)), 590 | self.transform_point(point2(min.x, max.y)), 591 | ]) 592 | } 593 | 594 | /// Returns a box that encompasses the result of transforming the given box by this 595 | /// transform. 596 | #[inline] 597 | #[must_use] 598 | pub fn outer_transformed_box(&self, b: &Box2D) -> Box2D 599 | where 600 | T: Sub + Zero + PartialOrd, 601 | { 602 | Box2D::from_points(&[ 603 | self.transform_point(b.min), 604 | self.transform_point(b.max), 605 | self.transform_point(point2(b.max.x, b.min.y)), 606 | self.transform_point(point2(b.min.x, b.max.y)), 607 | ]) 608 | } 609 | } 610 | 611 | impl Transform2D 612 | where 613 | T: Copy + Sub + Mul + Div + PartialEq + Zero + One, 614 | { 615 | /// Computes and returns the determinant of this transform. 616 | pub fn determinant(&self) -> T { 617 | self.m11 * self.m22 - self.m12 * self.m21 618 | } 619 | 620 | /// Returns whether it is possible to compute the inverse transform. 621 | #[inline] 622 | pub fn is_invertible(&self) -> bool { 623 | self.determinant() != Zero::zero() 624 | } 625 | 626 | /// Returns the inverse transform if possible. 627 | #[must_use] 628 | pub fn inverse(&self) -> Option> { 629 | let det = self.determinant(); 630 | 631 | let _0: T = Zero::zero(); 632 | let _1: T = One::one(); 633 | 634 | if det == _0 { 635 | return None; 636 | } 637 | 638 | let inv_det = _1 / det; 639 | Some(Transform2D::new( 640 | inv_det * self.m22, 641 | inv_det * (_0 - self.m12), 642 | inv_det * (_0 - self.m21), 643 | inv_det * self.m11, 644 | inv_det * (self.m21 * self.m32 - self.m22 * self.m31), 645 | inv_det * (self.m31 * self.m12 - self.m11 * self.m32), 646 | )) 647 | } 648 | } 649 | 650 | impl Default for Transform2D 651 | where 652 | T: Zero + One, 653 | { 654 | /// Returns the [identity transform](Transform2D::identity). 655 | fn default() -> Self { 656 | Self::identity() 657 | } 658 | } 659 | 660 | impl, Src, Dst> ApproxEq for Transform2D { 661 | #[inline] 662 | fn approx_epsilon() -> T { 663 | T::approx_epsilon() 664 | } 665 | 666 | /// Returns `true` if this transform is approximately equal to the other one, using 667 | /// a provided epsilon value. 668 | fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool { 669 | self.m11.approx_eq_eps(&other.m11, eps) 670 | && self.m12.approx_eq_eps(&other.m12, eps) 671 | && self.m21.approx_eq_eps(&other.m21, eps) 672 | && self.m22.approx_eq_eps(&other.m22, eps) 673 | && self.m31.approx_eq_eps(&other.m31, eps) 674 | && self.m32.approx_eq_eps(&other.m32, eps) 675 | } 676 | } 677 | 678 | impl fmt::Debug for Transform2D 679 | where 680 | T: Copy + fmt::Debug + PartialEq + One + Zero, 681 | { 682 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 683 | if self.is_identity() { 684 | write!(f, "[I]") 685 | } else { 686 | self.to_array().fmt(f) 687 | } 688 | } 689 | } 690 | 691 | #[cfg(feature = "mint")] 692 | impl From> for Transform2D { 693 | #[rustfmt::skip] 694 | fn from(m: mint::RowMatrix3x2) -> Self { 695 | Transform2D { 696 | m11: m.x.x, m12: m.x.y, 697 | m21: m.y.x, m22: m.y.y, 698 | m31: m.z.x, m32: m.z.y, 699 | _unit: PhantomData, 700 | } 701 | } 702 | } 703 | #[cfg(feature = "mint")] 704 | impl From> for mint::RowMatrix3x2 { 705 | fn from(t: Transform2D) -> Self { 706 | mint::RowMatrix3x2 { 707 | x: mint::Vector2 { x: t.m11, y: t.m12 }, 708 | y: mint::Vector2 { x: t.m21, y: t.m22 }, 709 | z: mint::Vector2 { x: t.m31, y: t.m32 }, 710 | } 711 | } 712 | } 713 | 714 | #[cfg(test)] 715 | mod test { 716 | use super::*; 717 | use crate::approxeq::ApproxEq; 718 | use crate::default; 719 | #[cfg(feature = "mint")] 720 | use mint; 721 | 722 | use core::f32::consts::FRAC_PI_2; 723 | 724 | type Mat = default::Transform2D; 725 | 726 | fn rad(v: f32) -> Angle { 727 | Angle::radians(v) 728 | } 729 | 730 | #[test] 731 | pub fn test_translation() { 732 | let t1 = Mat::translation(1.0, 2.0); 733 | let t2 = Mat::identity().pre_translate(vec2(1.0, 2.0)); 734 | let t3 = Mat::identity().then_translate(vec2(1.0, 2.0)); 735 | assert_eq!(t1, t2); 736 | assert_eq!(t1, t3); 737 | 738 | assert_eq!( 739 | t1.transform_point(Point2D::new(1.0, 1.0)), 740 | Point2D::new(2.0, 3.0) 741 | ); 742 | 743 | assert_eq!(t1.then(&t1), Mat::translation(2.0, 4.0)); 744 | } 745 | 746 | #[test] 747 | pub fn test_rotation() { 748 | let r1 = Mat::rotation(rad(FRAC_PI_2)); 749 | let r2 = Mat::identity().pre_rotate(rad(FRAC_PI_2)); 750 | let r3 = Mat::identity().then_rotate(rad(FRAC_PI_2)); 751 | assert_eq!(r1, r2); 752 | assert_eq!(r1, r3); 753 | 754 | assert!(r1 755 | .transform_point(Point2D::new(1.0, 2.0)) 756 | .approx_eq(&Point2D::new(-2.0, 1.0))); 757 | 758 | assert!(r1.then(&r1).approx_eq(&Mat::rotation(rad(FRAC_PI_2 * 2.0)))); 759 | } 760 | 761 | #[test] 762 | pub fn test_scale() { 763 | let s1 = Mat::scale(2.0, 3.0); 764 | let s2 = Mat::identity().pre_scale(2.0, 3.0); 765 | let s3 = Mat::identity().then_scale(2.0, 3.0); 766 | assert_eq!(s1, s2); 767 | assert_eq!(s1, s3); 768 | 769 | assert!(s1 770 | .transform_point(Point2D::new(2.0, 2.0)) 771 | .approx_eq(&Point2D::new(4.0, 6.0))); 772 | } 773 | 774 | #[test] 775 | pub fn test_pre_then_scale() { 776 | let m = Mat::rotation(rad(FRAC_PI_2)).then_translate(vec2(6.0, 7.0)); 777 | let s = Mat::scale(2.0, 3.0); 778 | assert_eq!(m.then(&s), m.then_scale(2.0, 3.0)); 779 | } 780 | 781 | #[test] 782 | pub fn test_inverse_simple() { 783 | let m1 = Mat::identity(); 784 | let m2 = m1.inverse().unwrap(); 785 | assert!(m1.approx_eq(&m2)); 786 | } 787 | 788 | #[test] 789 | pub fn test_inverse_scale() { 790 | let m1 = Mat::scale(1.5, 0.3); 791 | let m2 = m1.inverse().unwrap(); 792 | assert!(m1.then(&m2).approx_eq(&Mat::identity())); 793 | assert!(m2.then(&m1).approx_eq(&Mat::identity())); 794 | } 795 | 796 | #[test] 797 | pub fn test_inverse_translate() { 798 | let m1 = Mat::translation(-132.0, 0.3); 799 | let m2 = m1.inverse().unwrap(); 800 | assert!(m1.then(&m2).approx_eq(&Mat::identity())); 801 | assert!(m2.then(&m1).approx_eq(&Mat::identity())); 802 | } 803 | 804 | #[test] 805 | fn test_inverse_none() { 806 | assert!(Mat::scale(2.0, 0.0).inverse().is_none()); 807 | assert!(Mat::scale(2.0, 2.0).inverse().is_some()); 808 | } 809 | 810 | #[test] 811 | pub fn test_pre_post() { 812 | let m1 = default::Transform2D::identity() 813 | .then_scale(1.0, 2.0) 814 | .then_translate(vec2(1.0, 2.0)); 815 | let m2 = default::Transform2D::identity() 816 | .pre_translate(vec2(1.0, 2.0)) 817 | .pre_scale(1.0, 2.0); 818 | assert!(m1.approx_eq(&m2)); 819 | 820 | let r = Mat::rotation(rad(FRAC_PI_2)); 821 | let t = Mat::translation(2.0, 3.0); 822 | 823 | let a = Point2D::new(1.0, 1.0); 824 | 825 | assert!(r 826 | .then(&t) 827 | .transform_point(a) 828 | .approx_eq(&Point2D::new(1.0, 4.0))); 829 | assert!(t 830 | .then(&r) 831 | .transform_point(a) 832 | .approx_eq(&Point2D::new(-4.0, 3.0))); 833 | assert!(t 834 | .then(&r) 835 | .transform_point(a) 836 | .approx_eq(&r.transform_point(t.transform_point(a)))); 837 | } 838 | 839 | #[test] 840 | fn test_size_of() { 841 | use core::mem::size_of; 842 | assert_eq!(size_of::>(), 6 * size_of::()); 843 | assert_eq!(size_of::>(), 6 * size_of::()); 844 | } 845 | 846 | #[test] 847 | pub fn test_is_identity() { 848 | let m1 = default::Transform2D::identity(); 849 | assert!(m1.is_identity()); 850 | let m2 = m1.then_translate(vec2(0.1, 0.0)); 851 | assert!(!m2.is_identity()); 852 | } 853 | 854 | #[test] 855 | pub fn test_transform_vector() { 856 | // Translation does not apply to vectors. 857 | let m1 = Mat::translation(1.0, 1.0); 858 | let v1 = vec2(10.0, -10.0); 859 | assert_eq!(v1, m1.transform_vector(v1)); 860 | } 861 | 862 | #[cfg(feature = "mint")] 863 | #[test] 864 | pub fn test_mint() { 865 | let m1 = Mat::rotation(rad(FRAC_PI_2)); 866 | let mm: mint::RowMatrix3x2<_> = m1.into(); 867 | let m2 = Mat::from(mm); 868 | 869 | assert_eq!(m1, m2); 870 | } 871 | } 872 | -------------------------------------------------------------------------------- /src/trig.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Servo Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | /// Trait for basic trigonometry functions, so they can be used on generic numeric types 11 | pub trait Trig { 12 | fn sin(self) -> Self; 13 | fn cos(self) -> Self; 14 | fn tan(self) -> Self; 15 | fn fast_atan2(y: Self, x: Self) -> Self; 16 | fn degrees_to_radians(deg: Self) -> Self; 17 | fn radians_to_degrees(rad: Self) -> Self; 18 | } 19 | 20 | macro_rules! trig { 21 | ($ty:ident) => { 22 | impl Trig for $ty { 23 | #[inline] 24 | fn sin(self) -> $ty { 25 | num_traits::Float::sin(self) 26 | } 27 | #[inline] 28 | fn cos(self) -> $ty { 29 | num_traits::Float::cos(self) 30 | } 31 | #[inline] 32 | fn tan(self) -> $ty { 33 | num_traits::Float::tan(self) 34 | } 35 | 36 | /// A slightly faster approximation of `atan2`. 37 | /// 38 | /// Note that it does not deal with the case where both x and y are 0. 39 | #[inline] 40 | fn fast_atan2(y: $ty, x: $ty) -> $ty { 41 | // This macro is used with f32 and f64 and clippy warns about the extra 42 | // precision with f32. 43 | #![allow(clippy::excessive_precision)] 44 | 45 | // See https://math.stackexchange.com/questions/1098487/atan2-faster-approximation#1105038 46 | use core::$ty::consts; 47 | let x_abs = num_traits::Float::abs(x); 48 | let y_abs = num_traits::Float::abs(y); 49 | let a = x_abs.min(y_abs) / x_abs.max(y_abs); 50 | let s = a * a; 51 | let mut result = 52 | ((-0.046_496_474_9 * s + 0.159_314_22) * s - 0.327_622_764) * s * a + a; 53 | if y_abs > x_abs { 54 | result = consts::FRAC_PI_2 - result; 55 | } 56 | if x < 0.0 { 57 | result = consts::PI - result 58 | } 59 | if y < 0.0 { 60 | result = -result 61 | } 62 | 63 | result 64 | } 65 | 66 | #[inline] 67 | fn degrees_to_radians(deg: Self) -> Self { 68 | deg.to_radians() 69 | } 70 | 71 | #[inline] 72 | fn radians_to_degrees(rad: Self) -> Self { 73 | rad.to_degrees() 74 | } 75 | } 76 | }; 77 | } 78 | 79 | trig!(f32); 80 | trig!(f64); 81 | --------------------------------------------------------------------------------