summaryrefslogtreecommitdiffstats
path: root/third_party/rust/euclid
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/euclid
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/euclid')
-rw-r--r--third_party/rust/euclid/.cargo-checksum.json1
-rw-r--r--third_party/rust/euclid/COPYRIGHT5
-rw-r--r--third_party/rust/euclid/Cargo.toml52
-rw-r--r--third_party/rust/euclid/LICENSE-APACHE201
-rw-r--r--third_party/rust/euclid/LICENSE-MIT25
-rw-r--r--third_party/rust/euclid/README.md8
-rw-r--r--third_party/rust/euclid/src/angle.rs365
-rw-r--r--third_party/rust/euclid/src/approxeq.rs42
-rw-r--r--third_party/rust/euclid/src/approxord.rs44
-rw-r--r--third_party/rust/euclid/src/box2d.rs903
-rw-r--r--third_party/rust/euclid/src/box3d.rs931
-rw-r--r--third_party/rust/euclid/src/homogen.rs223
-rw-r--r--third_party/rust/euclid/src/length.rs609
-rw-r--r--third_party/rust/euclid/src/lib.rs115
-rw-r--r--third_party/rust/euclid/src/macros.rs30
-rw-r--r--third_party/rust/euclid/src/num.rs128
-rw-r--r--third_party/rust/euclid/src/point.rs2041
-rw-r--r--third_party/rust/euclid/src/rect.rs931
-rw-r--r--third_party/rust/euclid/src/rigid.rs286
-rw-r--r--third_party/rust/euclid/src/rotation.rs1014
-rw-r--r--third_party/rust/euclid/src/scale.rs428
-rw-r--r--third_party/rust/euclid/src/side_offsets.rs520
-rw-r--r--third_party/rust/euclid/src/size.rs1854
-rw-r--r--third_party/rust/euclid/src/transform2d.rs809
-rw-r--r--third_party/rust/euclid/src/transform3d.rs1436
-rw-r--r--third_party/rust/euclid/src/translation.rs867
-rw-r--r--third_party/rust/euclid/src/trig.rs80
-rw-r--r--third_party/rust/euclid/src/vector.rs2596
28 files changed, 16544 insertions, 0 deletions
diff --git a/third_party/rust/euclid/.cargo-checksum.json b/third_party/rust/euclid/.cargo-checksum.json
new file mode 100644
index 0000000000..6401957681
--- /dev/null
+++ b/third_party/rust/euclid/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"COPYRIGHT":"ec82b96487e9e778ee610c7ab245162464782cfa1f555c2299333f8dbe5c036a","Cargo.toml":"060e84b406cb49281fc96cbeaf8d4248b0fe08bfa20fc559fa37addcbb2f82b9","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"62065228e42caebca7e7d7db1204cbb867033de5982ca4009928915e4095f3a3","README.md":"625bec69c76ce5423fdd05cfe46922b2680ec517f97c5854ce34798d1d8a9541","src/angle.rs":"a1b4be460ff6c710d4dd226a82f5f5c9a85ed5078e3bb72547dee6d57e5b3828","src/approxeq.rs":"8ad9ab7336e1924b0543eb46f975367f4388a4dda91839b7cda7d21eec8991fb","src/approxord.rs":"f1b11ea7603b3dddb0002c7ded934816f5a8bb108e150028b49595b8e3382ac1","src/box2d.rs":"46bdab3abc1852fa5fa18cbcfdb70688c5ae30f82fac66e1f67116a4f957f618","src/box3d.rs":"5e72a25f75b0869499148a03e71c60eb7588669dfe745a128d8dca11af543968","src/homogen.rs":"61a58bfc231fd00cb2e26474279887f7c8782dc2a10456520a293475e0c1debf","src/length.rs":"e1ab737fd2a64be00c29e3b34567da9b11698d2f996232604b631a39c27a0fe3","src/lib.rs":"ebf2023c1e1bd397a4ae135a14657d00757c9ad4094d32d65848a1d5e061df7a","src/macros.rs":"3b475e84d00cceee6c7e96e9f2c97ba15d8dc7f4094efb82c5ed10bd60d86a64","src/num.rs":"9c8d4564549646e68eeb2f004239626369cce3d752002497f8dda8a91d833ec1","src/point.rs":"2c7ac7016b734796e4266a80596db1b85d6dded254e9eedba112749fd8cfec3a","src/rect.rs":"ab29d3939133bf97e2133ee784f3760b2ce0cfcaf0e022c525c04359f7a6e637","src/rigid.rs":"a13f50ee579164c1010393ff140af19ab2222c664b242e58d50357eba17bea51","src/rotation.rs":"ac5dc577c3ff3b3e615afb7668ee7487caf979218ca758b828a49e2640385c63","src/scale.rs":"6862f72dac03390ab0825c3a59bc980d3f538d21ea45070838bb5181ef98db9f","src/side_offsets.rs":"177423e00cf40c32839371253f7273ebe028909b0ab5ab0bceaeba4b529fd44e","src/size.rs":"81ea83b2cf8afa415ac360b025ddb6fd90f0b3c87e356d487039896fc530ac99","src/transform2d.rs":"a76af8f7f1a2d39591147cc71b4f7e505478ac3020fbcfcc635c005b06a9df4c","src/transform3d.rs":"8652dbab99be6d1af6f838fe8d20333d777dd477137ab168e9ab2c8a0b76cd49","src/translation.rs":"a5a60dc14dd620c8e8301f149a528f3362b70d3cc7afc9722210ec3c41d8abe4","src/trig.rs":"ab09a8503d04ac1d0d6848d074ac18a22d324184e5bb186bbd4287c977894886","src/vector.rs":"5321f58bc66ea658a9f873017acd3acae637a3fcd2e0f8bf5aad30c84a7b5275"},"package":"b52c2ef4a78da0ba68fbe1fd920627411096d2ac478f7f4c9f3a54ba6705bade"} \ No newline at end of file
diff --git a/third_party/rust/euclid/COPYRIGHT b/third_party/rust/euclid/COPYRIGHT
new file mode 100644
index 0000000000..8b7291ad28
--- /dev/null
+++ b/third_party/rust/euclid/COPYRIGHT
@@ -0,0 +1,5 @@
+Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+<LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+option. All files in the project carrying such notice may not be
+copied, modified, or distributed except according to those terms.
diff --git a/third_party/rust/euclid/Cargo.toml b/third_party/rust/euclid/Cargo.toml
new file mode 100644
index 0000000000..98293d1a87
--- /dev/null
+++ b/third_party/rust/euclid/Cargo.toml
@@ -0,0 +1,52 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "euclid"
+version = "0.22.7"
+authors = ["The Servo Project Developers"]
+description = "Geometry primitives"
+documentation = "https://docs.rs/euclid/"
+keywords = ["matrix", "vector", "linear-algebra", "geometry"]
+categories = ["science"]
+license = "MIT / Apache-2.0"
+repository = "https://github.com/servo/euclid"
+[dependencies.arbitrary]
+version = "1"
+features = ["derive"]
+optional = true
+
+[dependencies.bytemuck]
+version = "1.9"
+optional = true
+
+[dependencies.mint]
+version = "0.5.1"
+optional = true
+
+[dependencies.num-traits]
+version = "0.2.10"
+default-features = false
+
+[dependencies.serde]
+version = "1.0"
+features = ["serde_derive"]
+optional = true
+default-features = false
+[dev-dependencies.serde_test]
+version = "1.0"
+
+[features]
+default = ["std"]
+libm = ["num-traits/libm"]
+std = ["num-traits/std"]
+unstable = []
diff --git a/third_party/rust/euclid/LICENSE-APACHE b/third_party/rust/euclid/LICENSE-APACHE
new file mode 100644
index 0000000000..16fe87b06e
--- /dev/null
+++ b/third_party/rust/euclid/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/third_party/rust/euclid/LICENSE-MIT b/third_party/rust/euclid/LICENSE-MIT
new file mode 100644
index 0000000000..807526f57f
--- /dev/null
+++ b/third_party/rust/euclid/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2012-2013 Mozilla Foundation
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/third_party/rust/euclid/README.md b/third_party/rust/euclid/README.md
new file mode 100644
index 0000000000..13b488da9f
--- /dev/null
+++ b/third_party/rust/euclid/README.md
@@ -0,0 +1,8 @@
+# euclid
+
+This is a small library for geometric types with a focus on 2d graphics and
+layout.
+
+* [Documentation](https://docs.rs/euclid/)
+* [Release notes](https://github.com/servo/euclid/releases)
+* [crates.io](https://crates.io/crates/euclid)
diff --git a/third_party/rust/euclid/src/angle.rs b/third_party/rust/euclid/src/angle.rs
new file mode 100644
index 0000000000..a503df6efc
--- /dev/null
+++ b/third_party/rust/euclid/src/angle.rs
@@ -0,0 +1,365 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::approxeq::ApproxEq;
+use crate::trig::Trig;
+use core::cmp::{Eq, PartialEq};
+use core::hash::Hash;
+use core::iter::Sum;
+use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign};
+use num_traits::real::Real;
+use num_traits::{Float, FloatConst, NumCast, One, Zero};
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// An angle in radians
+#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
+pub struct Angle<T> {
+ pub radians: T,
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable> Zeroable for Angle<T> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod> Pod for Angle<T> {}
+
+impl<T> Angle<T> {
+ #[inline]
+ pub fn radians(radians: T) -> Self {
+ Angle { radians }
+ }
+
+ #[inline]
+ pub fn get(self) -> T {
+ self.radians
+ }
+}
+
+impl<T> Angle<T>
+where
+ T: Trig,
+{
+ #[inline]
+ pub fn degrees(deg: T) -> Self {
+ Angle {
+ radians: T::degrees_to_radians(deg),
+ }
+ }
+
+ #[inline]
+ pub fn to_degrees(self) -> T {
+ T::radians_to_degrees(self.radians)
+ }
+}
+
+impl<T> Angle<T>
+where
+ T: Rem<Output = T> + Sub<Output = T> + Add<Output = T> + Zero + FloatConst + PartialOrd + Copy,
+{
+ /// Returns this angle in the [0..2*PI[ range.
+ pub fn positive(&self) -> Self {
+ let two_pi = T::PI() + T::PI();
+ let mut a = self.radians % two_pi;
+ if a < T::zero() {
+ a = a + two_pi;
+ }
+ Angle::radians(a)
+ }
+
+ /// Returns this angle in the ]-PI..PI] range.
+ pub fn signed(&self) -> Self {
+ Angle::pi() - (Angle::pi() - *self).positive()
+ }
+}
+
+impl<T> Angle<T>
+where
+ T: Rem<Output = T>
+ + Mul<Output = T>
+ + Sub<Output = T>
+ + Add<Output = T>
+ + One
+ + FloatConst
+ + Copy,
+{
+ /// Returns the shortest signed angle between two angles.
+ ///
+ /// Takes wrapping and signs into account.
+ pub fn angle_to(&self, to: Self) -> Self {
+ let two = T::one() + T::one();
+ let max = T::PI() * two;
+ let d = (to.radians - self.radians) % max;
+
+ Angle::radians(two * d % max - d)
+ }
+
+ /// Linear interpolation between two angles, using the shortest path.
+ pub fn lerp(&self, other: Self, t: T) -> Self {
+ *self + self.angle_to(other) * t
+ }
+}
+
+impl<T> Angle<T>
+where
+ T: Float,
+{
+ /// Returns true if the angle is a finite number.
+ #[inline]
+ pub fn is_finite(self) -> bool {
+ self.radians.is_finite()
+ }
+}
+
+impl<T> Angle<T>
+where
+ T: Real,
+{
+ /// Returns (sin(self), cos(self)).
+ pub fn sin_cos(self) -> (T, T) {
+ self.radians.sin_cos()
+ }
+}
+
+impl<T> Angle<T>
+where
+ T: Zero,
+{
+ pub fn zero() -> Self {
+ Angle::radians(T::zero())
+ }
+}
+
+impl<T> Angle<T>
+where
+ T: FloatConst + Add<Output = T>,
+{
+ pub fn pi() -> Self {
+ Angle::radians(T::PI())
+ }
+
+ pub fn two_pi() -> Self {
+ Angle::radians(T::PI() + T::PI())
+ }
+
+ pub fn frac_pi_2() -> Self {
+ Angle::radians(T::FRAC_PI_2())
+ }
+
+ pub fn frac_pi_3() -> Self {
+ Angle::radians(T::FRAC_PI_3())
+ }
+
+ pub fn frac_pi_4() -> Self {
+ Angle::radians(T::FRAC_PI_4())
+ }
+}
+
+impl<T> Angle<T>
+where
+ T: NumCast + Copy,
+{
+ /// Cast from one numeric representation to another.
+ #[inline]
+ pub fn cast<NewT: NumCast>(&self) -> Angle<NewT> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another.
+ pub fn try_cast<NewT: NumCast>(&self) -> Option<Angle<NewT>> {
+ NumCast::from(self.radians).map(|radians| Angle { radians })
+ }
+
+ // Convenience functions for common casts.
+
+ /// Cast angle to `f32`.
+ #[inline]
+ pub fn to_f32(&self) -> Angle<f32> {
+ self.cast()
+ }
+
+ /// Cast angle `f64`.
+ #[inline]
+ pub fn to_f64(&self) -> Angle<f64> {
+ self.cast()
+ }
+}
+
+impl<T: Add<T, Output = T>> Add for Angle<T> {
+ type Output = Self;
+ fn add(self, other: Self) -> Self {
+ Self::radians(self.radians + other.radians)
+ }
+}
+
+impl<T: Copy + Add<T, Output = T>> Add<&Self> for Angle<T> {
+ type Output = Self;
+ fn add(self, other: &Self) -> Self {
+ Self::radians(self.radians + other.radians)
+ }
+}
+
+impl<T: Add + Zero> Sum for Angle<T> {
+ fn sum<I: Iterator<Item=Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+impl<'a, T: 'a + Add + Copy + Zero> Sum<&'a Self> for Angle<T> {
+ fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+impl<T: AddAssign<T>> AddAssign for Angle<T> {
+ fn add_assign(&mut self, other: Angle<T>) {
+ self.radians += other.radians;
+ }
+}
+
+impl<T: Sub<T, Output = T>> Sub<Angle<T>> for Angle<T> {
+ type Output = Angle<T>;
+ fn sub(self, other: Angle<T>) -> <Self as Sub>::Output {
+ Angle::radians(self.radians - other.radians)
+ }
+}
+
+impl<T: SubAssign<T>> SubAssign for Angle<T> {
+ fn sub_assign(&mut self, other: Angle<T>) {
+ self.radians -= other.radians;
+ }
+}
+
+impl<T: Div<T, Output = T>> Div<Angle<T>> for Angle<T> {
+ type Output = T;
+ #[inline]
+ fn div(self, other: Angle<T>) -> T {
+ self.radians / other.radians
+ }
+}
+
+impl<T: Div<T, Output = T>> Div<T> for Angle<T> {
+ type Output = Angle<T>;
+ #[inline]
+ fn div(self, factor: T) -> Angle<T> {
+ Angle::radians(self.radians / factor)
+ }
+}
+
+impl<T: DivAssign<T>> DivAssign<T> for Angle<T> {
+ fn div_assign(&mut self, factor: T) {
+ self.radians /= factor;
+ }
+}
+
+impl<T: Mul<T, Output = T>> Mul<T> for Angle<T> {
+ type Output = Angle<T>;
+ #[inline]
+ fn mul(self, factor: T) -> Angle<T> {
+ Angle::radians(self.radians * factor)
+ }
+}
+
+impl<T: MulAssign<T>> MulAssign<T> for Angle<T> {
+ fn mul_assign(&mut self, factor: T) {
+ self.radians *= factor;
+ }
+}
+
+impl<T: Neg<Output = T>> Neg for Angle<T> {
+ type Output = Self;
+ fn neg(self) -> Self {
+ Angle::radians(-self.radians)
+ }
+}
+
+impl<T: ApproxEq<T>> ApproxEq<T> for Angle<T> {
+ #[inline]
+ fn approx_epsilon() -> T {
+ T::approx_epsilon()
+ }
+
+ #[inline]
+ fn approx_eq_eps(&self, other: &Angle<T>, approx_epsilon: &T) -> bool {
+ self.radians.approx_eq_eps(&other.radians, approx_epsilon)
+ }
+}
+
+#[test]
+fn wrap_angles() {
+ use core::f32::consts::{FRAC_PI_2, PI};
+
+ assert!(Angle::radians(0.0).positive().approx_eq(&Angle::zero()));
+ assert!(Angle::radians(FRAC_PI_2)
+ .positive()
+ .approx_eq(&Angle::frac_pi_2()));
+ assert!(Angle::radians(-FRAC_PI_2)
+ .positive()
+ .approx_eq(&Angle::radians(3.0 * FRAC_PI_2)));
+ assert!(Angle::radians(3.0 * FRAC_PI_2)
+ .positive()
+ .approx_eq(&Angle::radians(3.0 * FRAC_PI_2)));
+ assert!(Angle::radians(5.0 * FRAC_PI_2)
+ .positive()
+ .approx_eq(&Angle::frac_pi_2()));
+ assert!(Angle::radians(2.0 * PI)
+ .positive()
+ .approx_eq(&Angle::zero()));
+ assert!(Angle::radians(-2.0 * PI)
+ .positive()
+ .approx_eq(&Angle::zero()));
+ assert!(Angle::radians(PI).positive().approx_eq(&Angle::pi()));
+ assert!(Angle::radians(-PI).positive().approx_eq(&Angle::pi()));
+
+ assert!(Angle::radians(FRAC_PI_2)
+ .signed()
+ .approx_eq(&Angle::frac_pi_2()));
+ assert!(Angle::radians(3.0 * FRAC_PI_2)
+ .signed()
+ .approx_eq(&-Angle::frac_pi_2()));
+ assert!(Angle::radians(5.0 * FRAC_PI_2)
+ .signed()
+ .approx_eq(&Angle::frac_pi_2()));
+ assert!(Angle::radians(2.0 * PI).signed().approx_eq(&Angle::zero()));
+ assert!(Angle::radians(-2.0 * PI).signed().approx_eq(&Angle::zero()));
+ assert!(Angle::radians(-PI).signed().approx_eq(&Angle::pi()));
+ assert!(Angle::radians(PI).signed().approx_eq(&Angle::pi()));
+}
+
+#[test]
+fn lerp() {
+ type A = Angle<f32>;
+
+ let a = A::radians(1.0);
+ let b = A::radians(2.0);
+ assert!(a.lerp(b, 0.25).approx_eq(&Angle::radians(1.25)));
+ assert!(a.lerp(b, 0.5).approx_eq(&Angle::radians(1.5)));
+ assert!(a.lerp(b, 0.75).approx_eq(&Angle::radians(1.75)));
+ assert!(a
+ .lerp(b + A::two_pi(), 0.75)
+ .approx_eq(&Angle::radians(1.75)));
+ assert!(a
+ .lerp(b - A::two_pi(), 0.75)
+ .approx_eq(&Angle::radians(1.75)));
+ assert!(a
+ .lerp(b + A::two_pi() * 5.0, 0.75)
+ .approx_eq(&Angle::radians(1.75)));
+}
+
+#[test]
+fn sum() {
+ type A = Angle<f32>;
+ let angles = [A::radians(1.0), A::radians(2.0), A::radians(3.0)];
+ let sum = A::radians(6.0);
+ assert_eq!(angles.iter().sum::<A>(), sum);
+}
diff --git a/third_party/rust/euclid/src/approxeq.rs b/third_party/rust/euclid/src/approxeq.rs
new file mode 100644
index 0000000000..911f5268a4
--- /dev/null
+++ b/third_party/rust/euclid/src/approxeq.rs
@@ -0,0 +1,42 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+/// Trait for testing approximate equality
+pub trait ApproxEq<Eps> {
+ /// Default epsilon value
+ fn approx_epsilon() -> Eps;
+
+ /// Returns `true` is this object is approximately equal to the other one, using
+ /// a provided epsilon value.
+ fn approx_eq_eps(&self, other: &Self, approx_epsilon: &Eps) -> bool;
+
+ /// Returns `true` is this object is approximately equal to the other one, using
+ /// the `approx_epsilon()` epsilon value.
+ fn approx_eq(&self, other: &Self) -> bool {
+ self.approx_eq_eps(other, &Self::approx_epsilon())
+ }
+}
+
+macro_rules! approx_eq {
+ ($ty:ty, $eps:expr) => {
+ impl ApproxEq<$ty> for $ty {
+ #[inline]
+ fn approx_epsilon() -> $ty {
+ $eps
+ }
+ #[inline]
+ fn approx_eq_eps(&self, other: &$ty, approx_epsilon: &$ty) -> bool {
+ num_traits::Float::abs(*self - *other) < *approx_epsilon
+ }
+ }
+ };
+}
+
+approx_eq!(f32, 1.0e-6);
+approx_eq!(f64, 1.0e-6);
diff --git a/third_party/rust/euclid/src/approxord.rs b/third_party/rust/euclid/src/approxord.rs
new file mode 100644
index 0000000000..db6207da39
--- /dev/null
+++ b/third_party/rust/euclid/src/approxord.rs
@@ -0,0 +1,44 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Utilities for testing approximate ordering - especially true for
+//! floating point types, where NaN's cannot be ordered.
+
+pub fn min<T: PartialOrd>(x: T, y: T) -> T {
+ if x <= y {
+ x
+ } else {
+ y
+ }
+}
+
+pub fn max<T: PartialOrd>(x: T, y: T) -> T {
+ if x >= y {
+ x
+ } else {
+ y
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_min() {
+ assert!(min(0u32, 1u32) == 0u32);
+ assert!(min(-1.0f32, 0.0f32) == -1.0f32);
+ }
+
+ #[test]
+ fn test_max() {
+ assert!(max(0u32, 1u32) == 1u32);
+ assert!(max(-1.0f32, 0.0f32) == 0.0f32);
+ }
+}
diff --git a/third_party/rust/euclid/src/box2d.rs b/third_party/rust/euclid/src/box2d.rs
new file mode 100644
index 0000000000..b59dacf162
--- /dev/null
+++ b/third_party/rust/euclid/src/box2d.rs
@@ -0,0 +1,903 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::UnknownUnit;
+use crate::approxord::{max, min};
+use crate::num::*;
+use crate::point::{point2, Point2D};
+use crate::rect::Rect;
+use crate::scale::Scale;
+use crate::side_offsets::SideOffsets2D;
+use crate::size::Size2D;
+use crate::vector::{vec2, Vector2D};
+
+use num_traits::{NumCast, Float};
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+use core::borrow::Borrow;
+use core::cmp::PartialOrd;
+use core::fmt;
+use core::hash::{Hash, Hasher};
+use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Sub, Range};
+
+/// A 2d axis aligned rectangle represented by its minimum and maximum coordinates.
+///
+/// # Representation
+///
+/// This struct is similar to [`Rect`], but stores rectangle as two endpoints
+/// instead of origin point and size. Such representation has several advantages over
+/// [`Rect`] representation:
+/// - Several operations are more efficient with `Box2D`, including [`intersection`],
+/// [`union`], and point-in-rect.
+/// - The representation is less susceptible to overflow. With [`Rect`], computation
+/// of second point can overflow for a large range of values of origin and size.
+/// However, with `Box2D`, computation of [`size`] cannot overflow if the coordinates
+/// are signed and the resulting size is unsigned.
+///
+/// A known disadvantage of `Box2D` is that translating the rectangle requires translating
+/// both points, whereas translating [`Rect`] only requires translating one point.
+///
+/// # Empty box
+///
+/// A box is considered empty (see [`is_empty`]) if any of the following is true:
+/// - it's area is empty,
+/// - it's area is negative (`min.x > max.x` or `min.y > max.y`),
+/// - it contains NaNs.
+///
+/// [`Rect`]: struct.Rect.html
+/// [`intersection`]: #method.intersection
+/// [`is_empty`]: #method.is_empty
+/// [`union`]: #method.union
+/// [`size`]: #method.size
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+ feature = "serde",
+ serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
+)]
+pub struct Box2D<T, U> {
+ pub min: Point2D<T, U>,
+ pub max: Point2D<T, U>,
+}
+
+impl<T: Hash, U> Hash for Box2D<T, U> {
+ fn hash<H: Hasher>(&self, h: &mut H) {
+ self.min.hash(h);
+ self.max.hash(h);
+ }
+}
+
+impl<T: Copy, U> Copy for Box2D<T, U> {}
+
+impl<T: Clone, U> Clone for Box2D<T, U> {
+ fn clone(&self) -> Self {
+ Self::new(self.min.clone(), self.max.clone())
+ }
+}
+
+impl<T: PartialEq, U> PartialEq for Box2D<T, U> {
+ fn eq(&self, other: &Self) -> bool {
+ self.min.eq(&other.min) && self.max.eq(&other.max)
+ }
+}
+
+impl<T: Eq, U> Eq for Box2D<T, U> {}
+
+impl<T: fmt::Debug, U> fmt::Debug for Box2D<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_tuple("Box2D")
+ .field(&self.min)
+ .field(&self.max)
+ .finish()
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for Box2D<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for Box2D<T, U> {}
+
+impl<T, U> Box2D<T, U> {
+ /// Constructor.
+ #[inline]
+ pub const fn new(min: Point2D<T, U>, max: Point2D<T, U>) -> Self {
+ Box2D { min, max }
+ }
+
+ /// Constructor.
+ #[inline]
+ pub fn from_origin_and_size(origin: Point2D<T, U>, size: Size2D<T, U>) -> Self
+ where
+ T: Copy + Add<T, Output = T>
+ {
+ Box2D {
+ min: origin,
+ max: point2(origin.x + size.width, origin.y + size.height),
+ }
+ }
+
+ /// Creates a Box2D of the given size, at offset zero.
+ #[inline]
+ pub fn from_size(size: Size2D<T, U>) -> Self where T: Zero {
+ Box2D {
+ min: Point2D::zero(),
+ max: point2(size.width, size.height),
+ }
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: PartialOrd,
+{
+ /// Returns true if the box has a negative area.
+ ///
+ /// The common interpretation for a negative box is to consider it empty. It can be obtained
+ /// by calculating the intersection of two boxes that do not intersect.
+ #[inline]
+ pub fn is_negative(&self) -> bool {
+ self.max.x < self.min.x || self.max.y < self.min.y
+ }
+
+ /// Returns true if the size is zero, negative or NaN.
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ !(self.max.x > self.min.x && self.max.y > self.min.y)
+ }
+
+ /// Returns `true` if the two boxes intersect.
+ #[inline]
+ pub fn intersects(&self, other: &Self) -> bool {
+ self.min.x < other.max.x
+ && self.max.x > other.min.x
+ && self.min.y < other.max.y
+ && self.max.y > other.min.y
+ }
+
+ /// Returns `true` if this box contains the point. Points are considered
+ /// in the box if they are on the front, left or top faces, but outside if they
+ /// are on the back, right or bottom faces.
+ #[inline]
+ pub fn contains(&self, p: Point2D<T, U>) -> bool {
+ self.min.x <= p.x && p.x < self.max.x && self.min.y <= p.y && p.y < self.max.y
+ }
+
+ /// Returns `true` if this box contains the interior of the other box. Always
+ /// returns `true` if other is empty, and always returns `false` if other is
+ /// nonempty but this box is empty.
+ #[inline]
+ pub fn contains_box(&self, other: &Self) -> bool {
+ other.is_empty()
+ || (self.min.x <= other.min.x
+ && other.max.x <= self.max.x
+ && self.min.y <= other.min.y
+ && other.max.y <= self.max.y)
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Copy + PartialOrd,
+{
+ #[inline]
+ pub fn to_non_empty(&self) -> Option<Self> {
+ if self.is_empty() {
+ return None;
+ }
+
+ Some(*self)
+ }
+
+ /// Computes the intersection of two boxes, returning `None` if the boxes do not intersect.
+ #[inline]
+ pub fn intersection(&self, other: &Self) -> Option<Self> {
+ let b = self.intersection_unchecked(other);
+
+ if b.is_empty() {
+ return None;
+ }
+
+ Some(b)
+ }
+
+ /// Computes the intersection of two boxes without check whether they do intersect.
+ ///
+ /// The result is a negative box if the boxes do not intersect.
+ /// This can be useful for computing the intersection of more than two boxes, as
+ /// it is possible to chain multiple intersection_unchecked calls and check for
+ /// empty/negative result at the end.
+ #[inline]
+ pub fn intersection_unchecked(&self, other: &Self) -> Self {
+ Box2D {
+ min: point2(max(self.min.x, other.min.x), max(self.min.y, other.min.y)),
+ max: point2(min(self.max.x, other.max.x), min(self.max.y, other.max.y)),
+ }
+ }
+
+ /// Computes the union of two boxes.
+ ///
+ /// If either of the boxes is empty, the other one is returned.
+ #[inline]
+ pub fn union(&self, other: &Self) -> Self {
+ if other.is_empty() {
+ return *self;
+ }
+ if self.is_empty() {
+ return *other;
+ }
+
+ Box2D {
+ min: point2(min(self.min.x, other.min.x), min(self.min.y, other.min.y)),
+ max: point2(max(self.max.x, other.max.x), max(self.max.y, other.max.y)),
+ }
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Copy + Add<T, Output = T>,
+{
+ /// Returns the same box, translated by a vector.
+ #[inline]
+ pub fn translate(&self, by: Vector2D<T, U>) -> Self {
+ Box2D {
+ min: self.min + by,
+ max: self.max + by,
+ }
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Copy + Sub<T, Output = T>,
+{
+ #[inline]
+ pub fn size(&self) -> Size2D<T, U> {
+ (self.max - self.min).to_size()
+ }
+
+ /// Change the size of the box by adjusting the max endpoint
+ /// without modifying the min endpoint.
+ #[inline]
+ pub fn set_size(&mut self, size: Size2D<T, U>) {
+ let diff = (self.size() - size).to_vector();
+ self.max -= diff;
+ }
+
+ #[inline]
+ pub fn width(&self) -> T {
+ self.max.x - self.min.x
+ }
+
+ #[inline]
+ pub fn height(&self) -> T {
+ self.max.y - self.min.y
+ }
+
+ #[inline]
+ pub fn to_rect(&self) -> Rect<T, U> {
+ Rect {
+ origin: self.min,
+ size: self.size(),
+ }
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Copy + Add<T, Output = T> + Sub<T, Output = T>,
+{
+ /// Inflates the box by the specified sizes on each side respectively.
+ #[inline]
+ #[must_use]
+ pub fn inflate(&self, width: T, height: T) -> Self {
+ Box2D {
+ min: point2(self.min.x - width, self.min.y - height),
+ max: point2(self.max.x + width, self.max.y + height),
+ }
+ }
+
+ /// Calculate the size and position of an inner box.
+ ///
+ /// Subtracts the side offsets from all sides. The horizontal, vertical
+ /// and applicate offsets must not be larger than the original side length.
+ pub fn inner_box(&self, offsets: SideOffsets2D<T, U>) -> Self {
+ Box2D {
+ min: self.min + vec2(offsets.left, offsets.top),
+ max: self.max - vec2(offsets.right, offsets.bottom),
+ }
+ }
+
+ /// Calculate the b and position of an outer box.
+ ///
+ /// Add the offsets to all sides. The expanded box is returned.
+ pub fn outer_box(&self, offsets: SideOffsets2D<T, U>) -> Self {
+ Box2D {
+ min: self.min - vec2(offsets.left, offsets.top),
+ max: self.max + vec2(offsets.right, offsets.bottom),
+ }
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Copy + Zero + PartialOrd,
+{
+ /// Returns the smallest box containing all of the provided points.
+ pub fn from_points<I>(points: I) -> Self
+ where
+ I: IntoIterator,
+ I::Item: Borrow<Point2D<T, U>>,
+ {
+ let mut points = points.into_iter();
+
+ let (mut min_x, mut min_y) = match points.next() {
+ Some(first) => first.borrow().to_tuple(),
+ None => return Box2D::zero(),
+ };
+
+ let (mut max_x, mut max_y) = (min_x, min_y);
+ for point in points {
+ let p = point.borrow();
+ if p.x < min_x {
+ min_x = p.x
+ }
+ if p.x > max_x {
+ max_x = p.x
+ }
+ if p.y < min_y {
+ min_y = p.y
+ }
+ if p.y > max_y {
+ max_y = p.y
+ }
+ }
+
+ Box2D {
+ min: point2(min_x, min_y),
+ max: point2(max_x, max_y),
+ }
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
+{
+ /// Linearly interpolate between this box and another box.
+ #[inline]
+ pub fn lerp(&self, other: Self, t: T) -> Self {
+ Self::new(self.min.lerp(other.min, t), self.max.lerp(other.max, t))
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Copy + One + Add<Output = T> + Div<Output = T>,
+{
+ pub fn center(&self) -> Point2D<T, U> {
+ let two = T::one() + T::one();
+ (self.min + self.max.to_vector()) / two
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Copy + Mul<T, Output = T> + Sub<T, Output = T>,
+{
+ #[inline]
+ pub fn area(&self) -> T {
+ let size = self.size();
+ size.width * size.height
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Zero,
+{
+ /// Constructor, setting all sides to zero.
+ pub fn zero() -> Self {
+ Box2D::new(Point2D::zero(), Point2D::zero())
+ }
+}
+
+impl<T: Copy + Mul, U> Mul<T> for Box2D<T, U> {
+ type Output = Box2D<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ Box2D::new(self.min * scale, self.max * scale)
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<T> for Box2D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: T) {
+ *self *= Scale::new(scale);
+ }
+}
+
+impl<T: Copy + Div, U> Div<T> for Box2D<T, U> {
+ type Output = Box2D<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ Box2D::new(self.min / scale, self.max / scale)
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<T> for Box2D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: T) {
+ *self /= Scale::new(scale);
+ }
+}
+
+impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Box2D<T, U1> {
+ type Output = Box2D<T::Output, U2>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ Box2D::new(self.min * scale, self.max * scale)
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Box2D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: Scale<T, U, U>) {
+ self.min *= scale;
+ self.max *= scale;
+ }
+}
+
+impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Box2D<T, U2> {
+ type Output = Box2D<T::Output, U1>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ Box2D::new(self.min / scale, self.max / scale)
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Box2D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: Scale<T, U, U>) {
+ self.min /= scale;
+ self.max /= scale;
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Copy,
+{
+ #[inline]
+ pub fn x_range(&self) -> Range<T> {
+ self.min.x..self.max.x
+ }
+
+ #[inline]
+ pub fn y_range(&self) -> Range<T> {
+ self.min.y..self.max.y
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(&self) -> Box2D<T, UnknownUnit> {
+ Box2D::new(self.min.to_untyped(), self.max.to_untyped())
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(c: &Box2D<T, UnknownUnit>) -> Box2D<T, U> {
+ Box2D::new(Point2D::from_untyped(c.min), Point2D::from_untyped(c.max))
+ }
+
+ /// Cast the unit
+ #[inline]
+ pub fn cast_unit<V>(&self) -> Box2D<T, V> {
+ Box2D::new(self.min.cast_unit(), self.max.cast_unit())
+ }
+
+ #[inline]
+ pub fn scale<S: Copy>(&self, x: S, y: S) -> Self
+ where
+ T: Mul<S, Output = T>,
+ {
+ Box2D {
+ min: point2(self.min.x * x, self.min.y * y),
+ max: point2(self.max.x * x, self.max.y * y),
+ }
+ }
+}
+
+impl<T: NumCast + Copy, U> Box2D<T, U> {
+ /// Cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using round(), round_in or round_out() before casting.
+ #[inline]
+ pub fn cast<NewT: NumCast>(&self) -> Box2D<NewT, U> {
+ Box2D::new(self.min.cast(), self.max.cast())
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using round(), round_in or round_out() before casting.
+ pub fn try_cast<NewT: NumCast>(&self) -> Option<Box2D<NewT, U>> {
+ match (self.min.try_cast(), self.max.try_cast()) {
+ (Some(a), Some(b)) => Some(Box2D::new(a, b)),
+ _ => None,
+ }
+ }
+
+ // Convenience functions for common casts
+
+ /// Cast into an `f32` box.
+ #[inline]
+ pub fn to_f32(&self) -> Box2D<f32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `f64` box.
+ #[inline]
+ pub fn to_f64(&self) -> Box2D<f64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `usize` box, truncating decimals if any.
+ ///
+ /// When casting from floating point boxes, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_usize(&self) -> Box2D<usize, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u32` box, truncating decimals if any.
+ ///
+ /// When casting from floating point boxes, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_u32(&self) -> Box2D<u32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i32` box, truncating decimals if any.
+ ///
+ /// When casting from floating point boxes, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_i32(&self) -> Box2D<i32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i64` box, truncating decimals if any.
+ ///
+ /// When casting from floating point boxes, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_i64(&self) -> Box2D<i64, U> {
+ self.cast()
+ }
+}
+
+impl<T: Float, U> Box2D<T, U> {
+ /// Returns true if all members are finite.
+ #[inline]
+ pub fn is_finite(self) -> bool {
+ self.min.is_finite() && self.max.is_finite()
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Round,
+{
+ /// Return a box with edges rounded to integer coordinates, such that
+ /// the returned box has the same set of pixel centers as the original
+ /// one.
+ /// Values equal to 0.5 round up.
+ /// Suitable for most places where integral device coordinates
+ /// are needed, but note that any translation should be applied first to
+ /// avoid pixel rounding errors.
+ /// Note that this is *not* rounding to nearest integer if the values are negative.
+ /// They are always rounding as floor(n + 0.5).
+ #[must_use]
+ pub fn round(&self) -> Self {
+ Box2D::new(self.min.round(), self.max.round())
+ }
+}
+
+impl<T, U> Box2D<T, U>
+where
+ T: Floor + Ceil,
+{
+ /// Return a box with faces/edges rounded to integer coordinates, such that
+ /// the original box contains the resulting box.
+ #[must_use]
+ pub fn round_in(&self) -> Self {
+ let min = self.min.ceil();
+ let max = self.max.floor();
+ Box2D { min, max }
+ }
+
+ /// Return a box with faces/edges rounded to integer coordinates, such that
+ /// the original box is contained in the resulting box.
+ #[must_use]
+ pub fn round_out(&self) -> Self {
+ let min = self.min.floor();
+ let max = self.max.ceil();
+ Box2D { min, max }
+ }
+}
+
+impl<T, U> From<Size2D<T, U>> for Box2D<T, U>
+where
+ T: Copy + Zero + PartialOrd,
+{
+ fn from(b: Size2D<T, U>) -> Self {
+ Self::from_size(b)
+ }
+}
+
+impl<T: Default, U> Default for Box2D<T, U> {
+ fn default() -> Self {
+ Box2D {
+ min: Default::default(),
+ max: Default::default(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::default::Box2D;
+ use crate::side_offsets::SideOffsets2D;
+ use crate::{point2, size2, vec2, Point2D};
+ //use super::*;
+
+ #[test]
+ fn test_size() {
+ let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0));
+ assert_eq!(b.size().width, 20.0);
+ assert_eq!(b.size().height, 20.0);
+ }
+
+ #[test]
+ fn test_width_height() {
+ let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0));
+ assert!(b.width() == 20.0);
+ assert!(b.height() == 20.0);
+ }
+
+ #[test]
+ fn test_center() {
+ let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0));
+ assert_eq!(b.center(), Point2D::zero());
+ }
+
+ #[test]
+ fn test_area() {
+ let b = Box2D::new(point2(-10.0, -10.0), point2(10.0, 10.0));
+ assert_eq!(b.area(), 400.0);
+ }
+
+ #[test]
+ fn test_from_points() {
+ let b = Box2D::from_points(&[point2(50.0, 160.0), point2(100.0, 25.0)]);
+ assert_eq!(b.min, point2(50.0, 25.0));
+ assert_eq!(b.max, point2(100.0, 160.0));
+ }
+
+ #[test]
+ fn test_round_in() {
+ let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round_in();
+ assert_eq!(b.min.x, -25.0);
+ assert_eq!(b.min.y, -40.0);
+ assert_eq!(b.max.x, 60.0);
+ assert_eq!(b.max.y, 36.0);
+ }
+
+ #[test]
+ fn test_round_out() {
+ let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round_out();
+ assert_eq!(b.min.x, -26.0);
+ assert_eq!(b.min.y, -41.0);
+ assert_eq!(b.max.x, 61.0);
+ assert_eq!(b.max.y, 37.0);
+ }
+
+ #[test]
+ fn test_round() {
+ let b = Box2D::from_points(&[point2(-25.5, -40.4), point2(60.3, 36.5)]).round();
+ assert_eq!(b.min.x, -25.0);
+ assert_eq!(b.min.y, -40.0);
+ assert_eq!(b.max.x, 60.0);
+ assert_eq!(b.max.y, 37.0);
+ }
+
+ #[test]
+ fn test_from_size() {
+ let b = Box2D::from_size(size2(30.0, 40.0));
+ assert!(b.min == Point2D::zero());
+ assert!(b.size().width == 30.0);
+ assert!(b.size().height == 40.0);
+ }
+
+ #[test]
+ fn test_inner_box() {
+ let b = Box2D::from_points(&[point2(50.0, 25.0), point2(100.0, 160.0)]);
+ let b = b.inner_box(SideOffsets2D::new(10.0, 20.0, 5.0, 10.0));
+ assert_eq!(b.max.x, 80.0);
+ assert_eq!(b.max.y, 155.0);
+ assert_eq!(b.min.x, 60.0);
+ assert_eq!(b.min.y, 35.0);
+ }
+
+ #[test]
+ fn test_outer_box() {
+ let b = Box2D::from_points(&[point2(50.0, 25.0), point2(100.0, 160.0)]);
+ let b = b.outer_box(SideOffsets2D::new(10.0, 20.0, 5.0, 10.0));
+ assert_eq!(b.max.x, 120.0);
+ assert_eq!(b.max.y, 165.0);
+ assert_eq!(b.min.x, 40.0);
+ assert_eq!(b.min.y, 15.0);
+ }
+
+ #[test]
+ fn test_translate() {
+ let size = size2(15.0, 15.0);
+ let mut center = (size / 2.0).to_vector().to_point();
+ let b = Box2D::from_size(size);
+ assert_eq!(b.center(), center);
+ let translation = vec2(10.0, 2.5);
+ let b = b.translate(translation);
+ center += translation;
+ assert_eq!(b.center(), center);
+ assert_eq!(b.max.x, 25.0);
+ assert_eq!(b.max.y, 17.5);
+ assert_eq!(b.min.x, 10.0);
+ assert_eq!(b.min.y, 2.5);
+ }
+
+ #[test]
+ fn test_union() {
+ let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(0.0, 20.0)]);
+ let b2 = Box2D::from_points(&[point2(0.0, 20.0), point2(20.0, -20.0)]);
+ let b = b1.union(&b2);
+ assert_eq!(b.max.x, 20.0);
+ assert_eq!(b.max.y, 20.0);
+ assert_eq!(b.min.x, -20.0);
+ assert_eq!(b.min.y, -20.0);
+ }
+
+ #[test]
+ fn test_intersects() {
+ let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]);
+ let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]);
+ assert!(b1.intersects(&b2));
+ }
+
+ #[test]
+ fn test_intersection_unchecked() {
+ let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]);
+ let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]);
+ let b = b1.intersection_unchecked(&b2);
+ assert_eq!(b.max.x, 10.0);
+ assert_eq!(b.max.y, 20.0);
+ assert_eq!(b.min.x, -10.0);
+ assert_eq!(b.min.y, -20.0);
+ }
+
+ #[test]
+ fn test_intersection() {
+ let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(10.0, 20.0)]);
+ let b2 = Box2D::from_points(&[point2(-10.0, 20.0), point2(15.0, -20.0)]);
+ assert!(b1.intersection(&b2).is_some());
+
+ let b1 = Box2D::from_points(&[point2(-15.0, -20.0), point2(-10.0, 20.0)]);
+ let b2 = Box2D::from_points(&[point2(10.0, 20.0), point2(15.0, -20.0)]);
+ assert!(b1.intersection(&b2).is_none());
+ }
+
+ #[test]
+ fn test_scale() {
+ let b = Box2D::from_points(&[point2(-10.0, -10.0), point2(10.0, 10.0)]);
+ let b = b.scale(0.5, 0.5);
+ assert_eq!(b.max.x, 5.0);
+ assert_eq!(b.max.y, 5.0);
+ assert_eq!(b.min.x, -5.0);
+ assert_eq!(b.min.y, -5.0);
+ }
+
+ #[test]
+ fn test_lerp() {
+ let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(-10.0, -10.0)]);
+ let b2 = Box2D::from_points(&[point2(10.0, 10.0), point2(20.0, 20.0)]);
+ let b = b1.lerp(b2, 0.5);
+ assert_eq!(b.center(), Point2D::zero());
+ assert_eq!(b.size().width, 10.0);
+ assert_eq!(b.size().height, 10.0);
+ }
+
+ #[test]
+ fn test_contains() {
+ let b = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]);
+ assert!(b.contains(point2(-15.3, 10.5)));
+ }
+
+ #[test]
+ fn test_contains_box() {
+ let b1 = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]);
+ let b2 = Box2D::from_points(&[point2(-14.3, -16.5), point2(6.7, 17.6)]);
+ assert!(b1.contains_box(&b2));
+ }
+
+ #[test]
+ fn test_inflate() {
+ let b = Box2D::from_points(&[point2(-20.0, -20.0), point2(20.0, 20.0)]);
+ let b = b.inflate(10.0, 5.0);
+ assert_eq!(b.size().width, 60.0);
+ assert_eq!(b.size().height, 50.0);
+ assert_eq!(b.center(), Point2D::zero());
+ }
+
+ #[test]
+ fn test_is_empty() {
+ for i in 0..2 {
+ let mut coords_neg = [-20.0, -20.0];
+ let mut coords_pos = [20.0, 20.0];
+ coords_neg[i] = 0.0;
+ coords_pos[i] = 0.0;
+ let b = Box2D::from_points(&[Point2D::from(coords_neg), Point2D::from(coords_pos)]);
+ assert!(b.is_empty());
+ }
+ }
+
+ #[test]
+ fn test_nan_empty() {
+ use std::f32::NAN;
+ assert!(Box2D { min: point2(NAN, 2.0), max: point2(1.0, 3.0) }.is_empty());
+ assert!(Box2D { min: point2(0.0, NAN), max: point2(1.0, 2.0) }.is_empty());
+ assert!(Box2D { min: point2(1.0, -2.0), max: point2(NAN, 2.0) }.is_empty());
+ assert!(Box2D { min: point2(1.0, -2.0), max: point2(0.0, NAN) }.is_empty());
+ }
+
+ #[test]
+ fn test_from_origin_and_size() {
+ let b = Box2D::from_origin_and_size(point2(1.0, 2.0), size2(3.0, 4.0));
+ assert_eq!(b.min, point2(1.0, 2.0));
+ assert_eq!(b.size(), size2(3.0, 4.0));
+ }
+
+ #[test]
+ fn test_set_size() {
+ let mut b = Box2D {
+ min: point2(1.0, 2.0),
+ max: point2(3.0, 4.0),
+ };
+ b.set_size(size2(5.0, 6.0));
+
+ assert_eq!(b.min, point2(1.0, 2.0));
+ assert_eq!(b.size(), size2(5.0, 6.0));
+ }
+}
diff --git a/third_party/rust/euclid/src/box3d.rs b/third_party/rust/euclid/src/box3d.rs
new file mode 100644
index 0000000000..34123f321d
--- /dev/null
+++ b/third_party/rust/euclid/src/box3d.rs
@@ -0,0 +1,931 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::UnknownUnit;
+use crate::approxord::{max, min};
+use crate::num::*;
+use crate::point::{point3, Point3D};
+use crate::scale::Scale;
+use crate::size::Size3D;
+use crate::vector::Vector3D;
+
+use num_traits::{NumCast, Float};
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+use core::borrow::Borrow;
+use core::cmp::PartialOrd;
+use core::fmt;
+use core::hash::{Hash, Hasher};
+use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Sub, Range};
+
+/// An axis aligned 3D box represented by its minimum and maximum coordinates.
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+ feature = "serde",
+ serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
+)]
+pub struct Box3D<T, U> {
+ pub min: Point3D<T, U>,
+ pub max: Point3D<T, U>,
+}
+
+impl<T: Hash, U> Hash for Box3D<T, U> {
+ fn hash<H: Hasher>(&self, h: &mut H) {
+ self.min.hash(h);
+ self.max.hash(h);
+ }
+}
+
+impl<T: Copy, U> Copy for Box3D<T, U> {}
+
+impl<T: Clone, U> Clone for Box3D<T, U> {
+ fn clone(&self) -> Self {
+ Self::new(self.min.clone(), self.max.clone())
+ }
+}
+
+impl<T: PartialEq, U> PartialEq for Box3D<T, U> {
+ fn eq(&self, other: &Self) -> bool {
+ self.min.eq(&other.min) && self.max.eq(&other.max)
+ }
+}
+
+impl<T: Eq, U> Eq for Box3D<T, U> {}
+
+impl<T: fmt::Debug, U> fmt::Debug for Box3D<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_tuple("Box3D")
+ .field(&self.min)
+ .field(&self.max)
+ .finish()
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for Box3D<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for Box3D<T, U> {}
+
+impl<T, U> Box3D<T, U> {
+ /// Constructor.
+ #[inline]
+ pub const fn new(min: Point3D<T, U>, max: Point3D<T, U>) -> Self {
+ Box3D { min, max }
+ }
+
+ /// Creates a Box3D of the given size, at offset zero.
+ #[inline]
+ pub fn from_size(size: Size3D<T, U>) -> Self where T: Zero {
+ Box3D {
+ min: Point3D::zero(),
+ max: point3(size.width, size.height, size.depth),
+ }
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: PartialOrd,
+{
+ /// Returns true if the box has a negative volume.
+ ///
+ /// The common interpretation for a negative box is to consider it empty. It can be obtained
+ /// by calculating the intersection of two boxes that do not intersect.
+ #[inline]
+ pub fn is_negative(&self) -> bool {
+ self.max.x < self.min.x || self.max.y < self.min.y || self.max.z < self.min.z
+ }
+
+ /// Returns true if the size is zero, negative or NaN.
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ !(self.max.x > self.min.x && self.max.y > self.min.y && self.max.z > self.min.z)
+ }
+
+ #[inline]
+ pub fn intersects(&self, other: &Self) -> bool {
+ self.min.x < other.max.x
+ && self.max.x > other.min.x
+ && self.min.y < other.max.y
+ && self.max.y > other.min.y
+ && self.min.z < other.max.z
+ && self.max.z > other.min.z
+ }
+
+ /// Returns `true` if this box3d contains the point. Points are considered
+ /// in the box3d if they are on the front, left or top faces, but outside if they
+ /// are on the back, right or bottom faces.
+ #[inline]
+ pub fn contains(&self, other: Point3D<T, U>) -> bool {
+ self.min.x <= other.x
+ && other.x < self.max.x
+ && self.min.y <= other.y
+ && other.y < self.max.y
+ && self.min.z <= other.z
+ && other.z < self.max.z
+ }
+
+ /// Returns `true` if this box3d contains the interior of the other box3d. Always
+ /// returns `true` if other is empty, and always returns `false` if other is
+ /// nonempty but this box3d is empty.
+ #[inline]
+ pub fn contains_box(&self, other: &Self) -> bool {
+ other.is_empty()
+ || (self.min.x <= other.min.x
+ && other.max.x <= self.max.x
+ && self.min.y <= other.min.y
+ && other.max.y <= self.max.y
+ && self.min.z <= other.min.z
+ && other.max.z <= self.max.z)
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Copy + PartialOrd,
+{
+ #[inline]
+ pub fn to_non_empty(&self) -> Option<Self> {
+ if self.is_empty() {
+ return None;
+ }
+
+ Some(*self)
+ }
+
+ #[inline]
+ pub fn intersection(&self, other: &Self) -> Option<Self> {
+ let b = self.intersection_unchecked(other);
+
+ if b.is_empty() {
+ return None;
+ }
+
+ Some(b)
+ }
+
+ pub fn intersection_unchecked(&self, other: &Self) -> Self {
+ let intersection_min = Point3D::new(
+ max(self.min.x, other.min.x),
+ max(self.min.y, other.min.y),
+ max(self.min.z, other.min.z),
+ );
+
+ let intersection_max = Point3D::new(
+ min(self.max.x, other.max.x),
+ min(self.max.y, other.max.y),
+ min(self.max.z, other.max.z),
+ );
+
+ Box3D::new(intersection_min, intersection_max)
+ }
+
+ /// Computes the union of two boxes.
+ ///
+ /// If either of the boxes is empty, the other one is returned.
+ #[inline]
+ pub fn union(&self, other: &Self) -> Self {
+ if other.is_empty() {
+ return *self;
+ }
+ if self.is_empty() {
+ return *other;
+ }
+
+ Box3D::new(
+ Point3D::new(
+ min(self.min.x, other.min.x),
+ min(self.min.y, other.min.y),
+ min(self.min.z, other.min.z),
+ ),
+ Point3D::new(
+ max(self.max.x, other.max.x),
+ max(self.max.y, other.max.y),
+ max(self.max.z, other.max.z),
+ ),
+ )
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Copy + Add<T, Output = T>,
+{
+ /// Returns the same box3d, translated by a vector.
+ #[inline]
+ #[must_use]
+ pub fn translate(&self, by: Vector3D<T, U>) -> Self {
+ Box3D {
+ min: self.min + by,
+ max: self.max + by,
+ }
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Copy + Sub<T, Output = T>,
+{
+ #[inline]
+ pub fn size(&self) -> Size3D<T, U> {
+ Size3D::new(
+ self.max.x - self.min.x,
+ self.max.y - self.min.y,
+ self.max.z - self.min.z,
+ )
+ }
+
+ #[inline]
+ pub fn width(&self) -> T {
+ self.max.x - self.min.x
+ }
+
+ #[inline]
+ pub fn height(&self) -> T {
+ self.max.y - self.min.y
+ }
+
+ #[inline]
+ pub fn depth(&self) -> T {
+ self.max.z - self.min.z
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Copy + Add<T, Output = T> + Sub<T, Output = T>,
+{
+ /// Inflates the box by the specified sizes on each side respectively.
+ #[inline]
+ #[must_use]
+ pub fn inflate(&self, width: T, height: T, depth: T) -> Self {
+ Box3D::new(
+ Point3D::new(self.min.x - width, self.min.y - height, self.min.z - depth),
+ Point3D::new(self.max.x + width, self.max.y + height, self.max.z + depth),
+ )
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Copy + Zero + PartialOrd,
+{
+ /// Returns the smallest box containing all of the provided points.
+ pub fn from_points<I>(points: I) -> Self
+ where
+ I: IntoIterator,
+ I::Item: Borrow<Point3D<T, U>>,
+ {
+ let mut points = points.into_iter();
+
+ let (mut min_x, mut min_y, mut min_z) = match points.next() {
+ Some(first) => first.borrow().to_tuple(),
+ None => return Box3D::zero(),
+ };
+ let (mut max_x, mut max_y, mut max_z) = (min_x, min_y, min_z);
+
+ for point in points {
+ let p = point.borrow();
+ if p.x < min_x {
+ min_x = p.x
+ }
+ if p.x > max_x {
+ max_x = p.x
+ }
+ if p.y < min_y {
+ min_y = p.y
+ }
+ if p.y > max_y {
+ max_y = p.y
+ }
+ if p.z < min_z {
+ min_z = p.z
+ }
+ if p.z > max_z {
+ max_z = p.z
+ }
+ }
+
+ Box3D {
+ min: point3(min_x, min_y, min_z),
+ max: point3(max_x, max_y, max_z),
+ }
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
+{
+ /// Linearly interpolate between this box3d and another box3d.
+ #[inline]
+ pub fn lerp(&self, other: Self, t: T) -> Self {
+ Self::new(self.min.lerp(other.min, t), self.max.lerp(other.max, t))
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Copy + One + Add<Output = T> + Div<Output = T>,
+{
+ pub fn center(&self) -> Point3D<T, U> {
+ let two = T::one() + T::one();
+ (self.min + self.max.to_vector()) / two
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Copy + Mul<T, Output = T> + Sub<T, Output = T>,
+{
+ #[inline]
+ pub fn volume(&self) -> T {
+ let size = self.size();
+ size.width * size.height * size.depth
+ }
+
+ #[inline]
+ pub fn xy_area(&self) -> T {
+ let size = self.size();
+ size.width * size.height
+ }
+
+ #[inline]
+ pub fn yz_area(&self) -> T {
+ let size = self.size();
+ size.depth * size.height
+ }
+
+ #[inline]
+ pub fn xz_area(&self) -> T {
+ let size = self.size();
+ size.depth * size.width
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Zero,
+{
+ /// Constructor, setting all sides to zero.
+ pub fn zero() -> Self {
+ Box3D::new(Point3D::zero(), Point3D::zero())
+ }
+}
+
+impl<T: Copy + Mul, U> Mul<T> for Box3D<T, U> {
+ type Output = Box3D<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ Box3D::new(self.min * scale, self.max * scale)
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<T> for Box3D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: T) {
+ self.min *= scale;
+ self.max *= scale;
+ }
+}
+
+impl<T: Copy + Div, U> Div<T> for Box3D<T, U> {
+ type Output = Box3D<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ Box3D::new(self.min / scale.clone(), self.max / scale)
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<T> for Box3D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: T) {
+ self.min /= scale;
+ self.max /= scale;
+ }
+}
+
+impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Box3D<T, U1> {
+ type Output = Box3D<T::Output, U2>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ Box3D::new(self.min * scale.clone(), self.max * scale)
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Box3D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: Scale<T, U, U>) {
+ self.min *= scale.clone();
+ self.max *= scale;
+ }
+}
+
+impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Box3D<T, U2> {
+ type Output = Box3D<T::Output, U1>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ Box3D::new(self.min / scale.clone(), self.max / scale)
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Box3D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: Scale<T, U, U>) {
+ self.min /= scale.clone();
+ self.max /= scale;
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Copy,
+{
+ #[inline]
+ pub fn x_range(&self) -> Range<T> {
+ self.min.x..self.max.x
+ }
+
+ #[inline]
+ pub fn y_range(&self) -> Range<T> {
+ self.min.y..self.max.y
+ }
+
+ #[inline]
+ pub fn z_range(&self) -> Range<T> {
+ self.min.z..self.max.z
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(&self) -> Box3D<T, UnknownUnit> {
+ Box3D {
+ min: self.min.to_untyped(),
+ max: self.max.to_untyped(),
+ }
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(c: &Box3D<T, UnknownUnit>) -> Box3D<T, U> {
+ Box3D {
+ min: Point3D::from_untyped(c.min),
+ max: Point3D::from_untyped(c.max),
+ }
+ }
+
+ /// Cast the unit
+ #[inline]
+ pub fn cast_unit<V>(&self) -> Box3D<T, V> {
+ Box3D::new(self.min.cast_unit(), self.max.cast_unit())
+ }
+
+ #[inline]
+ pub fn scale<S: Copy>(&self, x: S, y: S, z: S) -> Self
+ where
+ T: Mul<S, Output = T>,
+ {
+ Box3D::new(
+ Point3D::new(self.min.x * x, self.min.y * y, self.min.z * z),
+ Point3D::new(self.max.x * x, self.max.y * y, self.max.z * z),
+ )
+ }
+}
+
+impl<T: NumCast + Copy, U> Box3D<T, U> {
+ /// Cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using round(), round_in or round_out() before casting.
+ #[inline]
+ pub fn cast<NewT: NumCast>(&self) -> Box3D<NewT, U> {
+ Box3D::new(self.min.cast(), self.max.cast())
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using round(), round_in or round_out() before casting.
+ pub fn try_cast<NewT: NumCast>(&self) -> Option<Box3D<NewT, U>> {
+ match (self.min.try_cast(), self.max.try_cast()) {
+ (Some(a), Some(b)) => Some(Box3D::new(a, b)),
+ _ => None,
+ }
+ }
+
+ // Convenience functions for common casts
+
+ /// Cast into an `f32` box3d.
+ #[inline]
+ pub fn to_f32(&self) -> Box3D<f32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `f64` box3d.
+ #[inline]
+ pub fn to_f64(&self) -> Box3D<f64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `usize` box3d, truncating decimals if any.
+ ///
+ /// When casting from floating point cuboids, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_usize(&self) -> Box3D<usize, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u32` box3d, truncating decimals if any.
+ ///
+ /// When casting from floating point cuboids, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_u32(&self) -> Box3D<u32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i32` box3d, truncating decimals if any.
+ ///
+ /// When casting from floating point cuboids, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_i32(&self) -> Box3D<i32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i64` box3d, truncating decimals if any.
+ ///
+ /// When casting from floating point cuboids, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_i64(&self) -> Box3D<i64, U> {
+ self.cast()
+ }
+}
+
+impl<T: Float, U> Box3D<T, U> {
+ /// Returns true if all members are finite.
+ #[inline]
+ pub fn is_finite(self) -> bool {
+ self.min.is_finite() && self.max.is_finite()
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Round,
+{
+ /// Return a box3d with edges rounded to integer coordinates, such that
+ /// the returned box3d has the same set of pixel centers as the original
+ /// one.
+ /// Values equal to 0.5 round up.
+ /// Suitable for most places where integral device coordinates
+ /// are needed, but note that any translation should be applied first to
+ /// avoid pixel rounding errors.
+ /// Note that this is *not* rounding to nearest integer if the values are negative.
+ /// They are always rounding as floor(n + 0.5).
+ #[must_use]
+ pub fn round(&self) -> Self {
+ Box3D::new(self.min.round(), self.max.round())
+ }
+}
+
+impl<T, U> Box3D<T, U>
+where
+ T: Floor + Ceil,
+{
+ /// Return a box3d with faces/edges rounded to integer coordinates, such that
+ /// the original box3d contains the resulting box3d.
+ #[must_use]
+ pub fn round_in(&self) -> Self {
+ Box3D {
+ min: self.min.ceil(),
+ max: self.max.floor(),
+ }
+ }
+
+ /// Return a box3d with faces/edges rounded to integer coordinates, such that
+ /// the original box3d is contained in the resulting box3d.
+ #[must_use]
+ pub fn round_out(&self) -> Self {
+ Box3D {
+ min: self.min.floor(),
+ max: self.max.ceil(),
+ }
+ }
+}
+
+impl<T, U> From<Size3D<T, U>> for Box3D<T, U>
+where
+ T: Copy + Zero + PartialOrd,
+{
+ fn from(b: Size3D<T, U>) -> Self {
+ Self::from_size(b)
+ }
+}
+
+impl<T: Default, U> Default for Box3D<T, U> {
+ fn default() -> Self {
+ Box3D {
+ min: Default::default(),
+ max: Default::default(),
+ }
+ }
+}
+
+/// Shorthand for `Box3D::new(Point3D::new(x1, y1, z1), Point3D::new(x2, y2, z2))`.
+pub fn box3d<T: Copy, U>(
+ min_x: T,
+ min_y: T,
+ min_z: T,
+ max_x: T,
+ max_y: T,
+ max_z: T,
+) -> Box3D<T, U> {
+ Box3D::new(
+ Point3D::new(min_x, min_y, min_z),
+ Point3D::new(max_x, max_y, max_z),
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::default::{Box3D, Point3D};
+ use crate::{point3, size3, vec3};
+
+ #[test]
+ fn test_new() {
+ let b = Box3D::new(point3(-1.0, -1.0, -1.0), point3(1.0, 1.0, 1.0));
+ assert!(b.min.x == -1.0);
+ assert!(b.min.y == -1.0);
+ assert!(b.min.z == -1.0);
+ assert!(b.max.x == 1.0);
+ assert!(b.max.y == 1.0);
+ assert!(b.max.z == 1.0);
+ }
+
+ #[test]
+ fn test_size() {
+ let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0));
+ assert!(b.size().width == 20.0);
+ assert!(b.size().height == 20.0);
+ assert!(b.size().depth == 20.0);
+ }
+
+ #[test]
+ fn test_width_height_depth() {
+ let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0));
+ assert!(b.width() == 20.0);
+ assert!(b.height() == 20.0);
+ assert!(b.depth() == 20.0);
+ }
+
+ #[test]
+ fn test_center() {
+ let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0));
+ assert!(b.center() == Point3D::zero());
+ }
+
+ #[test]
+ fn test_volume() {
+ let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0));
+ assert!(b.volume() == 8000.0);
+ }
+
+ #[test]
+ fn test_area() {
+ let b = Box3D::new(point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0));
+ assert!(b.xy_area() == 400.0);
+ assert!(b.yz_area() == 400.0);
+ assert!(b.xz_area() == 400.0);
+ }
+
+ #[test]
+ fn test_from_points() {
+ let b = Box3D::from_points(&[point3(50.0, 160.0, 12.5), point3(100.0, 25.0, 200.0)]);
+ assert!(b.min == point3(50.0, 25.0, 12.5));
+ assert!(b.max == point3(100.0, 160.0, 200.0));
+ }
+
+ #[test]
+ fn test_min_max() {
+ let b = Box3D::from_points(&[point3(50.0, 25.0, 12.5), point3(100.0, 160.0, 200.0)]);
+ assert!(b.min.x == 50.0);
+ assert!(b.min.y == 25.0);
+ assert!(b.min.z == 12.5);
+ assert!(b.max.x == 100.0);
+ assert!(b.max.y == 160.0);
+ assert!(b.max.z == 200.0);
+ }
+
+ #[test]
+ fn test_round_in() {
+ let b =
+ Box3D::from_points(&[point3(-25.5, -40.4, -70.9), point3(60.3, 36.5, 89.8)]).round_in();
+ assert!(b.min.x == -25.0);
+ assert!(b.min.y == -40.0);
+ assert!(b.min.z == -70.0);
+ assert!(b.max.x == 60.0);
+ assert!(b.max.y == 36.0);
+ assert!(b.max.z == 89.0);
+ }
+
+ #[test]
+ fn test_round_out() {
+ let b = Box3D::from_points(&[point3(-25.5, -40.4, -70.9), point3(60.3, 36.5, 89.8)])
+ .round_out();
+ assert!(b.min.x == -26.0);
+ assert!(b.min.y == -41.0);
+ assert!(b.min.z == -71.0);
+ assert!(b.max.x == 61.0);
+ assert!(b.max.y == 37.0);
+ assert!(b.max.z == 90.0);
+ }
+
+ #[test]
+ fn test_round() {
+ let b =
+ Box3D::from_points(&[point3(-25.5, -40.4, -70.9), point3(60.3, 36.5, 89.8)]).round();
+ assert!(b.min.x == -25.0);
+ assert!(b.min.y == -40.0);
+ assert!(b.min.z == -71.0);
+ assert!(b.max.x == 60.0);
+ assert!(b.max.y == 37.0);
+ assert!(b.max.z == 90.0);
+ }
+
+ #[test]
+ fn test_from_size() {
+ let b = Box3D::from_size(size3(30.0, 40.0, 50.0));
+ assert!(b.min == Point3D::zero());
+ assert!(b.size().width == 30.0);
+ assert!(b.size().height == 40.0);
+ assert!(b.size().depth == 50.0);
+ }
+
+ #[test]
+ fn test_translate() {
+ let size = size3(15.0, 15.0, 200.0);
+ let mut center = (size / 2.0).to_vector().to_point();
+ let b = Box3D::from_size(size);
+ assert!(b.center() == center);
+ let translation = vec3(10.0, 2.5, 9.5);
+ let b = b.translate(translation);
+ center += translation;
+ assert!(b.center() == center);
+ assert!(b.max.x == 25.0);
+ assert!(b.max.y == 17.5);
+ assert!(b.max.z == 209.5);
+ assert!(b.min.x == 10.0);
+ assert!(b.min.y == 2.5);
+ assert!(b.min.z == 9.5);
+ }
+
+ #[test]
+ fn test_union() {
+ let b1 = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(0.0, 20.0, 20.0)]);
+ let b2 = Box3D::from_points(&[point3(0.0, 20.0, 20.0), point3(20.0, -20.0, -20.0)]);
+ let b = b1.union(&b2);
+ assert!(b.max.x == 20.0);
+ assert!(b.max.y == 20.0);
+ assert!(b.max.z == 20.0);
+ assert!(b.min.x == -20.0);
+ assert!(b.min.y == -20.0);
+ assert!(b.min.z == -20.0);
+ assert!(b.volume() == (40.0 * 40.0 * 40.0));
+ }
+
+ #[test]
+ fn test_intersects() {
+ let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(10.0, 20.0, 20.0)]);
+ let b2 = Box3D::from_points(&[point3(-10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]);
+ assert!(b1.intersects(&b2));
+ }
+
+ #[test]
+ fn test_intersection_unchecked() {
+ let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(10.0, 20.0, 20.0)]);
+ let b2 = Box3D::from_points(&[point3(-10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]);
+ let b = b1.intersection_unchecked(&b2);
+ assert!(b.max.x == 10.0);
+ assert!(b.max.y == 20.0);
+ assert!(b.max.z == 20.0);
+ assert!(b.min.x == -10.0);
+ assert!(b.min.y == -20.0);
+ assert!(b.min.z == -20.0);
+ assert!(b.volume() == (20.0 * 40.0 * 40.0));
+ }
+
+ #[test]
+ fn test_intersection() {
+ let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(10.0, 20.0, 20.0)]);
+ let b2 = Box3D::from_points(&[point3(-10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]);
+ assert!(b1.intersection(&b2).is_some());
+
+ let b1 = Box3D::from_points(&[point3(-15.0, -20.0, -20.0), point3(-10.0, 20.0, 20.0)]);
+ let b2 = Box3D::from_points(&[point3(10.0, 20.0, 20.0), point3(15.0, -20.0, -20.0)]);
+ assert!(b1.intersection(&b2).is_none());
+ }
+
+ #[test]
+ fn test_scale() {
+ let b = Box3D::from_points(&[point3(-10.0, -10.0, -10.0), point3(10.0, 10.0, 10.0)]);
+ let b = b.scale(0.5, 0.5, 0.5);
+ assert!(b.max.x == 5.0);
+ assert!(b.max.y == 5.0);
+ assert!(b.max.z == 5.0);
+ assert!(b.min.x == -5.0);
+ assert!(b.min.y == -5.0);
+ assert!(b.min.z == -5.0);
+ }
+
+ #[test]
+ fn test_zero() {
+ let b = Box3D::<f64>::zero();
+ assert!(b.max.x == 0.0);
+ assert!(b.max.y == 0.0);
+ assert!(b.max.z == 0.0);
+ assert!(b.min.x == 0.0);
+ assert!(b.min.y == 0.0);
+ assert!(b.min.z == 0.0);
+ }
+
+ #[test]
+ fn test_lerp() {
+ let b1 = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(-10.0, -10.0, -10.0)]);
+ let b2 = Box3D::from_points(&[point3(10.0, 10.0, 10.0), point3(20.0, 20.0, 20.0)]);
+ let b = b1.lerp(b2, 0.5);
+ assert!(b.center() == Point3D::zero());
+ assert!(b.size().width == 10.0);
+ assert!(b.size().height == 10.0);
+ assert!(b.size().depth == 10.0);
+ }
+
+ #[test]
+ fn test_contains() {
+ let b = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(20.0, 20.0, 20.0)]);
+ assert!(b.contains(point3(-15.3, 10.5, 18.4)));
+ }
+
+ #[test]
+ fn test_contains_box() {
+ let b1 = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(20.0, 20.0, 20.0)]);
+ let b2 = Box3D::from_points(&[point3(-14.3, -16.5, -19.3), point3(6.7, 17.6, 2.5)]);
+ assert!(b1.contains_box(&b2));
+ }
+
+ #[test]
+ fn test_inflate() {
+ let b = Box3D::from_points(&[point3(-20.0, -20.0, -20.0), point3(20.0, 20.0, 20.0)]);
+ let b = b.inflate(10.0, 5.0, 2.0);
+ assert!(b.size().width == 60.0);
+ assert!(b.size().height == 50.0);
+ assert!(b.size().depth == 44.0);
+ assert!(b.center() == Point3D::zero());
+ }
+
+ #[test]
+ fn test_is_empty() {
+ for i in 0..3 {
+ let mut coords_neg = [-20.0, -20.0, -20.0];
+ let mut coords_pos = [20.0, 20.0, 20.0];
+ coords_neg[i] = 0.0;
+ coords_pos[i] = 0.0;
+ let b = Box3D::from_points(&[Point3D::from(coords_neg), Point3D::from(coords_pos)]);
+ assert!(b.is_empty());
+ }
+ }
+
+ #[test]
+ fn test_nan_empty_or_negative() {
+ use std::f32::NAN;
+ assert!(Box3D { min: point3(NAN, 2.0, 1.0), max: point3(1.0, 3.0, 5.0) }.is_empty());
+ assert!(Box3D { min: point3(0.0, NAN, 1.0), max: point3(1.0, 2.0, 5.0) }.is_empty());
+ assert!(Box3D { min: point3(1.0, -2.0, NAN), max: point3(3.0, 2.0, 5.0) }.is_empty());
+ assert!(Box3D { min: point3(1.0, -2.0, 1.0), max: point3(NAN, 2.0, 5.0) }.is_empty());
+ assert!(Box3D { min: point3(1.0, -2.0, 1.0), max: point3(0.0, NAN, 5.0) }.is_empty());
+ assert!(Box3D { min: point3(1.0, -2.0, 1.0), max: point3(0.0, 1.0, NAN) }.is_empty());
+ }
+}
diff --git a/third_party/rust/euclid/src/homogen.rs b/third_party/rust/euclid/src/homogen.rs
new file mode 100644
index 0000000000..afd4f22957
--- /dev/null
+++ b/third_party/rust/euclid/src/homogen.rs
@@ -0,0 +1,223 @@
+// Copyright 2018 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::point::{Point2D, Point3D};
+use crate::vector::{Vector2D, Vector3D};
+
+use crate::num::{One, Zero};
+
+use core::cmp::{Eq, PartialEq};
+use core::fmt;
+use core::hash::Hash;
+use core::marker::PhantomData;
+use core::ops::Div;
+#[cfg(feature = "serde")]
+use serde;
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// Homogeneous vector in 3D space.
+#[repr(C)]
+pub struct HomogeneousVector<T, U> {
+ pub x: T,
+ pub y: T,
+ pub z: T,
+ pub w: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<U>,
+}
+
+impl<T: Copy, U> Copy for HomogeneousVector<T, U> {}
+
+impl<T: Clone, U> Clone for HomogeneousVector<T, U> {
+ fn clone(&self) -> Self {
+ HomogeneousVector {
+ x: self.x.clone(),
+ y: self.y.clone(),
+ z: self.z.clone(),
+ w: self.w.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de, T, U> serde::Deserialize<'de> for HomogeneousVector<T, U>
+where
+ T: serde::Deserialize<'de>,
+{
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let (x, y, z, w) = serde::Deserialize::deserialize(deserializer)?;
+ Ok(HomogeneousVector {
+ x,
+ y,
+ z,
+ w,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<T, U> serde::Serialize for HomogeneousVector<T, U>
+where
+ T: serde::Serialize,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ (&self.x, &self.y, &self.z, &self.w).serialize(serializer)
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for HomogeneousVector<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for HomogeneousVector<T, U> {}
+
+impl<T, U> Eq for HomogeneousVector<T, U> where T: Eq {}
+
+impl<T, U> PartialEq for HomogeneousVector<T, U>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.x == other.x && self.y == other.y && self.z == other.z && self.w == other.w
+ }
+}
+
+impl<T, U> Hash for HomogeneousVector<T, U>
+where
+ T: Hash,
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.x.hash(h);
+ self.y.hash(h);
+ self.z.hash(h);
+ self.w.hash(h);
+ }
+}
+
+impl<T, U> HomogeneousVector<T, U> {
+ /// Constructor taking scalar values directly.
+ #[inline]
+ pub const fn new(x: T, y: T, z: T, w: T) -> Self {
+ HomogeneousVector {
+ x,
+ y,
+ z,
+ w,
+ _unit: PhantomData,
+ }
+ }
+}
+
+impl<T: Copy + Div<T, Output = T> + Zero + PartialOrd, U> HomogeneousVector<T, U> {
+ /// Convert into Cartesian 2D point.
+ ///
+ /// Returns None if the point is on or behind the W=0 hemisphere.
+ #[inline]
+ pub fn to_point2d(self) -> Option<Point2D<T, U>> {
+ if self.w > T::zero() {
+ Some(Point2D::new(self.x / self.w, self.y / self.w))
+ } else {
+ None
+ }
+ }
+
+ /// Convert into Cartesian 3D point.
+ ///
+ /// Returns None if the point is on or behind the W=0 hemisphere.
+ #[inline]
+ pub fn to_point3d(self) -> Option<Point3D<T, U>> {
+ if self.w > T::zero() {
+ Some(Point3D::new(
+ self.x / self.w,
+ self.y / self.w,
+ self.z / self.w,
+ ))
+ } else {
+ None
+ }
+ }
+}
+
+impl<T: Zero, U> From<Vector2D<T, U>> for HomogeneousVector<T, U> {
+ #[inline]
+ fn from(v: Vector2D<T, U>) -> Self {
+ HomogeneousVector::new(v.x, v.y, T::zero(), T::zero())
+ }
+}
+
+impl<T: Zero, U> From<Vector3D<T, U>> for HomogeneousVector<T, U> {
+ #[inline]
+ fn from(v: Vector3D<T, U>) -> Self {
+ HomogeneousVector::new(v.x, v.y, v.z, T::zero())
+ }
+}
+
+impl<T: Zero + One, U> From<Point2D<T, U>> for HomogeneousVector<T, U> {
+ #[inline]
+ fn from(p: Point2D<T, U>) -> Self {
+ HomogeneousVector::new(p.x, p.y, T::zero(), T::one())
+ }
+}
+
+impl<T: One, U> From<Point3D<T, U>> for HomogeneousVector<T, U> {
+ #[inline]
+ fn from(p: Point3D<T, U>) -> Self {
+ HomogeneousVector::new(p.x, p.y, p.z, T::one())
+ }
+}
+
+impl<T: fmt::Debug, U> fmt::Debug for HomogeneousVector<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_tuple("")
+ .field(&self.x)
+ .field(&self.y)
+ .field(&self.z)
+ .field(&self.w)
+ .finish()
+ }
+}
+
+#[cfg(test)]
+mod homogeneous {
+ use super::HomogeneousVector;
+ use crate::default::{Point2D, Point3D};
+
+ #[test]
+ fn roundtrip() {
+ assert_eq!(
+ Some(Point2D::new(1.0, 2.0)),
+ HomogeneousVector::from(Point2D::new(1.0, 2.0)).to_point2d()
+ );
+ assert_eq!(
+ Some(Point3D::new(1.0, -2.0, 0.1)),
+ HomogeneousVector::from(Point3D::new(1.0, -2.0, 0.1)).to_point3d()
+ );
+ }
+
+ #[test]
+ fn negative() {
+ assert_eq!(
+ None,
+ HomogeneousVector::<f32, ()>::new(1.0, 2.0, 3.0, 0.0).to_point2d()
+ );
+ assert_eq!(
+ None,
+ HomogeneousVector::<f32, ()>::new(1.0, -2.0, -3.0, -2.0).to_point3d()
+ );
+ }
+}
diff --git a/third_party/rust/euclid/src/length.rs b/third_party/rust/euclid/src/length.rs
new file mode 100644
index 0000000000..e65d38b61d
--- /dev/null
+++ b/third_party/rust/euclid/src/length.rs
@@ -0,0 +1,609 @@
+// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//! A one-dimensional length, tagged with its units.
+
+use crate::approxeq::ApproxEq;
+use crate::num::Zero;
+use crate::scale::Scale;
+use crate::approxord::{max, min};
+
+use crate::num::One;
+use core::cmp::Ordering;
+use core::fmt;
+use core::hash::{Hash, Hasher};
+use core::iter::Sum;
+use core::marker::PhantomData;
+use core::ops::{Add, Div, Mul, Neg, Sub};
+use core::ops::{AddAssign, DivAssign, MulAssign, SubAssign};
+use num_traits::{NumCast, Saturating};
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A one-dimensional distance, with value represented by `T` and unit of measurement `Unit`.
+///
+/// `T` can be any numeric type, for example a primitive type like `u64` or `f32`.
+///
+/// `Unit` is not used in the representation of a `Length` value. It is used only at compile time
+/// to ensure that a `Length` stored with one unit is converted explicitly before being used in an
+/// expression that requires a different unit. It may be a type without values, such as an empty
+/// enum.
+///
+/// You can multiply a `Length` by a `scale::Scale` to convert it from one unit to
+/// another. See the [`Scale`] docs for an example.
+///
+/// [`Scale`]: struct.Scale.html
+#[repr(C)]
+pub struct Length<T, Unit>(pub T, #[doc(hidden)] pub PhantomData<Unit>);
+
+impl<T: Clone, U> Clone for Length<T, U> {
+ fn clone(&self) -> Self {
+ Length(self.0.clone(), PhantomData)
+ }
+}
+
+impl<T: Copy, U> Copy for Length<T, U> {}
+
+#[cfg(feature = "serde")]
+impl<'de, T, U> Deserialize<'de> for Length<T, U>
+where
+ T: Deserialize<'de>,
+{
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ Ok(Length(Deserialize::deserialize(deserializer)?, PhantomData))
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<T, U> Serialize for Length<T, U>
+where
+ T: Serialize,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ self.0.serialize(serializer)
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for Length<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for Length<T, U> {}
+
+impl<T, U> Length<T, U> {
+ /// Associate a value with a unit of measure.
+ #[inline]
+ pub const fn new(x: T) -> Self {
+ Length(x, PhantomData)
+ }
+}
+
+impl<T: Clone, U> Length<T, U> {
+ /// Unpack the underlying value from the wrapper.
+ pub fn get(self) -> T {
+ self.0
+ }
+
+ /// Cast the unit
+ #[inline]
+ pub fn cast_unit<V>(self) -> Length<T, V> {
+ Length::new(self.0)
+ }
+
+ /// Linearly interpolate between this length and another length.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::default::Length;
+ ///
+ /// let from = Length::new(0.0);
+ /// let to = Length::new(8.0);
+ ///
+ /// assert_eq!(from.lerp(to, -1.0), Length::new(-8.0));
+ /// assert_eq!(from.lerp(to, 0.0), Length::new( 0.0));
+ /// assert_eq!(from.lerp(to, 0.5), Length::new( 4.0));
+ /// assert_eq!(from.lerp(to, 1.0), Length::new( 8.0));
+ /// assert_eq!(from.lerp(to, 2.0), Length::new(16.0));
+ /// ```
+ #[inline]
+ pub fn lerp(self, other: Self, t: T) -> Self
+ where
+ T: One + Sub<Output = T> + Mul<Output = T> + Add<Output = T>,
+ {
+ let one_t = T::one() - t.clone();
+ Length::new(one_t * self.0.clone() + t * other.0)
+ }
+}
+
+impl<T: PartialOrd, U> Length<T, U> {
+ /// Returns minimum between this length and another length.
+ #[inline]
+ pub fn min(self, other: Self) -> Self {
+ min(self, other)
+ }
+
+ /// Returns maximum between this length and another length.
+ #[inline]
+ pub fn max(self, other: Self) -> Self {
+ max(self, other)
+ }
+}
+
+impl<T: NumCast + Clone, U> Length<T, U> {
+ /// Cast from one numeric representation to another, preserving the units.
+ #[inline]
+ pub fn cast<NewT: NumCast>(self) -> Length<NewT, U> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ pub fn try_cast<NewT: NumCast>(self) -> Option<Length<NewT, U>> {
+ NumCast::from(self.0).map(Length::new)
+ }
+}
+
+impl<T: fmt::Debug, U> fmt::Debug for Length<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl<T: Default, U> Default for Length<T, U> {
+ #[inline]
+ fn default() -> Self {
+ Length::new(Default::default())
+ }
+}
+
+impl<T: Hash, U> Hash for Length<T, U> {
+ fn hash<H: Hasher>(&self, h: &mut H) {
+ self.0.hash(h);
+ }
+}
+
+// length + length
+impl<T: Add, U> Add for Length<T, U> {
+ type Output = Length<T::Output, U>;
+
+ fn add(self, other: Self) -> Self::Output {
+ Length::new(self.0 + other.0)
+ }
+}
+
+// length + &length
+impl<T: Add + Copy, U> Add<&Self> for Length<T, U> {
+ type Output = Length<T::Output, U>;
+
+ fn add(self, other: &Self) -> Self::Output {
+ Length::new(self.0 + other.0)
+ }
+}
+
+// length_iter.copied().sum()
+impl<T: Add<Output = T> + Zero, U> Sum for Length<T, U> {
+ fn sum<I: Iterator<Item=Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+// length_iter.sum()
+impl<'a, T: 'a + Add<Output = T> + Copy + Zero, U: 'a> Sum<&'a Self> for Length<T, U> {
+ fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+// length += length
+impl<T: AddAssign, U> AddAssign for Length<T, U> {
+ fn add_assign(&mut self, other: Self) {
+ self.0 += other.0;
+ }
+}
+
+// length - length
+impl<T: Sub, U> Sub for Length<T, U> {
+ type Output = Length<T::Output, U>;
+
+ fn sub(self, other: Length<T, U>) -> Self::Output {
+ Length::new(self.0 - other.0)
+ }
+}
+
+// length -= length
+impl<T: SubAssign, U> SubAssign for Length<T, U> {
+ fn sub_assign(&mut self, other: Self) {
+ self.0 -= other.0;
+ }
+}
+
+// Saturating length + length and length - length.
+impl<T: Saturating, U> Saturating for Length<T, U> {
+ fn saturating_add(self, other: Self) -> Self {
+ Length::new(self.0.saturating_add(other.0))
+ }
+
+ fn saturating_sub(self, other: Self) -> Self {
+ Length::new(self.0.saturating_sub(other.0))
+ }
+}
+
+// length / length
+impl<Src, Dst, T: Div> Div<Length<T, Src>> for Length<T, Dst> {
+ type Output = Scale<T::Output, Src, Dst>;
+
+ #[inline]
+ fn div(self, other: Length<T, Src>) -> Self::Output {
+ Scale::new(self.0 / other.0)
+ }
+}
+
+// length * scalar
+impl<T: Mul, U> Mul<T> for Length<T, U> {
+ type Output = Length<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ Length::new(self.0 * scale)
+ }
+}
+
+// length *= scalar
+impl<T: Copy + Mul<T, Output = T>, U> MulAssign<T> for Length<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: T) {
+ *self = *self * scale
+ }
+}
+
+// length / scalar
+impl<T: Div, U> Div<T> for Length<T, U> {
+ type Output = Length<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ Length::new(self.0 / scale)
+ }
+}
+
+// length /= scalar
+impl<T: Copy + Div<T, Output = T>, U> DivAssign<T> for Length<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: T) {
+ *self = *self / scale
+ }
+}
+
+// length * scaleFactor
+impl<Src, Dst, T: Mul> Mul<Scale<T, Src, Dst>> for Length<T, Src> {
+ type Output = Length<T::Output, Dst>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, Src, Dst>) -> Self::Output {
+ Length::new(self.0 * scale.0)
+ }
+}
+
+// length / scaleFactor
+impl<Src, Dst, T: Div> Div<Scale<T, Src, Dst>> for Length<T, Dst> {
+ type Output = Length<T::Output, Src>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, Src, Dst>) -> Self::Output {
+ Length::new(self.0 / scale.0)
+ }
+}
+
+// -length
+impl<U, T: Neg> Neg for Length<T, U> {
+ type Output = Length<T::Output, U>;
+
+ #[inline]
+ fn neg(self) -> Self::Output {
+ Length::new(-self.0)
+ }
+}
+
+impl<T: PartialEq, U> PartialEq for Length<T, U> {
+ fn eq(&self, other: &Self) -> bool {
+ self.0.eq(&other.0)
+ }
+}
+
+impl<T: PartialOrd, U> PartialOrd for Length<T, U> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.0.partial_cmp(&other.0)
+ }
+}
+
+impl<T: Eq, U> Eq for Length<T, U> {}
+
+impl<T: Ord, U> Ord for Length<T, U> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.0.cmp(&other.0)
+ }
+}
+
+impl<T: Zero, U> Zero for Length<T, U> {
+ #[inline]
+ fn zero() -> Self {
+ Length::new(Zero::zero())
+ }
+}
+
+impl<U, T: ApproxEq<T>> ApproxEq<T> for Length<T, U> {
+ #[inline]
+ fn approx_epsilon() -> T {
+ T::approx_epsilon()
+ }
+
+ #[inline]
+ fn approx_eq_eps(&self, other: &Length<T, U>, approx_epsilon: &T) -> bool {
+ self.0.approx_eq_eps(&other.0, approx_epsilon)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Length;
+ use crate::num::Zero;
+
+ use crate::scale::Scale;
+ use core::f32::INFINITY;
+ use num_traits::Saturating;
+
+ enum Inch {}
+ enum Mm {}
+ enum Cm {}
+ enum Second {}
+
+ #[cfg(feature = "serde")]
+ mod serde {
+ use super::*;
+
+ extern crate serde_test;
+ use self::serde_test::assert_tokens;
+ use self::serde_test::Token;
+
+ #[test]
+ fn test_length_serde() {
+ let one_cm: Length<f32, Mm> = Length::new(10.0);
+
+ assert_tokens(&one_cm, &[Token::F32(10.0)]);
+ }
+ }
+
+ #[test]
+ fn test_clone() {
+ // A cloned Length is a separate length with the state matching the
+ // original Length at the point it was cloned.
+ let mut variable_length: Length<f32, Inch> = Length::new(12.0);
+
+ let one_foot = variable_length.clone();
+ variable_length.0 = 24.0;
+
+ assert_eq!(one_foot.get(), 12.0);
+ assert_eq!(variable_length.get(), 24.0);
+ }
+
+ #[test]
+ fn test_add() {
+ let length1: Length<u8, Mm> = Length::new(250);
+ let length2: Length<u8, Mm> = Length::new(5);
+
+ assert_eq!((length1 + length2).get(), 255);
+ assert_eq!((length1 + &length2).get(), 255);
+ }
+
+ #[test]
+ fn test_sum() {
+ type L = Length<f32, Mm>;
+ let lengths = [L::new(1.0), L::new(2.0), L::new(3.0)];
+
+ assert_eq!(lengths.iter().sum::<L>(), L::new(6.0));
+ }
+
+ #[test]
+ fn test_addassign() {
+ let one_cm: Length<f32, Mm> = Length::new(10.0);
+ let mut measurement: Length<f32, Mm> = Length::new(5.0);
+
+ measurement += one_cm;
+
+ assert_eq!(measurement.get(), 15.0);
+ }
+
+ #[test]
+ fn test_sub() {
+ let length1: Length<u8, Mm> = Length::new(250);
+ let length2: Length<u8, Mm> = Length::new(5);
+
+ let result = length1 - length2;
+
+ assert_eq!(result.get(), 245);
+ }
+
+ #[test]
+ fn test_subassign() {
+ let one_cm: Length<f32, Mm> = Length::new(10.0);
+ let mut measurement: Length<f32, Mm> = Length::new(5.0);
+
+ measurement -= one_cm;
+
+ assert_eq!(measurement.get(), -5.0);
+ }
+
+ #[test]
+ fn test_saturating_add() {
+ let length1: Length<u8, Mm> = Length::new(250);
+ let length2: Length<u8, Mm> = Length::new(6);
+
+ let result = length1.saturating_add(length2);
+
+ assert_eq!(result.get(), 255);
+ }
+
+ #[test]
+ fn test_saturating_sub() {
+ let length1: Length<u8, Mm> = Length::new(5);
+ let length2: Length<u8, Mm> = Length::new(10);
+
+ let result = length1.saturating_sub(length2);
+
+ assert_eq!(result.get(), 0);
+ }
+
+ #[test]
+ fn test_division_by_length() {
+ // Division results in a Scale from denominator units
+ // to numerator units.
+ let length: Length<f32, Cm> = Length::new(5.0);
+ let duration: Length<f32, Second> = Length::new(10.0);
+
+ let result = length / duration;
+
+ let expected: Scale<f32, Second, Cm> = Scale::new(0.5);
+ assert_eq!(result, expected);
+ }
+
+ #[test]
+ fn test_multiplication() {
+ let length_mm: Length<f32, Mm> = Length::new(10.0);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = length_mm * cm_per_mm;
+
+ let expected: Length<f32, Cm> = Length::new(1.0);
+ assert_eq!(result, expected);
+ }
+
+ #[test]
+ fn test_multiplication_with_scalar() {
+ let length_mm: Length<f32, Mm> = Length::new(10.0);
+
+ let result = length_mm * 2.0;
+
+ let expected: Length<f32, Mm> = Length::new(20.0);
+ assert_eq!(result, expected);
+ }
+
+ #[test]
+ fn test_multiplication_assignment() {
+ let mut length: Length<f32, Mm> = Length::new(10.0);
+
+ length *= 2.0;
+
+ let expected: Length<f32, Mm> = Length::new(20.0);
+ assert_eq!(length, expected);
+ }
+
+ #[test]
+ fn test_division_by_scalefactor() {
+ let length: Length<f32, Cm> = Length::new(5.0);
+ let cm_per_second: Scale<f32, Second, Cm> = Scale::new(10.0);
+
+ let result = length / cm_per_second;
+
+ let expected: Length<f32, Second> = Length::new(0.5);
+ assert_eq!(result, expected);
+ }
+
+ #[test]
+ fn test_division_by_scalar() {
+ let length: Length<f32, Cm> = Length::new(5.0);
+
+ let result = length / 2.0;
+
+ let expected: Length<f32, Cm> = Length::new(2.5);
+ assert_eq!(result, expected);
+ }
+
+ #[test]
+ fn test_division_assignment() {
+ let mut length: Length<f32, Mm> = Length::new(10.0);
+
+ length /= 2.0;
+
+ let expected: Length<f32, Mm> = Length::new(5.0);
+ assert_eq!(length, expected);
+ }
+
+ #[test]
+ fn test_negation() {
+ let length: Length<f32, Cm> = Length::new(5.0);
+
+ let result = -length;
+
+ let expected: Length<f32, Cm> = Length::new(-5.0);
+ assert_eq!(result, expected);
+ }
+
+ #[test]
+ fn test_cast() {
+ let length_as_i32: Length<i32, Cm> = Length::new(5);
+
+ let result: Length<f32, Cm> = length_as_i32.cast();
+
+ let length_as_f32: Length<f32, Cm> = Length::new(5.0);
+ assert_eq!(result, length_as_f32);
+ }
+
+ #[test]
+ fn test_equality() {
+ let length_5_point_0: Length<f32, Cm> = Length::new(5.0);
+ let length_5_point_1: Length<f32, Cm> = Length::new(5.1);
+ let length_0_point_1: Length<f32, Cm> = Length::new(0.1);
+
+ assert!(length_5_point_0 == length_5_point_1 - length_0_point_1);
+ assert!(length_5_point_0 != length_5_point_1);
+ }
+
+ #[test]
+ fn test_order() {
+ let length_5_point_0: Length<f32, Cm> = Length::new(5.0);
+ let length_5_point_1: Length<f32, Cm> = Length::new(5.1);
+ let length_0_point_1: Length<f32, Cm> = Length::new(0.1);
+
+ assert!(length_5_point_0 < length_5_point_1);
+ assert!(length_5_point_0 <= length_5_point_1);
+ assert!(length_5_point_0 <= length_5_point_1 - length_0_point_1);
+ assert!(length_5_point_1 > length_5_point_0);
+ assert!(length_5_point_1 >= length_5_point_0);
+ assert!(length_5_point_0 >= length_5_point_1 - length_0_point_1);
+ }
+
+ #[test]
+ fn test_zero_add() {
+ type LengthCm = Length<f32, Cm>;
+ let length: LengthCm = Length::new(5.0);
+
+ let result = length - LengthCm::zero();
+
+ assert_eq!(result, length);
+ }
+
+ #[test]
+ fn test_zero_division() {
+ type LengthCm = Length<f32, Cm>;
+ let length: LengthCm = Length::new(5.0);
+ let length_zero: LengthCm = Length::zero();
+
+ let result = length / length_zero;
+
+ let expected: Scale<f32, Cm, Cm> = Scale::new(INFINITY);
+ assert_eq!(result, expected);
+ }
+}
diff --git a/third_party/rust/euclid/src/lib.rs b/third_party/rust/euclid/src/lib.rs
new file mode 100644
index 0000000000..ec7f52ff26
--- /dev/null
+++ b/third_party/rust/euclid/src/lib.rs
@@ -0,0 +1,115 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![cfg_attr(not(test), no_std)]
+
+//! A collection of strongly typed math tools for computer graphics with an inclination
+//! towards 2d graphics and layout.
+//!
+//! All types are generic over the scalar type of their component (`f32`, `i32`, etc.),
+//! and tagged with a generic Unit parameter which is useful to prevent mixing
+//! values from different spaces. For example it should not be legal to translate
+//! a screen-space position by a world-space vector and this can be expressed using
+//! the generic Unit parameter.
+//!
+//! This unit system is not mandatory and all structures have an alias
+//! with the default unit: `UnknownUnit`.
+//! for example ```default::Point2D<T>``` is equivalent to ```Point2D<T, UnknownUnit>```.
+//! Client code typically creates a set of aliases for each type and doesn't need
+//! to deal with the specifics of typed units further. For example:
+//!
+//! ```rust
+//! use euclid::*;
+//! pub struct ScreenSpace;
+//! pub type ScreenPoint = Point2D<f32, ScreenSpace>;
+//! pub type ScreenSize = Size2D<f32, ScreenSpace>;
+//! pub struct WorldSpace;
+//! pub type WorldPoint = Point3D<f32, WorldSpace>;
+//! pub type ProjectionMatrix = Transform3D<f32, WorldSpace, ScreenSpace>;
+//! // etc...
+//! ```
+//!
+//! All euclid types are marked `#[repr(C)]` in order to facilitate exposing them to
+//! foreign function interfaces (provided the underlying scalar type is also `repr(C)`).
+//!
+#![deny(unconditional_recursion)]
+
+pub use crate::angle::Angle;
+pub use crate::box2d::Box2D;
+pub use crate::homogen::HomogeneousVector;
+pub use crate::length::Length;
+pub use crate::point::{point2, point3, Point2D, Point3D};
+pub use crate::scale::Scale;
+pub use crate::transform2d::Transform2D;
+pub use crate::transform3d::Transform3D;
+pub use crate::vector::{bvec2, bvec3, BoolVector2D, BoolVector3D};
+pub use crate::vector::{vec2, vec3, Vector2D, Vector3D};
+
+pub use crate::box3d::{box3d, Box3D};
+pub use crate::rect::{rect, Rect};
+pub use crate::rigid::RigidTransform3D;
+pub use crate::rotation::{Rotation2D, Rotation3D};
+pub use crate::side_offsets::SideOffsets2D;
+pub use crate::size::{size2, size3, Size2D, Size3D};
+pub use crate::translation::{Translation2D, Translation3D};
+pub use crate::trig::Trig;
+
+#[macro_use]
+mod macros;
+
+mod angle;
+pub mod approxeq;
+pub mod approxord;
+mod box2d;
+mod box3d;
+mod homogen;
+mod length;
+pub mod num;
+mod point;
+mod rect;
+mod rigid;
+mod rotation;
+mod scale;
+mod side_offsets;
+mod size;
+mod transform2d;
+mod transform3d;
+mod translation;
+mod trig;
+mod vector;
+
+/// The default unit.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct UnknownUnit;
+
+pub mod default {
+ //! A set of aliases for all types, tagged with the default unknown unit.
+
+ use super::UnknownUnit;
+ pub type Length<T> = super::Length<T, UnknownUnit>;
+ pub type Point2D<T> = super::Point2D<T, UnknownUnit>;
+ pub type Point3D<T> = super::Point3D<T, UnknownUnit>;
+ pub type Vector2D<T> = super::Vector2D<T, UnknownUnit>;
+ pub type Vector3D<T> = super::Vector3D<T, UnknownUnit>;
+ pub type HomogeneousVector<T> = super::HomogeneousVector<T, UnknownUnit>;
+ pub type Size2D<T> = super::Size2D<T, UnknownUnit>;
+ pub type Size3D<T> = super::Size3D<T, UnknownUnit>;
+ pub type Rect<T> = super::Rect<T, UnknownUnit>;
+ pub type Box2D<T> = super::Box2D<T, UnknownUnit>;
+ pub type Box3D<T> = super::Box3D<T, UnknownUnit>;
+ pub type SideOffsets2D<T> = super::SideOffsets2D<T, UnknownUnit>;
+ pub type Transform2D<T> = super::Transform2D<T, UnknownUnit, UnknownUnit>;
+ pub type Transform3D<T> = super::Transform3D<T, UnknownUnit, UnknownUnit>;
+ pub type Rotation2D<T> = super::Rotation2D<T, UnknownUnit, UnknownUnit>;
+ pub type Rotation3D<T> = super::Rotation3D<T, UnknownUnit, UnknownUnit>;
+ pub type Translation2D<T> = super::Translation2D<T, UnknownUnit, UnknownUnit>;
+ pub type Translation3D<T> = super::Translation3D<T, UnknownUnit, UnknownUnit>;
+ pub type Scale<T> = super::Scale<T, UnknownUnit, UnknownUnit>;
+ pub type RigidTransform3D<T> = super::RigidTransform3D<T, UnknownUnit, UnknownUnit>;
+}
diff --git a/third_party/rust/euclid/src/macros.rs b/third_party/rust/euclid/src/macros.rs
new file mode 100644
index 0000000000..9cc392eb30
--- /dev/null
+++ b/third_party/rust/euclid/src/macros.rs
@@ -0,0 +1,30 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+macro_rules! mint_vec {
+ ($name:ident [ $($field:ident),* ] = $std_name:ident) => {
+ #[cfg(feature = "mint")]
+ impl<T, U> From<mint::$std_name<T>> for $name<T, U> {
+ fn from(v: mint::$std_name<T>) -> Self {
+ $name {
+ $( $field: v.$field, )*
+ _unit: PhantomData,
+ }
+ }
+ }
+ #[cfg(feature = "mint")]
+ impl<T, U> Into<mint::$std_name<T>> for $name<T, U> {
+ fn into(self) -> mint::$std_name<T> {
+ mint::$std_name {
+ $( $field: self.$field, )*
+ }
+ }
+ }
+ }
+}
diff --git a/third_party/rust/euclid/src/num.rs b/third_party/rust/euclid/src/num.rs
new file mode 100644
index 0000000000..e5135d074e
--- /dev/null
+++ b/third_party/rust/euclid/src/num.rs
@@ -0,0 +1,128 @@
+// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//! A one-dimensional length, tagged with its units.
+
+use num_traits;
+
+// Euclid has its own Zero and One traits instead of of using the num_traits equivalents.
+// Unfortunately, num_traits::Zero requires Add, which opens a bag of sad things:
+// - Most importantly, for Point2D to implement Zero it would need to implement Add<Self> which we
+// don't want (we allow "Point + Vector" and "Vector + Vector" semantics and purposefully disallow
+// "Point + Point".
+// - Some operations that require, say, One and Div (for example Scale::inv) currently return a
+// type parameterized over T::Output which is ambiguous with num_traits::One because it inherits
+// Mul which also has an Output associated type. To fix it need to complicate type signatures
+// by using <T as Trait>::Output which makes the code and documentation harder to read.
+//
+// On the other hand, euclid::num::Zero/One are automatically implemented for all types that
+// implement their num_traits counterpart. Euclid users never need to explicitly use
+// euclid::num::Zero/One and can/should only manipulate the num_traits equivalents without risk
+// of compatibility issues with euclid.
+
+pub trait Zero {
+ fn zero() -> Self;
+}
+
+impl<T: num_traits::Zero> Zero for T {
+ fn zero() -> T {
+ num_traits::Zero::zero()
+ }
+}
+
+pub trait One {
+ fn one() -> Self;
+}
+
+impl<T: num_traits::One> One for T {
+ fn one() -> T {
+ num_traits::One::one()
+ }
+}
+
+/// Defines the nearest integer value to the original value.
+pub trait Round: Copy {
+ /// Rounds to the nearest integer value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ #[must_use]
+ fn round(self) -> Self;
+}
+/// Defines the biggest integer equal or lower than the original value.
+pub trait Floor: Copy {
+ /// Rounds to the biggest integer equal or lower than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ #[must_use]
+ fn floor(self) -> Self;
+}
+/// Defines the smallest integer equal or greater than the original value.
+pub trait Ceil: Copy {
+ /// Rounds to the smallest integer equal or greater than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ #[must_use]
+ fn ceil(self) -> Self;
+}
+
+macro_rules! num_int {
+ ($ty:ty) => {
+ impl Round for $ty {
+ #[inline]
+ fn round(self) -> $ty {
+ self
+ }
+ }
+ impl Floor for $ty {
+ #[inline]
+ fn floor(self) -> $ty {
+ self
+ }
+ }
+ impl Ceil for $ty {
+ #[inline]
+ fn ceil(self) -> $ty {
+ self
+ }
+ }
+ };
+}
+
+macro_rules! num_float {
+ ($ty:ty) => {
+ impl Round for $ty {
+ #[inline]
+ fn round(self) -> $ty {
+ (self + 0.5).floor()
+ }
+ }
+ impl Floor for $ty {
+ #[inline]
+ fn floor(self) -> $ty {
+ num_traits::Float::floor(self)
+ }
+ }
+ impl Ceil for $ty {
+ #[inline]
+ fn ceil(self) -> $ty {
+ num_traits::Float::ceil(self)
+ }
+ }
+ };
+}
+
+num_int!(i16);
+num_int!(u16);
+num_int!(i32);
+num_int!(u32);
+num_int!(i64);
+num_int!(u64);
+num_int!(isize);
+num_int!(usize);
+num_float!(f32);
+num_float!(f64);
diff --git a/third_party/rust/euclid/src/point.rs b/third_party/rust/euclid/src/point.rs
new file mode 100644
index 0000000000..f364f8626a
--- /dev/null
+++ b/third_party/rust/euclid/src/point.rs
@@ -0,0 +1,2041 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::UnknownUnit;
+use crate::approxeq::ApproxEq;
+use crate::approxord::{max, min};
+use crate::length::Length;
+use crate::num::*;
+use crate::scale::Scale;
+use crate::size::{Size2D, Size3D};
+use crate::vector::{vec2, vec3, Vector2D, Vector3D};
+use core::cmp::{Eq, PartialEq};
+use core::fmt;
+use core::hash::Hash;
+use core::marker::PhantomData;
+use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+#[cfg(feature = "mint")]
+use mint;
+use num_traits::real::Real;
+use num_traits::{Float, NumCast};
+#[cfg(feature = "serde")]
+use serde;
+
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A 2d Point tagged with a unit.
+#[repr(C)]
+pub struct Point2D<T, U> {
+ pub x: T,
+ pub y: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<U>,
+}
+
+impl<T: Copy, U> Copy for Point2D<T, U> {}
+
+impl<T: Clone, U> Clone for Point2D<T, U> {
+ fn clone(&self) -> Self {
+ Point2D {
+ x: self.x.clone(),
+ y: self.y.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de, T, U> serde::Deserialize<'de> for Point2D<T, U>
+where
+ T: serde::Deserialize<'de>,
+{
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let (x, y) = serde::Deserialize::deserialize(deserializer)?;
+ Ok(Point2D {
+ x,
+ y,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<T, U> serde::Serialize for Point2D<T, U>
+where
+ T: serde::Serialize,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ (&self.x, &self.y).serialize(serializer)
+ }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a, T, U> arbitrary::Arbitrary<'a> for Point2D<T, U>
+where
+ T: arbitrary::Arbitrary<'a>,
+{
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>
+ {
+ let (x, y) = arbitrary::Arbitrary::arbitrary(u)?;
+ Ok(Point2D {
+ x,
+ y,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for Point2D<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for Point2D<T, U> {}
+
+impl<T, U> Eq for Point2D<T, U> where T: Eq {}
+
+impl<T, U> PartialEq for Point2D<T, U>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.x == other.x && self.y == other.y
+ }
+}
+
+impl<T, U> Hash for Point2D<T, U>
+where
+ T: Hash,
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.x.hash(h);
+ self.y.hash(h);
+ }
+}
+
+mint_vec!(Point2D[x, y] = Point2);
+
+impl<T: fmt::Debug, U> fmt::Debug for Point2D<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_tuple("").field(&self.x).field(&self.y).finish()
+ }
+}
+
+impl<T: Default, U> Default for Point2D<T, U> {
+ fn default() -> Self {
+ Point2D::new(Default::default(), Default::default())
+ }
+}
+
+impl<T, U> Point2D<T, U> {
+ /// Constructor, setting all components to zero.
+ #[inline]
+ pub fn origin() -> Self
+ where
+ T: Zero,
+ {
+ point2(Zero::zero(), Zero::zero())
+ }
+
+ /// The same as [`origin()`](#method.origin).
+ #[inline]
+ pub fn zero() -> Self
+ where
+ T: Zero,
+ {
+ Self::origin()
+ }
+
+ /// Constructor taking scalar values directly.
+ #[inline]
+ pub const fn new(x: T, y: T) -> Self {
+ Point2D {
+ x,
+ y,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Constructor taking properly Lengths instead of scalar values.
+ #[inline]
+ pub fn from_lengths(x: Length<T, U>, y: Length<T, U>) -> Self {
+ point2(x.0, y.0)
+ }
+
+ /// Constructor setting all components to the same value.
+ #[inline]
+ pub fn splat(v: T) -> Self
+ where
+ T: Clone,
+ {
+ Point2D {
+ x: v.clone(),
+ y: v,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(p: Point2D<T, UnknownUnit>) -> Self {
+ point2(p.x, p.y)
+ }
+}
+
+impl<T: Copy, U> Point2D<T, U> {
+ /// Create a 3d point from this one, using the specified z value.
+ #[inline]
+ pub fn extend(self, z: T) -> Point3D<T, U> {
+ point3(self.x, self.y, z)
+ }
+
+ /// Cast this point into a vector.
+ ///
+ /// Equivalent to subtracting the origin from this point.
+ #[inline]
+ pub fn to_vector(self) -> Vector2D<T, U> {
+ Vector2D {
+ x: self.x,
+ y: self.y,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Swap x and y.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::{Point2D, point2};
+ /// enum Mm {}
+ ///
+ /// let point: Point2D<_, Mm> = point2(1, -8);
+ ///
+ /// assert_eq!(point.yx(), point2(-8, 1));
+ /// ```
+ #[inline]
+ pub fn yx(self) -> Self {
+ point2(self.y, self.x)
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::{Point2D, point2};
+ /// enum Mm {}
+ ///
+ /// let point: Point2D<_, Mm> = point2(1, -8);
+ ///
+ /// assert_eq!(point.x, point.to_untyped().x);
+ /// assert_eq!(point.y, point.to_untyped().y);
+ /// ```
+ #[inline]
+ pub fn to_untyped(self) -> Point2D<T, UnknownUnit> {
+ point2(self.x, self.y)
+ }
+
+ /// Cast the unit, preserving the numeric value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::{Point2D, point2};
+ /// enum Mm {}
+ /// enum Cm {}
+ ///
+ /// let point: Point2D<_, Mm> = point2(1, -8);
+ ///
+ /// assert_eq!(point.x, point.cast_unit::<Cm>().x);
+ /// assert_eq!(point.y, point.cast_unit::<Cm>().y);
+ /// ```
+ #[inline]
+ pub fn cast_unit<V>(self) -> Point2D<T, V> {
+ point2(self.x, self.y)
+ }
+
+ /// Cast into an array with x and y.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::{Point2D, point2};
+ /// enum Mm {}
+ ///
+ /// let point: Point2D<_, Mm> = point2(1, -8);
+ ///
+ /// assert_eq!(point.to_array(), [1, -8]);
+ /// ```
+ #[inline]
+ pub fn to_array(self) -> [T; 2] {
+ [self.x, self.y]
+ }
+
+ /// Cast into a tuple with x and y.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::{Point2D, point2};
+ /// enum Mm {}
+ ///
+ /// let point: Point2D<_, Mm> = point2(1, -8);
+ ///
+ /// assert_eq!(point.to_tuple(), (1, -8));
+ /// ```
+ #[inline]
+ pub fn to_tuple(self) -> (T, T) {
+ (self.x, self.y)
+ }
+
+ /// Convert into a 3d point with z-coordinate equals to zero.
+ #[inline]
+ pub fn to_3d(self) -> Point3D<T, U>
+ where
+ T: Zero,
+ {
+ point3(self.x, self.y, Zero::zero())
+ }
+
+ /// Rounds each component to the nearest integer value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::point2;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(point2::<_, Mm>(-0.1, -0.8).round(), point2::<_, Mm>(0.0, -1.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn round(self) -> Self
+ where
+ T: Round,
+ {
+ point2(self.x.round(), self.y.round())
+ }
+
+ /// Rounds each component to the smallest integer equal or greater than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::point2;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(point2::<_, Mm>(-0.1, -0.8).ceil(), point2::<_, Mm>(0.0, 0.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn ceil(self) -> Self
+ where
+ T: Ceil,
+ {
+ point2(self.x.ceil(), self.y.ceil())
+ }
+
+ /// Rounds each component to the biggest integer equal or lower than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::point2;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(point2::<_, Mm>(-0.1, -0.8).floor(), point2::<_, Mm>(-1.0, -1.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn floor(self) -> Self
+ where
+ T: Floor,
+ {
+ point2(self.x.floor(), self.y.floor())
+ }
+
+ /// Linearly interpolate between this point and another point.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::point2;
+ /// use euclid::default::Point2D;
+ ///
+ /// let from: Point2D<_> = point2(0.0, 10.0);
+ /// let to: Point2D<_> = point2(8.0, -4.0);
+ ///
+ /// assert_eq!(from.lerp(to, -1.0), point2(-8.0, 24.0));
+ /// assert_eq!(from.lerp(to, 0.0), point2( 0.0, 10.0));
+ /// assert_eq!(from.lerp(to, 0.5), point2( 4.0, 3.0));
+ /// assert_eq!(from.lerp(to, 1.0), point2( 8.0, -4.0));
+ /// assert_eq!(from.lerp(to, 2.0), point2(16.0, -18.0));
+ /// ```
+ #[inline]
+ pub fn lerp(self, other: Self, t: T) -> Self
+ where
+ T: One + Sub<Output = T> + Mul<Output = T> + Add<Output = T>,
+ {
+ let one_t = T::one() - t;
+ point2(one_t * self.x + t * other.x, one_t * self.y + t * other.y)
+ }
+}
+
+impl<T: PartialOrd, U> Point2D<T, U> {
+ #[inline]
+ pub fn min(self, other: Self) -> Self {
+ point2(min(self.x, other.x), min(self.y, other.y))
+ }
+
+ #[inline]
+ pub fn max(self, other: Self) -> Self {
+ point2(max(self.x, other.x), max(self.y, other.y))
+ }
+
+ /// Returns the point each component of which clamped by corresponding
+ /// components of `start` and `end`.
+ ///
+ /// Shortcut for `self.max(start).min(end)`.
+ #[inline]
+ pub fn clamp(self, start: Self, end: Self) -> Self
+ where
+ T: Copy,
+ {
+ self.max(start).min(end)
+ }
+}
+
+impl<T: NumCast + Copy, U> Point2D<T, U> {
+ /// Cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ #[inline]
+ pub fn cast<NewT: NumCast>(self) -> Point2D<NewT, U> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ pub fn try_cast<NewT: NumCast>(self) -> Option<Point2D<NewT, U>> {
+ match (NumCast::from(self.x), NumCast::from(self.y)) {
+ (Some(x), Some(y)) => Some(point2(x, y)),
+ _ => None,
+ }
+ }
+
+ // Convenience functions for common casts
+
+ /// Cast into an `f32` point.
+ #[inline]
+ pub fn to_f32(self) -> Point2D<f32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `f64` point.
+ #[inline]
+ pub fn to_f64(self) -> Point2D<f64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `usize` point, truncating decimals if any.
+ ///
+ /// When casting from floating point points, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_usize(self) -> Point2D<usize, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u32` point, truncating decimals if any.
+ ///
+ /// When casting from floating point points, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_u32(self) -> Point2D<u32, U> {
+ self.cast()
+ }
+
+ /// Cast into an i32 point, truncating decimals if any.
+ ///
+ /// When casting from floating point points, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i32(self) -> Point2D<i32, U> {
+ self.cast()
+ }
+
+ /// Cast into an i64 point, truncating decimals if any.
+ ///
+ /// When casting from floating point points, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i64(self) -> Point2D<i64, U> {
+ self.cast()
+ }
+}
+
+impl<T: Float, U> Point2D<T, U> {
+ /// Returns true if all members are finite.
+ #[inline]
+ pub fn is_finite(self) -> bool {
+ self.x.is_finite() && self.y.is_finite()
+ }
+}
+
+impl<T: Copy + Add<T, Output = T>, U> Point2D<T, U> {
+ #[inline]
+ pub fn add_size(self, other: &Size2D<T, U>) -> Self {
+ point2(self.x + other.width, self.y + other.height)
+ }
+}
+
+impl<T: Real + Sub<T, Output = T>, U> Point2D<T, U> {
+ #[inline]
+ pub fn distance_to(self, other: Self) -> T {
+ (self - other).length()
+ }
+}
+
+impl<T: Neg, U> Neg for Point2D<T, U> {
+ type Output = Point2D<T::Output, U>;
+
+ #[inline]
+ fn neg(self) -> Self::Output {
+ point2(-self.x, -self.y)
+ }
+}
+
+impl<T: Add, U> Add<Size2D<T, U>> for Point2D<T, U> {
+ type Output = Point2D<T::Output, U>;
+
+ #[inline]
+ fn add(self, other: Size2D<T, U>) -> Self::Output {
+ point2(self.x + other.width, self.y + other.height)
+ }
+}
+
+impl<T: AddAssign, U> AddAssign<Size2D<T, U>> for Point2D<T, U> {
+ #[inline]
+ fn add_assign(&mut self, other: Size2D<T, U>) {
+ self.x += other.width;
+ self.y += other.height;
+ }
+}
+
+impl<T: Add, U> Add<Vector2D<T, U>> for Point2D<T, U> {
+ type Output = Point2D<T::Output, U>;
+
+ #[inline]
+ fn add(self, other: Vector2D<T, U>) -> Self::Output {
+ point2(self.x + other.x, self.y + other.y)
+ }
+}
+
+impl<T: Copy + Add<T, Output = T>, U> AddAssign<Vector2D<T, U>> for Point2D<T, U> {
+ #[inline]
+ fn add_assign(&mut self, other: Vector2D<T, U>) {
+ *self = *self + other
+ }
+}
+
+impl<T: Sub, U> Sub for Point2D<T, U> {
+ type Output = Vector2D<T::Output, U>;
+
+ #[inline]
+ fn sub(self, other: Self) -> Self::Output {
+ vec2(self.x - other.x, self.y - other.y)
+ }
+}
+
+impl<T: Sub, U> Sub<Size2D<T, U>> for Point2D<T, U> {
+ type Output = Point2D<T::Output, U>;
+
+ #[inline]
+ fn sub(self, other: Size2D<T, U>) -> Self::Output {
+ point2(self.x - other.width, self.y - other.height)
+ }
+}
+
+impl<T: SubAssign, U> SubAssign<Size2D<T, U>> for Point2D<T, U> {
+ #[inline]
+ fn sub_assign(&mut self, other: Size2D<T, U>) {
+ self.x -= other.width;
+ self.y -= other.height;
+ }
+}
+
+impl<T: Sub, U> Sub<Vector2D<T, U>> for Point2D<T, U> {
+ type Output = Point2D<T::Output, U>;
+
+ #[inline]
+ fn sub(self, other: Vector2D<T, U>) -> Self::Output {
+ point2(self.x - other.x, self.y - other.y)
+ }
+}
+
+impl<T: Copy + Sub<T, Output = T>, U> SubAssign<Vector2D<T, U>> for Point2D<T, U> {
+ #[inline]
+ fn sub_assign(&mut self, other: Vector2D<T, U>) {
+ *self = *self - other
+ }
+}
+
+impl<T: Copy + Mul, U> Mul<T> for Point2D<T, U> {
+ type Output = Point2D<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ point2(self.x * scale, self.y * scale)
+ }
+}
+
+impl<T: Copy + Mul<T, Output = T>, U> MulAssign<T> for Point2D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: T) {
+ *self = *self * scale
+ }
+}
+
+impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Point2D<T, U1> {
+ type Output = Point2D<T::Output, U2>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ point2(self.x * scale.0, self.y * scale.0)
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Point2D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: Scale<T, U, U>) {
+ self.x *= scale.0;
+ self.y *= scale.0;
+ }
+}
+
+impl<T: Copy + Div, U> Div<T> for Point2D<T, U> {
+ type Output = Point2D<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ point2(self.x / scale, self.y / scale)
+ }
+}
+
+impl<T: Copy + Div<T, Output = T>, U> DivAssign<T> for Point2D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: T) {
+ *self = *self / scale
+ }
+}
+
+impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Point2D<T, U2> {
+ type Output = Point2D<T::Output, U1>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ point2(self.x / scale.0, self.y / scale.0)
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Point2D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: Scale<T, U, U>) {
+ self.x /= scale.0;
+ self.y /= scale.0;
+ }
+}
+
+impl<T: Zero, U> Zero for Point2D<T, U> {
+ #[inline]
+ fn zero() -> Self {
+ Self::origin()
+ }
+}
+
+impl<T: Round, U> Round for Point2D<T, U> {
+ /// See [Point2D::round()](#method.round)
+ #[inline]
+ fn round(self) -> Self {
+ self.round()
+ }
+}
+
+impl<T: Ceil, U> Ceil for Point2D<T, U> {
+ /// See [Point2D::ceil()](#method.ceil)
+ #[inline]
+ fn ceil(self) -> Self {
+ self.ceil()
+ }
+}
+
+impl<T: Floor, U> Floor for Point2D<T, U> {
+ /// See [Point2D::floor()](#method.floor)
+ #[inline]
+ fn floor(self) -> Self {
+ self.floor()
+ }
+}
+
+impl<T: ApproxEq<T>, U> ApproxEq<Point2D<T, U>> for Point2D<T, U> {
+ #[inline]
+ fn approx_epsilon() -> Self {
+ point2(T::approx_epsilon(), T::approx_epsilon())
+ }
+
+ #[inline]
+ fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool {
+ self.x.approx_eq_eps(&other.x, &eps.x) && self.y.approx_eq_eps(&other.y, &eps.y)
+ }
+}
+
+impl<T, U> Into<[T; 2]> for Point2D<T, U> {
+ fn into(self) -> [T; 2] {
+ [self.x, self.y]
+ }
+}
+
+impl<T, U> From<[T; 2]> for Point2D<T, U> {
+ fn from([x, y]: [T; 2]) -> Self {
+ point2(x, y)
+ }
+}
+
+impl<T, U> Into<(T, T)> for Point2D<T, U> {
+ fn into(self) -> (T, T) {
+ (self.x, self.y)
+ }
+}
+
+impl<T, U> From<(T, T)> for Point2D<T, U> {
+ fn from(tuple: (T, T)) -> Self {
+ point2(tuple.0, tuple.1)
+ }
+}
+
+/// A 3d Point tagged with a unit.
+#[repr(C)]
+pub struct Point3D<T, U> {
+ pub x: T,
+ pub y: T,
+ pub z: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<U>,
+}
+
+mint_vec!(Point3D[x, y, z] = Point3);
+
+impl<T: Copy, U> Copy for Point3D<T, U> {}
+
+impl<T: Clone, U> Clone for Point3D<T, U> {
+ fn clone(&self) -> Self {
+ Point3D {
+ x: self.x.clone(),
+ y: self.y.clone(),
+ z: self.z.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de, T, U> serde::Deserialize<'de> for Point3D<T, U>
+where
+ T: serde::Deserialize<'de>,
+{
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let (x, y, z) = serde::Deserialize::deserialize(deserializer)?;
+ Ok(Point3D {
+ x,
+ y,
+ z,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<T, U> serde::Serialize for Point3D<T, U>
+where
+ T: serde::Serialize,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ (&self.x, &self.y, &self.z).serialize(serializer)
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for Point3D<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for Point3D<T, U> {}
+
+impl<T, U> Eq for Point3D<T, U> where T: Eq {}
+
+impl<T, U> PartialEq for Point3D<T, U>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.x == other.x && self.y == other.y && self.z == other.z
+ }
+}
+
+impl<T, U> Hash for Point3D<T, U>
+where
+ T: Hash,
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.x.hash(h);
+ self.y.hash(h);
+ self.z.hash(h);
+ }
+}
+
+impl<T: fmt::Debug, U> fmt::Debug for Point3D<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_tuple("")
+ .field(&self.x)
+ .field(&self.y)
+ .field(&self.z)
+ .finish()
+ }
+}
+
+impl<T: Default, U> Default for Point3D<T, U> {
+ fn default() -> Self {
+ Point3D::new(Default::default(), Default::default(), Default::default())
+ }
+}
+
+impl<T, U> Point3D<T, U> {
+ /// Constructor, setting all components to zero.
+ #[inline]
+ pub fn origin() -> Self
+ where
+ T: Zero,
+ {
+ point3(Zero::zero(), Zero::zero(), Zero::zero())
+ }
+
+ /// The same as [`origin()`](#method.origin).
+ #[inline]
+ pub fn zero() -> Self
+ where
+ T: Zero,
+ {
+ Self::origin()
+ }
+
+ /// Constructor taking scalar values directly.
+ #[inline]
+ pub const fn new(x: T, y: T, z: T) -> Self {
+ Point3D {
+ x,
+ y,
+ z,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Constructor taking properly Lengths instead of scalar values.
+ #[inline]
+ pub fn from_lengths(x: Length<T, U>, y: Length<T, U>, z: Length<T, U>) -> Self {
+ point3(x.0, y.0, z.0)
+ }
+
+ /// Constructor setting all components to the same value.
+ #[inline]
+ pub fn splat(v: T) -> Self
+ where
+ T: Clone,
+ {
+ Point3D {
+ x: v.clone(),
+ y: v.clone(),
+ z: v,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(p: Point3D<T, UnknownUnit>) -> Self {
+ point3(p.x, p.y, p.z)
+ }
+}
+
+impl<T: Copy, U> Point3D<T, U> {
+ /// Cast this point into a vector.
+ ///
+ /// Equivalent to subtracting the origin to this point.
+ #[inline]
+ pub fn to_vector(self) -> Vector3D<T, U> {
+ Vector3D {
+ x: self.x,
+ y: self.y,
+ z: self.z,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Returns a 2d point using this point's x and y coordinates
+ #[inline]
+ pub fn xy(self) -> Point2D<T, U> {
+ point2(self.x, self.y)
+ }
+
+ /// Returns a 2d point using this point's x and z coordinates
+ #[inline]
+ pub fn xz(self) -> Point2D<T, U> {
+ point2(self.x, self.z)
+ }
+
+ /// Returns a 2d point using this point's x and z coordinates
+ #[inline]
+ pub fn yz(self) -> Point2D<T, U> {
+ point2(self.y, self.z)
+ }
+
+ /// Cast into an array with x, y and z.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::{Point3D, point3};
+ /// enum Mm {}
+ ///
+ /// let point: Point3D<_, Mm> = point3(1, -8, 0);
+ ///
+ /// assert_eq!(point.to_array(), [1, -8, 0]);
+ /// ```
+ #[inline]
+ pub fn to_array(self) -> [T; 3] {
+ [self.x, self.y, self.z]
+ }
+
+ #[inline]
+ pub fn to_array_4d(self) -> [T; 4]
+ where
+ T: One,
+ {
+ [self.x, self.y, self.z, One::one()]
+ }
+
+ /// Cast into a tuple with x, y and z.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::{Point3D, point3};
+ /// enum Mm {}
+ ///
+ /// let point: Point3D<_, Mm> = point3(1, -8, 0);
+ ///
+ /// assert_eq!(point.to_tuple(), (1, -8, 0));
+ /// ```
+ #[inline]
+ pub fn to_tuple(self) -> (T, T, T) {
+ (self.x, self.y, self.z)
+ }
+
+ #[inline]
+ pub fn to_tuple_4d(self) -> (T, T, T, T)
+ where
+ T: One,
+ {
+ (self.x, self.y, self.z, One::one())
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::{Point3D, point3};
+ /// enum Mm {}
+ ///
+ /// let point: Point3D<_, Mm> = point3(1, -8, 0);
+ ///
+ /// assert_eq!(point.x, point.to_untyped().x);
+ /// assert_eq!(point.y, point.to_untyped().y);
+ /// assert_eq!(point.z, point.to_untyped().z);
+ /// ```
+ #[inline]
+ pub fn to_untyped(self) -> Point3D<T, UnknownUnit> {
+ point3(self.x, self.y, self.z)
+ }
+
+ /// Cast the unit, preserving the numeric value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::{Point3D, point3};
+ /// enum Mm {}
+ /// enum Cm {}
+ ///
+ /// let point: Point3D<_, Mm> = point3(1, -8, 0);
+ ///
+ /// assert_eq!(point.x, point.cast_unit::<Cm>().x);
+ /// assert_eq!(point.y, point.cast_unit::<Cm>().y);
+ /// assert_eq!(point.z, point.cast_unit::<Cm>().z);
+ /// ```
+ #[inline]
+ pub fn cast_unit<V>(self) -> Point3D<T, V> {
+ point3(self.x, self.y, self.z)
+ }
+
+ /// Convert into a 2d point.
+ #[inline]
+ pub fn to_2d(self) -> Point2D<T, U> {
+ self.xy()
+ }
+
+ /// Rounds each component to the nearest integer value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::point3;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(point3::<_, Mm>(-0.1, -0.8, 0.4).round(), point3::<_, Mm>(0.0, -1.0, 0.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn round(self) -> Self
+ where
+ T: Round,
+ {
+ point3(self.x.round(), self.y.round(), self.z.round())
+ }
+
+ /// Rounds each component to the smallest integer equal or greater than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::point3;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(point3::<_, Mm>(-0.1, -0.8, 0.4).ceil(), point3::<_, Mm>(0.0, 0.0, 1.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn ceil(self) -> Self
+ where
+ T: Ceil,
+ {
+ point3(self.x.ceil(), self.y.ceil(), self.z.ceil())
+ }
+
+ /// Rounds each component to the biggest integer equal or lower than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::point3;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(point3::<_, Mm>(-0.1, -0.8, 0.4).floor(), point3::<_, Mm>(-1.0, -1.0, 0.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn floor(self) -> Self
+ where
+ T: Floor,
+ {
+ point3(self.x.floor(), self.y.floor(), self.z.floor())
+ }
+
+ /// Linearly interpolate between this point and another point.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::point3;
+ /// use euclid::default::Point3D;
+ ///
+ /// let from: Point3D<_> = point3(0.0, 10.0, -1.0);
+ /// let to: Point3D<_> = point3(8.0, -4.0, 0.0);
+ ///
+ /// assert_eq!(from.lerp(to, -1.0), point3(-8.0, 24.0, -2.0));
+ /// assert_eq!(from.lerp(to, 0.0), point3( 0.0, 10.0, -1.0));
+ /// assert_eq!(from.lerp(to, 0.5), point3( 4.0, 3.0, -0.5));
+ /// assert_eq!(from.lerp(to, 1.0), point3( 8.0, -4.0, 0.0));
+ /// assert_eq!(from.lerp(to, 2.0), point3(16.0, -18.0, 1.0));
+ /// ```
+ #[inline]
+ pub fn lerp(self, other: Self, t: T) -> Self
+ where
+ T: One + Sub<Output = T> + Mul<Output = T> + Add<Output = T>,
+ {
+ let one_t = T::one() - t;
+ point3(
+ one_t * self.x + t * other.x,
+ one_t * self.y + t * other.y,
+ one_t * self.z + t * other.z,
+ )
+ }
+}
+
+impl<T: PartialOrd, U> Point3D<T, U> {
+ #[inline]
+ pub fn min(self, other: Self) -> Self {
+ point3(
+ min(self.x, other.x),
+ min(self.y, other.y),
+ min(self.z, other.z),
+ )
+ }
+
+ #[inline]
+ pub fn max(self, other: Self) -> Self {
+ point3(
+ max(self.x, other.x),
+ max(self.y, other.y),
+ max(self.z, other.z),
+ )
+ }
+
+ /// Returns the point each component of which clamped by corresponding
+ /// components of `start` and `end`.
+ ///
+ /// Shortcut for `self.max(start).min(end)`.
+ #[inline]
+ pub fn clamp(self, start: Self, end: Self) -> Self
+ where
+ T: Copy,
+ {
+ self.max(start).min(end)
+ }
+}
+
+impl<T: NumCast + Copy, U> Point3D<T, U> {
+ /// Cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ #[inline]
+ pub fn cast<NewT: NumCast>(self) -> Point3D<NewT, U> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ pub fn try_cast<NewT: NumCast>(self) -> Option<Point3D<NewT, U>> {
+ match (
+ NumCast::from(self.x),
+ NumCast::from(self.y),
+ NumCast::from(self.z),
+ ) {
+ (Some(x), Some(y), Some(z)) => Some(point3(x, y, z)),
+ _ => None,
+ }
+ }
+
+ // Convenience functions for common casts
+
+ /// Cast into an `f32` point.
+ #[inline]
+ pub fn to_f32(self) -> Point3D<f32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `f64` point.
+ #[inline]
+ pub fn to_f64(self) -> Point3D<f64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `usize` point, truncating decimals if any.
+ ///
+ /// When casting from floating point points, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_usize(self) -> Point3D<usize, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u32` point, truncating decimals if any.
+ ///
+ /// When casting from floating point points, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_u32(self) -> Point3D<u32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i32` point, truncating decimals if any.
+ ///
+ /// When casting from floating point points, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i32(self) -> Point3D<i32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i64` point, truncating decimals if any.
+ ///
+ /// When casting from floating point points, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i64(self) -> Point3D<i64, U> {
+ self.cast()
+ }
+}
+
+impl<T: Float, U> Point3D<T, U> {
+ /// Returns true if all members are finite.
+ #[inline]
+ pub fn is_finite(self) -> bool {
+ self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
+ }
+}
+
+impl<T: Copy + Add<T, Output = T>, U> Point3D<T, U> {
+ #[inline]
+ pub fn add_size(self, other: Size3D<T, U>) -> Self {
+ point3(
+ self.x + other.width,
+ self.y + other.height,
+ self.z + other.depth,
+ )
+ }
+}
+
+impl<T: Real + Sub<T, Output = T>, U> Point3D<T, U> {
+ #[inline]
+ pub fn distance_to(self, other: Self) -> T {
+ (self - other).length()
+ }
+}
+
+impl<T: Neg, U> Neg for Point3D<T, U> {
+ type Output = Point3D<T::Output, U>;
+
+ #[inline]
+ fn neg(self) -> Self::Output {
+ point3(-self.x, -self.y, -self.z)
+ }
+}
+
+impl<T: Add, U> Add<Size3D<T, U>> for Point3D<T, U> {
+ type Output = Point3D<T::Output, U>;
+
+ #[inline]
+ fn add(self, other: Size3D<T, U>) -> Self::Output {
+ point3(
+ self.x + other.width,
+ self.y + other.height,
+ self.z + other.depth,
+ )
+ }
+}
+
+impl<T: AddAssign, U> AddAssign<Size3D<T, U>> for Point3D<T, U> {
+ #[inline]
+ fn add_assign(&mut self, other: Size3D<T, U>) {
+ self.x += other.width;
+ self.y += other.height;
+ self.z += other.depth;
+ }
+}
+
+impl<T: Add, U> Add<Vector3D<T, U>> for Point3D<T, U> {
+ type Output = Point3D<T::Output, U>;
+
+ #[inline]
+ fn add(self, other: Vector3D<T, U>) -> Self::Output {
+ point3(self.x + other.x, self.y + other.y, self.z + other.z)
+ }
+}
+
+impl<T: Copy + Add<T, Output = T>, U> AddAssign<Vector3D<T, U>> for Point3D<T, U> {
+ #[inline]
+ fn add_assign(&mut self, other: Vector3D<T, U>) {
+ *self = *self + other
+ }
+}
+
+impl<T: Sub, U> Sub for Point3D<T, U> {
+ type Output = Vector3D<T::Output, U>;
+
+ #[inline]
+ fn sub(self, other: Self) -> Self::Output {
+ vec3(self.x - other.x, self.y - other.y, self.z - other.z)
+ }
+}
+
+impl<T: Sub, U> Sub<Size3D<T, U>> for Point3D<T, U> {
+ type Output = Point3D<T::Output, U>;
+
+ #[inline]
+ fn sub(self, other: Size3D<T, U>) -> Self::Output {
+ point3(
+ self.x - other.width,
+ self.y - other.height,
+ self.z - other.depth,
+ )
+ }
+}
+
+impl<T: SubAssign, U> SubAssign<Size3D<T, U>> for Point3D<T, U> {
+ #[inline]
+ fn sub_assign(&mut self, other: Size3D<T, U>) {
+ self.x -= other.width;
+ self.y -= other.height;
+ self.z -= other.depth;
+ }
+}
+
+impl<T: Sub, U> Sub<Vector3D<T, U>> for Point3D<T, U> {
+ type Output = Point3D<T::Output, U>;
+
+ #[inline]
+ fn sub(self, other: Vector3D<T, U>) -> Self::Output {
+ point3(self.x - other.x, self.y - other.y, self.z - other.z)
+ }
+}
+
+impl<T: Copy + Sub<T, Output = T>, U> SubAssign<Vector3D<T, U>> for Point3D<T, U> {
+ #[inline]
+ fn sub_assign(&mut self, other: Vector3D<T, U>) {
+ *self = *self - other
+ }
+}
+
+impl<T: Copy + Mul, U> Mul<T> for Point3D<T, U> {
+ type Output = Point3D<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ point3(
+ self.x * scale,
+ self.y * scale,
+ self.z * scale,
+ )
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<T> for Point3D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: T) {
+ self.x *= scale;
+ self.y *= scale;
+ self.z *= scale;
+ }
+}
+
+impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Point3D<T, U1> {
+ type Output = Point3D<T::Output, U2>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ point3(
+ self.x * scale.0,
+ self.y * scale.0,
+ self.z * scale.0,
+ )
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Point3D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: Scale<T, U, U>) {
+ *self *= scale.0;
+ }
+}
+
+impl<T: Copy + Div, U> Div<T> for Point3D<T, U> {
+ type Output = Point3D<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ point3(
+ self.x / scale,
+ self.y / scale,
+ self.z / scale,
+ )
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<T> for Point3D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: T) {
+ self.x /= scale;
+ self.y /= scale;
+ self.z /= scale;
+ }
+}
+
+impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Point3D<T, U2> {
+ type Output = Point3D<T::Output, U1>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ point3(
+ self.x / scale.0,
+ self.y / scale.0,
+ self.z / scale.0,
+ )
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Point3D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: Scale<T, U, U>) {
+ *self /= scale.0;
+ }
+}
+
+impl<T: Zero, U> Zero for Point3D<T, U> {
+ #[inline]
+ fn zero() -> Self {
+ Self::origin()
+ }
+}
+
+impl<T: Round, U> Round for Point3D<T, U> {
+ /// See [Point3D::round()](#method.round)
+ #[inline]
+ fn round(self) -> Self {
+ self.round()
+ }
+}
+
+impl<T: Ceil, U> Ceil for Point3D<T, U> {
+ /// See [Point3D::ceil()](#method.ceil)
+ #[inline]
+ fn ceil(self) -> Self {
+ self.ceil()
+ }
+}
+
+impl<T: Floor, U> Floor for Point3D<T, U> {
+ /// See [Point3D::floor()](#method.floor)
+ #[inline]
+ fn floor(self) -> Self {
+ self.floor()
+ }
+}
+
+impl<T: ApproxEq<T>, U> ApproxEq<Point3D<T, U>> for Point3D<T, U> {
+ #[inline]
+ fn approx_epsilon() -> Self {
+ point3(
+ T::approx_epsilon(),
+ T::approx_epsilon(),
+ T::approx_epsilon(),
+ )
+ }
+
+ #[inline]
+ fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool {
+ self.x.approx_eq_eps(&other.x, &eps.x)
+ && self.y.approx_eq_eps(&other.y, &eps.y)
+ && self.z.approx_eq_eps(&other.z, &eps.z)
+ }
+}
+
+impl<T, U> Into<[T; 3]> for Point3D<T, U> {
+ fn into(self) -> [T; 3] {
+ [self.x, self.y, self.z]
+ }
+}
+
+impl<T, U> From<[T; 3]> for Point3D<T, U> {
+ fn from([x, y, z]: [T; 3]) -> Self {
+ point3(x, y, z)
+ }
+}
+
+impl<T, U> Into<(T, T, T)> for Point3D<T, U> {
+ fn into(self) -> (T, T, T) {
+ (self.x, self.y, self.z)
+ }
+}
+
+impl<T, U> From<(T, T, T)> for Point3D<T, U> {
+ fn from(tuple: (T, T, T)) -> Self {
+ point3(tuple.0, tuple.1, tuple.2)
+ }
+}
+
+/// Shorthand for `Point2D::new(x, y)`.
+#[inline]
+pub const fn point2<T, U>(x: T, y: T) -> Point2D<T, U> {
+ Point2D {
+ x,
+ y,
+ _unit: PhantomData,
+ }
+}
+
+/// Shorthand for `Point3D::new(x, y)`.
+#[inline]
+pub const fn point3<T, U>(x: T, y: T, z: T) -> Point3D<T, U> {
+ Point3D {
+ x,
+ y,
+ z,
+ _unit: PhantomData,
+ }
+}
+
+#[cfg(test)]
+mod point2d {
+ use crate::default::Point2D;
+ use crate::point2;
+
+ #[cfg(feature = "mint")]
+ use mint;
+
+ #[test]
+ pub fn test_min() {
+ let p1 = Point2D::new(1.0, 3.0);
+ let p2 = Point2D::new(2.0, 2.0);
+
+ let result = p1.min(p2);
+
+ assert_eq!(result, Point2D::new(1.0, 2.0));
+ }
+
+ #[test]
+ pub fn test_max() {
+ let p1 = Point2D::new(1.0, 3.0);
+ let p2 = Point2D::new(2.0, 2.0);
+
+ let result = p1.max(p2);
+
+ assert_eq!(result, Point2D::new(2.0, 3.0));
+ }
+
+ #[cfg(feature = "mint")]
+ #[test]
+ pub fn test_mint() {
+ let p1 = Point2D::new(1.0, 3.0);
+ let pm: mint::Point2<_> = p1.into();
+ let p2 = Point2D::from(pm);
+
+ assert_eq!(p1, p2);
+ }
+
+ #[test]
+ pub fn test_conv_vector() {
+ for i in 0..100 {
+ // We don't care about these values as long as they are not the same.
+ let x = i as f32 * 0.012345;
+ let y = i as f32 * 0.987654;
+ let p: Point2D<f32> = point2(x, y);
+ assert_eq!(p.to_vector().to_point(), p);
+ }
+ }
+
+ #[test]
+ pub fn test_swizzling() {
+ let p: Point2D<i32> = point2(1, 2);
+ assert_eq!(p.yx(), point2(2, 1));
+ }
+
+ #[test]
+ pub fn test_distance_to() {
+ let p1 = Point2D::new(1.0, 2.0);
+ let p2 = Point2D::new(2.0, 2.0);
+
+ assert_eq!(p1.distance_to(p2), 1.0);
+
+ let p1 = Point2D::new(1.0, 2.0);
+ let p2 = Point2D::new(1.0, 4.0);
+
+ assert_eq!(p1.distance_to(p2), 2.0);
+ }
+
+ mod ops {
+ use crate::default::Point2D;
+ use crate::scale::Scale;
+ use crate::{size2, vec2, Vector2D};
+
+ pub enum Mm {}
+ pub enum Cm {}
+
+ pub type Point2DMm<T> = crate::Point2D<T, Mm>;
+ pub type Point2DCm<T> = crate::Point2D<T, Cm>;
+
+ #[test]
+ pub fn test_neg() {
+ assert_eq!(-Point2D::new(1.0, 2.0), Point2D::new(-1.0, -2.0));
+ assert_eq!(-Point2D::new(0.0, 0.0), Point2D::new(-0.0, -0.0));
+ assert_eq!(-Point2D::new(-1.0, -2.0), Point2D::new(1.0, 2.0));
+ }
+
+ #[test]
+ pub fn test_add_size() {
+ let p1 = Point2DMm::new(1.0, 2.0);
+ let p2 = size2(3.0, 4.0);
+
+ let result = p1 + p2;
+
+ assert_eq!(result, Point2DMm::new(4.0, 6.0));
+ }
+
+ #[test]
+ pub fn test_add_assign_size() {
+ let mut p1 = Point2DMm::new(1.0, 2.0);
+
+ p1 += size2(3.0, 4.0);
+
+ assert_eq!(p1, Point2DMm::new(4.0, 6.0));
+ }
+
+ #[test]
+ pub fn test_add_vec() {
+ let p1 = Point2DMm::new(1.0, 2.0);
+ let p2 = vec2(3.0, 4.0);
+
+ let result = p1 + p2;
+
+ assert_eq!(result, Point2DMm::new(4.0, 6.0));
+ }
+
+ #[test]
+ pub fn test_add_assign_vec() {
+ let mut p1 = Point2DMm::new(1.0, 2.0);
+
+ p1 += vec2(3.0, 4.0);
+
+ assert_eq!(p1, Point2DMm::new(4.0, 6.0));
+ }
+
+ #[test]
+ pub fn test_sub() {
+ let p1 = Point2DMm::new(1.0, 2.0);
+ let p2 = Point2DMm::new(3.0, 4.0);
+
+ let result = p1 - p2;
+
+ assert_eq!(result, Vector2D::<_, Mm>::new(-2.0, -2.0));
+ }
+
+ #[test]
+ pub fn test_sub_size() {
+ let p1 = Point2DMm::new(1.0, 2.0);
+ let p2 = size2(3.0, 4.0);
+
+ let result = p1 - p2;
+
+ assert_eq!(result, Point2DMm::new(-2.0, -2.0));
+ }
+
+ #[test]
+ pub fn test_sub_assign_size() {
+ let mut p1 = Point2DMm::new(1.0, 2.0);
+
+ p1 -= size2(3.0, 4.0);
+
+ assert_eq!(p1, Point2DMm::new(-2.0, -2.0));
+ }
+
+ #[test]
+ pub fn test_sub_vec() {
+ let p1 = Point2DMm::new(1.0, 2.0);
+ let p2 = vec2(3.0, 4.0);
+
+ let result = p1 - p2;
+
+ assert_eq!(result, Point2DMm::new(-2.0, -2.0));
+ }
+
+ #[test]
+ pub fn test_sub_assign_vec() {
+ let mut p1 = Point2DMm::new(1.0, 2.0);
+
+ p1 -= vec2(3.0, 4.0);
+
+ assert_eq!(p1, Point2DMm::new(-2.0, -2.0));
+ }
+
+ #[test]
+ pub fn test_mul_scalar() {
+ let p1: Point2D<f32> = Point2D::new(3.0, 5.0);
+
+ let result = p1 * 5.0;
+
+ assert_eq!(result, Point2D::new(15.0, 25.0));
+ }
+
+ #[test]
+ pub fn test_mul_assign_scalar() {
+ let mut p1 = Point2D::new(3.0, 5.0);
+
+ p1 *= 5.0;
+
+ assert_eq!(p1, Point2D::new(15.0, 25.0));
+ }
+
+ #[test]
+ pub fn test_mul_scale() {
+ let p1 = Point2DMm::new(1.0, 2.0);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = p1 * cm_per_mm;
+
+ assert_eq!(result, Point2DCm::new(0.1, 0.2));
+ }
+
+ #[test]
+ pub fn test_mul_assign_scale() {
+ let mut p1 = Point2DMm::new(1.0, 2.0);
+ let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
+
+ p1 *= scale;
+
+ assert_eq!(p1, Point2DMm::new(0.1, 0.2));
+ }
+
+ #[test]
+ pub fn test_div_scalar() {
+ let p1: Point2D<f32> = Point2D::new(15.0, 25.0);
+
+ let result = p1 / 5.0;
+
+ assert_eq!(result, Point2D::new(3.0, 5.0));
+ }
+
+ #[test]
+ pub fn test_div_assign_scalar() {
+ let mut p1: Point2D<f32> = Point2D::new(15.0, 25.0);
+
+ p1 /= 5.0;
+
+ assert_eq!(p1, Point2D::new(3.0, 5.0));
+ }
+
+ #[test]
+ pub fn test_div_scale() {
+ let p1 = Point2DCm::new(0.1, 0.2);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = p1 / cm_per_mm;
+
+ assert_eq!(result, Point2DMm::new(1.0, 2.0));
+ }
+
+ #[test]
+ pub fn test_div_assign_scale() {
+ let mut p1 = Point2DMm::new(0.1, 0.2);
+ let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
+
+ p1 /= scale;
+
+ assert_eq!(p1, Point2DMm::new(1.0, 2.0));
+ }
+
+ #[test]
+ pub fn test_point_debug_formatting() {
+ let n = 1.23456789;
+ let p1 = Point2D::new(n, -n);
+ let should_be = format!("({:.4}, {:.4})", n, -n);
+
+ let got = format!("{:.4?}", p1);
+
+ assert_eq!(got, should_be);
+ }
+ }
+}
+
+#[cfg(test)]
+mod point3d {
+ use crate::default;
+ use crate::default::Point3D;
+ use crate::{point2, point3};
+ #[cfg(feature = "mint")]
+ use mint;
+
+ #[test]
+ pub fn test_min() {
+ let p1 = Point3D::new(1.0, 3.0, 5.0);
+ let p2 = Point3D::new(2.0, 2.0, -1.0);
+
+ let result = p1.min(p2);
+
+ assert_eq!(result, Point3D::new(1.0, 2.0, -1.0));
+ }
+
+ #[test]
+ pub fn test_max() {
+ let p1 = Point3D::new(1.0, 3.0, 5.0);
+ let p2 = Point3D::new(2.0, 2.0, -1.0);
+
+ let result = p1.max(p2);
+
+ assert_eq!(result, Point3D::new(2.0, 3.0, 5.0));
+ }
+
+ #[test]
+ pub fn test_conv_vector() {
+ use crate::point3;
+ for i in 0..100 {
+ // We don't care about these values as long as they are not the same.
+ let x = i as f32 * 0.012345;
+ let y = i as f32 * 0.987654;
+ let z = x * y;
+ let p: Point3D<f32> = point3(x, y, z);
+ assert_eq!(p.to_vector().to_point(), p);
+ }
+ }
+
+ #[test]
+ pub fn test_swizzling() {
+ let p: default::Point3D<i32> = point3(1, 2, 3);
+ assert_eq!(p.xy(), point2(1, 2));
+ assert_eq!(p.xz(), point2(1, 3));
+ assert_eq!(p.yz(), point2(2, 3));
+ }
+
+ #[test]
+ pub fn test_distance_to() {
+ let p1 = Point3D::new(1.0, 2.0, 3.0);
+ let p2 = Point3D::new(2.0, 2.0, 3.0);
+
+ assert_eq!(p1.distance_to(p2), 1.0);
+
+ let p1 = Point3D::new(1.0, 2.0, 3.0);
+ let p2 = Point3D::new(1.0, 4.0, 3.0);
+
+ assert_eq!(p1.distance_to(p2), 2.0);
+
+ let p1 = Point3D::new(1.0, 2.0, 3.0);
+ let p2 = Point3D::new(1.0, 2.0, 6.0);
+
+ assert_eq!(p1.distance_to(p2), 3.0);
+ }
+
+ #[cfg(feature = "mint")]
+ #[test]
+ pub fn test_mint() {
+ let p1 = Point3D::new(1.0, 3.0, 5.0);
+ let pm: mint::Point3<_> = p1.into();
+ let p2 = Point3D::from(pm);
+
+ assert_eq!(p1, p2);
+ }
+
+ mod ops {
+ use crate::default::Point3D;
+ use crate::scale::Scale;
+ use crate::{size3, vec3, Vector3D};
+
+ pub enum Mm {}
+ pub enum Cm {}
+
+ pub type Point3DMm<T> = crate::Point3D<T, Mm>;
+ pub type Point3DCm<T> = crate::Point3D<T, Cm>;
+
+ #[test]
+ pub fn test_neg() {
+ assert_eq!(-Point3D::new(1.0, 2.0, 3.0), Point3D::new(-1.0, -2.0, -3.0));
+ assert_eq!(-Point3D::new(0.0, 0.0, 0.0), Point3D::new(-0.0, -0.0, -0.0));
+ assert_eq!(-Point3D::new(-1.0, -2.0, -3.0), Point3D::new(1.0, 2.0, 3.0));
+ }
+
+ #[test]
+ pub fn test_add_size() {
+ let p1 = Point3DMm::new(1.0, 2.0, 3.0);
+ let p2 = size3(4.0, 5.0, 6.0);
+
+ let result = p1 + p2;
+
+ assert_eq!(result, Point3DMm::new(5.0, 7.0, 9.0));
+ }
+
+ #[test]
+ pub fn test_add_assign_size() {
+ let mut p1 = Point3DMm::new(1.0, 2.0, 3.0);
+
+ p1 += size3(4.0, 5.0, 6.0);
+
+ assert_eq!(p1, Point3DMm::new(5.0, 7.0, 9.0));
+ }
+
+ #[test]
+ pub fn test_add_vec() {
+ let p1 = Point3DMm::new(1.0, 2.0, 3.0);
+ let p2 = vec3(4.0, 5.0, 6.0);
+
+ let result = p1 + p2;
+
+ assert_eq!(result, Point3DMm::new(5.0, 7.0, 9.0));
+ }
+
+ #[test]
+ pub fn test_add_assign_vec() {
+ let mut p1 = Point3DMm::new(1.0, 2.0, 3.0);
+
+ p1 += vec3(4.0, 5.0, 6.0);
+
+ assert_eq!(p1, Point3DMm::new(5.0, 7.0, 9.0));
+ }
+
+ #[test]
+ pub fn test_sub() {
+ let p1 = Point3DMm::new(1.0, 2.0, 3.0);
+ let p2 = Point3DMm::new(4.0, 5.0, 6.0);
+
+ let result = p1 - p2;
+
+ assert_eq!(result, Vector3D::<_, Mm>::new(-3.0, -3.0, -3.0));
+ }
+
+ #[test]
+ pub fn test_sub_size() {
+ let p1 = Point3DMm::new(1.0, 2.0, 3.0);
+ let p2 = size3(4.0, 5.0, 6.0);
+
+ let result = p1 - p2;
+
+ assert_eq!(result, Point3DMm::new(-3.0, -3.0, -3.0));
+ }
+
+ #[test]
+ pub fn test_sub_assign_size() {
+ let mut p1 = Point3DMm::new(1.0, 2.0, 3.0);
+
+ p1 -= size3(4.0, 5.0, 6.0);
+
+ assert_eq!(p1, Point3DMm::new(-3.0, -3.0, -3.0));
+ }
+
+ #[test]
+ pub fn test_sub_vec() {
+ let p1 = Point3DMm::new(1.0, 2.0, 3.0);
+ let p2 = vec3(4.0, 5.0, 6.0);
+
+ let result = p1 - p2;
+
+ assert_eq!(result, Point3DMm::new(-3.0, -3.0, -3.0));
+ }
+
+ #[test]
+ pub fn test_sub_assign_vec() {
+ let mut p1 = Point3DMm::new(1.0, 2.0, 3.0);
+
+ p1 -= vec3(4.0, 5.0, 6.0);
+
+ assert_eq!(p1, Point3DMm::new(-3.0, -3.0, -3.0));
+ }
+
+ #[test]
+ pub fn test_mul_scalar() {
+ let p1: Point3D<f32> = Point3D::new(3.0, 5.0, 7.0);
+
+ let result = p1 * 5.0;
+
+ assert_eq!(result, Point3D::new(15.0, 25.0, 35.0));
+ }
+
+ #[test]
+ pub fn test_mul_assign_scalar() {
+ let mut p1: Point3D<f32> = Point3D::new(3.0, 5.0, 7.0);
+
+ p1 *= 5.0;
+
+ assert_eq!(p1, Point3D::new(15.0, 25.0, 35.0));
+ }
+
+ #[test]
+ pub fn test_mul_scale() {
+ let p1 = Point3DMm::new(1.0, 2.0, 3.0);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = p1 * cm_per_mm;
+
+ assert_eq!(result, Point3DCm::new(0.1, 0.2, 0.3));
+ }
+
+ #[test]
+ pub fn test_mul_assign_scale() {
+ let mut p1 = Point3DMm::new(1.0, 2.0, 3.0);
+ let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
+
+ p1 *= scale;
+
+ assert_eq!(p1, Point3DMm::new(0.1, 0.2, 0.3));
+ }
+
+ #[test]
+ pub fn test_div_scalar() {
+ let p1: Point3D<f32> = Point3D::new(15.0, 25.0, 35.0);
+
+ let result = p1 / 5.0;
+
+ assert_eq!(result, Point3D::new(3.0, 5.0, 7.0));
+ }
+
+ #[test]
+ pub fn test_div_assign_scalar() {
+ let mut p1: Point3D<f32> = Point3D::new(15.0, 25.0, 35.0);
+
+ p1 /= 5.0;
+
+ assert_eq!(p1, Point3D::new(3.0, 5.0, 7.0));
+ }
+
+ #[test]
+ pub fn test_div_scale() {
+ let p1 = Point3DCm::new(0.1, 0.2, 0.3);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = p1 / cm_per_mm;
+
+ assert_eq!(result, Point3DMm::new(1.0, 2.0, 3.0));
+ }
+
+ #[test]
+ pub fn test_div_assign_scale() {
+ let mut p1 = Point3DMm::new(0.1, 0.2, 0.3);
+ let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
+
+ p1 /= scale;
+
+ assert_eq!(p1, Point3DMm::new(1.0, 2.0, 3.0));
+ }
+ }
+}
diff --git a/third_party/rust/euclid/src/rect.rs b/third_party/rust/euclid/src/rect.rs
new file mode 100644
index 0000000000..04721db811
--- /dev/null
+++ b/third_party/rust/euclid/src/rect.rs
@@ -0,0 +1,931 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::UnknownUnit;
+use crate::box2d::Box2D;
+use crate::num::*;
+use crate::point::Point2D;
+use crate::scale::Scale;
+use crate::side_offsets::SideOffsets2D;
+use crate::size::Size2D;
+use crate::vector::Vector2D;
+
+use num_traits::{NumCast, Float};
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+use core::borrow::Borrow;
+use core::cmp::PartialOrd;
+use core::fmt;
+use core::hash::{Hash, Hasher};
+use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Range, Sub};
+
+/// A 2d Rectangle optionally tagged with a unit.
+///
+/// # Representation
+///
+/// `Rect` is represented by an origin point and a size.
+///
+/// See [`Box2D`] for a rectangle represented by two endpoints.
+///
+/// # Empty rectangle
+///
+/// A rectangle is considered empty (see [`is_empty`]) if any of the following is true:
+/// - it's area is empty,
+/// - it's area is negative (`size.x < 0` or `size.y < 0`),
+/// - it contains NaNs.
+///
+/// [`is_empty`]: #method.is_empty
+/// [`Box2D`]: struct.Box2D.html
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+ feature = "serde",
+ serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
+)]
+pub struct Rect<T, U> {
+ pub origin: Point2D<T, U>,
+ pub size: Size2D<T, U>,
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a, T, U> arbitrary::Arbitrary<'a> for Rect<T, U>
+where
+ T: arbitrary::Arbitrary<'a>,
+{
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>
+ {
+ let (origin, size) = arbitrary::Arbitrary::arbitrary(u)?;
+ Ok(Rect {
+ origin,
+ size,
+ })
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for Rect<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for Rect<T, U> {}
+
+impl<T: Hash, U> Hash for Rect<T, U> {
+ fn hash<H: Hasher>(&self, h: &mut H) {
+ self.origin.hash(h);
+ self.size.hash(h);
+ }
+}
+
+impl<T: Copy, U> Copy for Rect<T, U> {}
+
+impl<T: Clone, U> Clone for Rect<T, U> {
+ fn clone(&self) -> Self {
+ Self::new(self.origin.clone(), self.size.clone())
+ }
+}
+
+impl<T: PartialEq, U> PartialEq for Rect<T, U> {
+ fn eq(&self, other: &Self) -> bool {
+ self.origin.eq(&other.origin) && self.size.eq(&other.size)
+ }
+}
+
+impl<T: Eq, U> Eq for Rect<T, U> {}
+
+impl<T: fmt::Debug, U> fmt::Debug for Rect<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Rect(")?;
+ fmt::Debug::fmt(&self.size, f)?;
+ write!(f, " at ")?;
+ fmt::Debug::fmt(&self.origin, f)?;
+ write!(f, ")")
+ }
+}
+
+impl<T: Default, U> Default for Rect<T, U> {
+ fn default() -> Self {
+ Rect::new(Default::default(), Default::default())
+ }
+}
+
+impl<T, U> Rect<T, U> {
+ /// Constructor.
+ #[inline]
+ pub const fn new(origin: Point2D<T, U>, size: Size2D<T, U>) -> Self {
+ Rect { origin, size }
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Zero,
+{
+ /// Constructor, setting all sides to zero.
+ #[inline]
+ pub fn zero() -> Self {
+ Rect::new(Point2D::origin(), Size2D::zero())
+ }
+
+ /// Creates a rect of the given size, at offset zero.
+ #[inline]
+ pub fn from_size(size: Size2D<T, U>) -> Self {
+ Rect {
+ origin: Point2D::zero(),
+ size,
+ }
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + Add<T, Output = T>,
+{
+ #[inline]
+ pub fn min(&self) -> Point2D<T, U> {
+ self.origin
+ }
+
+ #[inline]
+ pub fn max(&self) -> Point2D<T, U> {
+ self.origin + self.size
+ }
+
+ #[inline]
+ pub fn max_x(&self) -> T {
+ self.origin.x + self.size.width
+ }
+
+ #[inline]
+ pub fn min_x(&self) -> T {
+ self.origin.x
+ }
+
+ #[inline]
+ pub fn max_y(&self) -> T {
+ self.origin.y + self.size.height
+ }
+
+ #[inline]
+ pub fn min_y(&self) -> T {
+ self.origin.y
+ }
+
+ #[inline]
+ pub fn width(&self) -> T {
+ self.size.width
+ }
+
+ #[inline]
+ pub fn height(&self) -> T {
+ self.size.height
+ }
+
+ #[inline]
+ pub fn x_range(&self) -> Range<T> {
+ self.min_x()..self.max_x()
+ }
+
+ #[inline]
+ pub fn y_range(&self) -> Range<T> {
+ self.min_y()..self.max_y()
+ }
+
+ /// Returns the same rectangle, translated by a vector.
+ #[inline]
+ #[must_use]
+ pub fn translate(&self, by: Vector2D<T, U>) -> Self {
+ Self::new(self.origin + by, self.size)
+ }
+
+ #[inline]
+ pub fn to_box2d(&self) -> Box2D<T, U> {
+ Box2D {
+ min: self.min(),
+ max: self.max(),
+ }
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + PartialOrd + Add<T, Output = T>,
+{
+ /// Returns true if this rectangle contains the point. Points are considered
+ /// in the rectangle if they are on the left or top edge, but outside if they
+ /// are on the right or bottom edge.
+ #[inline]
+ pub fn contains(&self, p: Point2D<T, U>) -> bool {
+ self.to_box2d().contains(p)
+ }
+
+ #[inline]
+ pub fn intersects(&self, other: &Self) -> bool {
+ self.to_box2d().intersects(&other.to_box2d())
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + PartialOrd + Add<T, Output = T> + Sub<T, Output = T>,
+{
+ #[inline]
+ pub fn intersection(&self, other: &Self) -> Option<Self> {
+ let box2d = self.to_box2d().intersection_unchecked(&other.to_box2d());
+
+ if box2d.is_empty() {
+ return None;
+ }
+
+ Some(box2d.to_rect())
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + Add<T, Output = T> + Sub<T, Output = T>,
+{
+ #[inline]
+ #[must_use]
+ pub fn inflate(&self, width: T, height: T) -> Self {
+ Rect::new(
+ Point2D::new(self.origin.x - width, self.origin.y - height),
+ Size2D::new(
+ self.size.width + width + width,
+ self.size.height + height + height,
+ ),
+ )
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + Zero + PartialOrd + Add<T, Output = T>,
+{
+ /// Returns true if this rectangle contains the interior of rect. Always
+ /// returns true if rect is empty, and always returns false if rect is
+ /// nonempty but this rectangle is empty.
+ #[inline]
+ pub fn contains_rect(&self, rect: &Self) -> bool {
+ rect.is_empty()
+ || (self.min_x() <= rect.min_x()
+ && rect.max_x() <= self.max_x()
+ && self.min_y() <= rect.min_y()
+ && rect.max_y() <= self.max_y())
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + Zero + PartialOrd + Add<T, Output = T> + Sub<T, Output = T>,
+{
+ /// Calculate the size and position of an inner rectangle.
+ ///
+ /// Subtracts the side offsets from all sides. The horizontal and vertical
+ /// offsets must not be larger than the original side length.
+ /// This method assumes y oriented downward.
+ pub fn inner_rect(&self, offsets: SideOffsets2D<T, U>) -> Self {
+ let rect = Rect::new(
+ Point2D::new(self.origin.x + offsets.left, self.origin.y + offsets.top),
+ Size2D::new(
+ self.size.width - offsets.horizontal(),
+ self.size.height - offsets.vertical(),
+ ),
+ );
+ debug_assert!(rect.size.width >= Zero::zero());
+ debug_assert!(rect.size.height >= Zero::zero());
+ rect
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + Add<T, Output = T> + Sub<T, Output = T>,
+{
+ /// Calculate the size and position of an outer rectangle.
+ ///
+ /// Add the offsets to all sides. The expanded rectangle is returned.
+ /// This method assumes y oriented downward.
+ pub fn outer_rect(&self, offsets: SideOffsets2D<T, U>) -> Self {
+ Rect::new(
+ Point2D::new(self.origin.x - offsets.left, self.origin.y - offsets.top),
+ Size2D::new(
+ self.size.width + offsets.horizontal(),
+ self.size.height + offsets.vertical(),
+ ),
+ )
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + Zero + PartialOrd + Sub<T, Output = T>,
+{
+ /// Returns the smallest rectangle defined by the top/bottom/left/right-most
+ /// points provided as parameter.
+ ///
+ /// Note: This function has a behavior that can be surprising because
+ /// the right-most and bottom-most points are exactly on the edge
+ /// of the rectangle while the `contains` function is has exclusive
+ /// semantic on these edges. This means that the right-most and bottom-most
+ /// points provided to `from_points` will count as not contained by the rect.
+ /// This behavior may change in the future.
+ pub fn from_points<I>(points: I) -> Self
+ where
+ I: IntoIterator,
+ I::Item: Borrow<Point2D<T, U>>,
+ {
+ Box2D::from_points(points).to_rect()
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
+{
+ /// Linearly interpolate between this rectangle and another rectangle.
+ #[inline]
+ pub fn lerp(&self, other: Self, t: T) -> Self {
+ Self::new(
+ self.origin.lerp(other.origin, t),
+ self.size.lerp(other.size, t),
+ )
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + One + Add<Output = T> + Div<Output = T>,
+{
+ pub fn center(&self) -> Point2D<T, U> {
+ let two = T::one() + T::one();
+ self.origin + self.size.to_vector() / two
+ }
+}
+
+impl<T, U> Rect<T, U>
+where
+ T: Copy + PartialOrd + Add<T, Output = T> + Sub<T, Output = T> + Zero,
+{
+ #[inline]
+ pub fn union(&self, other: &Self) -> Self {
+ self.to_box2d().union(&other.to_box2d()).to_rect()
+ }
+}
+
+impl<T, U> Rect<T, U> {
+ #[inline]
+ pub fn scale<S: Copy>(&self, x: S, y: S) -> Self
+ where
+ T: Copy + Mul<S, Output = T>,
+ {
+ Rect::new(
+ Point2D::new(self.origin.x * x, self.origin.y * y),
+ Size2D::new(self.size.width * x, self.size.height * y),
+ )
+ }
+}
+
+impl<T: Copy + Mul<T, Output = T>, U> Rect<T, U> {
+ #[inline]
+ pub fn area(&self) -> T {
+ self.size.area()
+ }
+}
+
+impl<T: Copy + Zero + PartialOrd, U> Rect<T, U> {
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.size.is_empty()
+ }
+}
+
+impl<T: Copy + Zero + PartialOrd, U> Rect<T, U> {
+ #[inline]
+ pub fn to_non_empty(&self) -> Option<Self> {
+ if self.is_empty() {
+ return None;
+ }
+
+ Some(*self)
+ }
+}
+
+impl<T: Copy + Mul, U> Mul<T> for Rect<T, U> {
+ type Output = Rect<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ Rect::new(self.origin * scale, self.size * scale)
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<T> for Rect<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: T) {
+ *self *= Scale::new(scale);
+ }
+}
+
+impl<T: Copy + Div, U> Div<T> for Rect<T, U> {
+ type Output = Rect<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ Rect::new(self.origin / scale.clone(), self.size / scale)
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<T> for Rect<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: T) {
+ *self /= Scale::new(scale);
+ }
+}
+
+impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Rect<T, U1> {
+ type Output = Rect<T::Output, U2>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ Rect::new(self.origin * scale.clone(), self.size * scale)
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Rect<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: Scale<T, U, U>) {
+ self.origin *= scale.clone();
+ self.size *= scale;
+ }
+}
+
+impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Rect<T, U2> {
+ type Output = Rect<T::Output, U1>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ Rect::new(self.origin / scale.clone(), self.size / scale)
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Rect<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: Scale<T, U, U>) {
+ self.origin /= scale.clone();
+ self.size /= scale;
+ }
+}
+
+impl<T: Copy, U> Rect<T, U> {
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(&self) -> Rect<T, UnknownUnit> {
+ Rect::new(self.origin.to_untyped(), self.size.to_untyped())
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(r: &Rect<T, UnknownUnit>) -> Rect<T, U> {
+ Rect::new(
+ Point2D::from_untyped(r.origin),
+ Size2D::from_untyped(r.size),
+ )
+ }
+
+ /// Cast the unit
+ #[inline]
+ pub fn cast_unit<V>(&self) -> Rect<T, V> {
+ Rect::new(self.origin.cast_unit(), self.size.cast_unit())
+ }
+}
+
+impl<T: NumCast + Copy, U> Rect<T, U> {
+ /// Cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using round(), round_in or round_out() before casting.
+ #[inline]
+ pub fn cast<NewT: NumCast>(&self) -> Rect<NewT, U> {
+ Rect::new(self.origin.cast(), self.size.cast())
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using round(), round_in or round_out() before casting.
+ pub fn try_cast<NewT: NumCast>(&self) -> Option<Rect<NewT, U>> {
+ match (self.origin.try_cast(), self.size.try_cast()) {
+ (Some(origin), Some(size)) => Some(Rect::new(origin, size)),
+ _ => None,
+ }
+ }
+
+ // Convenience functions for common casts
+
+ /// Cast into an `f32` rectangle.
+ #[inline]
+ pub fn to_f32(&self) -> Rect<f32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `f64` rectangle.
+ #[inline]
+ pub fn to_f64(&self) -> Rect<f64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `usize` rectangle, truncating decimals if any.
+ ///
+ /// When casting from floating point rectangles, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_usize(&self) -> Rect<usize, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u32` rectangle, truncating decimals if any.
+ ///
+ /// When casting from floating point rectangles, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_u32(&self) -> Rect<u32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u64` rectangle, truncating decimals if any.
+ ///
+ /// When casting from floating point rectangles, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_u64(&self) -> Rect<u64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i32` rectangle, truncating decimals if any.
+ ///
+ /// When casting from floating point rectangles, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_i32(&self) -> Rect<i32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i64` rectangle, truncating decimals if any.
+ ///
+ /// When casting from floating point rectangles, it is worth considering whether
+ /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+ /// obtain the desired conversion behavior.
+ #[inline]
+ pub fn to_i64(&self) -> Rect<i64, U> {
+ self.cast()
+ }
+}
+
+impl<T: Float, U> Rect<T, U> {
+ /// Returns true if all members are finite.
+ #[inline]
+ pub fn is_finite(self) -> bool {
+ self.origin.is_finite() && self.size.is_finite()
+ }
+}
+
+impl<T: Floor + Ceil + Round + Add<T, Output = T> + Sub<T, Output = T>, U> Rect<T, U> {
+ /// Return a rectangle with edges rounded to integer coordinates, such that
+ /// the returned rectangle has the same set of pixel centers as the original
+ /// one.
+ /// Edges at offset 0.5 round up.
+ /// Suitable for most places where integral device coordinates
+ /// are needed, but note that any translation should be applied first to
+ /// avoid pixel rounding errors.
+ /// Note that this is *not* rounding to nearest integer if the values are negative.
+ /// They are always rounding as floor(n + 0.5).
+ ///
+ /// # Usage notes
+ /// Note, that when using with floating-point `T` types that method can significantly
+ /// loose precision for large values, so if you need to call this method very often it
+ /// is better to use [`Box2D`].
+ ///
+ /// [`Box2D`]: struct.Box2D.html
+ #[must_use]
+ pub fn round(&self) -> Self {
+ self.to_box2d().round().to_rect()
+ }
+
+ /// Return a rectangle with edges rounded to integer coordinates, such that
+ /// the original rectangle contains the resulting rectangle.
+ ///
+ /// # Usage notes
+ /// Note, that when using with floating-point `T` types that method can significantly
+ /// loose precision for large values, so if you need to call this method very often it
+ /// is better to use [`Box2D`].
+ ///
+ /// [`Box2D`]: struct.Box2D.html
+ #[must_use]
+ pub fn round_in(&self) -> Self {
+ self.to_box2d().round_in().to_rect()
+ }
+
+ /// Return a rectangle with edges rounded to integer coordinates, such that
+ /// the original rectangle is contained in the resulting rectangle.
+ ///
+ /// # Usage notes
+ /// Note, that when using with floating-point `T` types that method can significantly
+ /// loose precision for large values, so if you need to call this method very often it
+ /// is better to use [`Box2D`].
+ ///
+ /// [`Box2D`]: struct.Box2D.html
+ #[must_use]
+ pub fn round_out(&self) -> Self {
+ self.to_box2d().round_out().to_rect()
+ }
+}
+
+impl<T, U> From<Size2D<T, U>> for Rect<T, U>
+where
+ T: Zero,
+{
+ fn from(size: Size2D<T, U>) -> Self {
+ Self::from_size(size)
+ }
+}
+
+/// Shorthand for `Rect::new(Point2D::new(x, y), Size2D::new(w, h))`.
+pub const fn rect<T, U>(x: T, y: T, w: T, h: T) -> Rect<T, U> {
+ Rect::new(Point2D::new(x, y), Size2D::new(w, h))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::default::{Point2D, Rect, Size2D};
+ use crate::side_offsets::SideOffsets2D;
+ use crate::{point2, rect, size2, vec2};
+
+ #[test]
+ fn test_translate() {
+ let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
+ let pp = p.translate(vec2(10, 15));
+
+ assert!(pp.size.width == 50);
+ assert!(pp.size.height == 40);
+ assert!(pp.origin.x == 10);
+ assert!(pp.origin.y == 15);
+
+ let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
+ let rr = r.translate(vec2(0, -10));
+
+ assert!(rr.size.width == 50);
+ assert!(rr.size.height == 40);
+ assert!(rr.origin.x == -10);
+ assert!(rr.origin.y == -15);
+ }
+
+ #[test]
+ fn test_union() {
+ let p = Rect::new(Point2D::new(0, 0), Size2D::new(50, 40));
+ let q = Rect::new(Point2D::new(20, 20), Size2D::new(5, 5));
+ let r = Rect::new(Point2D::new(-15, -30), Size2D::new(200, 15));
+ let s = Rect::new(Point2D::new(20, -15), Size2D::new(250, 200));
+
+ let pq = p.union(&q);
+ assert!(pq.origin == Point2D::new(0, 0));
+ assert!(pq.size == Size2D::new(50, 40));
+
+ let pr = p.union(&r);
+ assert!(pr.origin == Point2D::new(-15, -30));
+ assert!(pr.size == Size2D::new(200, 70));
+
+ let ps = p.union(&s);
+ assert!(ps.origin == Point2D::new(0, -15));
+ assert!(ps.size == Size2D::new(270, 200));
+ }
+
+ #[test]
+ fn test_intersection() {
+ let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20));
+ let q = Rect::new(Point2D::new(5, 15), Size2D::new(10, 10));
+ let r = Rect::new(Point2D::new(-5, -5), Size2D::new(8, 8));
+
+ let pq = p.intersection(&q);
+ assert!(pq.is_some());
+ let pq = pq.unwrap();
+ assert!(pq.origin == Point2D::new(5, 15));
+ assert!(pq.size == Size2D::new(5, 5));
+
+ let pr = p.intersection(&r);
+ assert!(pr.is_some());
+ let pr = pr.unwrap();
+ assert!(pr.origin == Point2D::new(0, 0));
+ assert!(pr.size == Size2D::new(3, 3));
+
+ let qr = q.intersection(&r);
+ assert!(qr.is_none());
+ }
+
+ #[test]
+ fn test_intersection_overflow() {
+ // test some scenarios where the intersection can overflow but
+ // the min_x() and max_x() don't. Gecko currently fails these cases
+ let p = Rect::new(Point2D::new(-2147483648, -2147483648), Size2D::new(0, 0));
+ let q = Rect::new(
+ Point2D::new(2136893440, 2136893440),
+ Size2D::new(279552, 279552),
+ );
+ let r = Rect::new(Point2D::new(-2147483648, -2147483648), Size2D::new(1, 1));
+
+ assert!(p.is_empty());
+ let pq = p.intersection(&q);
+ assert!(pq.is_none());
+
+ let qr = q.intersection(&r);
+ assert!(qr.is_none());
+ }
+
+ #[test]
+ fn test_contains() {
+ let r = Rect::new(Point2D::new(-20, 15), Size2D::new(100, 200));
+
+ assert!(r.contains(Point2D::new(0, 50)));
+ assert!(r.contains(Point2D::new(-10, 200)));
+
+ // The `contains` method is inclusive of the top/left edges, but not the
+ // bottom/right edges.
+ assert!(r.contains(Point2D::new(-20, 15)));
+ assert!(!r.contains(Point2D::new(80, 15)));
+ assert!(!r.contains(Point2D::new(80, 215)));
+ assert!(!r.contains(Point2D::new(-20, 215)));
+
+ // Points beyond the top-left corner.
+ assert!(!r.contains(Point2D::new(-25, 15)));
+ assert!(!r.contains(Point2D::new(-15, 10)));
+
+ // Points beyond the top-right corner.
+ assert!(!r.contains(Point2D::new(85, 20)));
+ assert!(!r.contains(Point2D::new(75, 10)));
+
+ // Points beyond the bottom-right corner.
+ assert!(!r.contains(Point2D::new(85, 210)));
+ assert!(!r.contains(Point2D::new(75, 220)));
+
+ // Points beyond the bottom-left corner.
+ assert!(!r.contains(Point2D::new(-25, 210)));
+ assert!(!r.contains(Point2D::new(-15, 220)));
+
+ let r = Rect::new(Point2D::new(-20.0, 15.0), Size2D::new(100.0, 200.0));
+ assert!(r.contains_rect(&r));
+ assert!(!r.contains_rect(&r.translate(vec2(0.1, 0.0))));
+ assert!(!r.contains_rect(&r.translate(vec2(-0.1, 0.0))));
+ assert!(!r.contains_rect(&r.translate(vec2(0.0, 0.1))));
+ assert!(!r.contains_rect(&r.translate(vec2(0.0, -0.1))));
+ // Empty rectangles are always considered as contained in other rectangles,
+ // even if their origin is not.
+ let p = Point2D::new(1.0, 1.0);
+ assert!(!r.contains(p));
+ assert!(r.contains_rect(&Rect::new(p, Size2D::zero())));
+ }
+
+ #[test]
+ fn test_scale() {
+ let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
+ let pp = p.scale(10, 15);
+
+ assert!(pp.size.width == 500);
+ assert!(pp.size.height == 600);
+ assert!(pp.origin.x == 0);
+ assert!(pp.origin.y == 0);
+
+ let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
+ let rr = r.scale(1, 20);
+
+ assert!(rr.size.width == 50);
+ assert!(rr.size.height == 800);
+ assert!(rr.origin.x == -10);
+ assert!(rr.origin.y == -100);
+ }
+
+ #[test]
+ fn test_inflate() {
+ let p = Rect::new(Point2D::new(0, 0), Size2D::new(10, 10));
+ let pp = p.inflate(10, 20);
+
+ assert!(pp.size.width == 30);
+ assert!(pp.size.height == 50);
+ assert!(pp.origin.x == -10);
+ assert!(pp.origin.y == -20);
+
+ let r = Rect::new(Point2D::new(0, 0), Size2D::new(10, 20));
+ let rr = r.inflate(-2, -5);
+
+ assert!(rr.size.width == 6);
+ assert!(rr.size.height == 10);
+ assert!(rr.origin.x == 2);
+ assert!(rr.origin.y == 5);
+ }
+
+ #[test]
+ fn test_inner_outer_rect() {
+ let inner_rect = Rect::new(point2(20, 40), size2(80, 100));
+ let offsets = SideOffsets2D::new(20, 10, 10, 10);
+ let outer_rect = inner_rect.outer_rect(offsets);
+ assert_eq!(outer_rect.origin.x, 10);
+ assert_eq!(outer_rect.origin.y, 20);
+ assert_eq!(outer_rect.size.width, 100);
+ assert_eq!(outer_rect.size.height, 130);
+ assert_eq!(outer_rect.inner_rect(offsets), inner_rect);
+ }
+
+ #[test]
+ fn test_min_max_x_y() {
+ let p = Rect::new(Point2D::new(0u32, 0u32), Size2D::new(50u32, 40u32));
+ assert!(p.max_y() == 40);
+ assert!(p.min_y() == 0);
+ assert!(p.max_x() == 50);
+ assert!(p.min_x() == 0);
+
+ let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
+ assert!(r.max_y() == 35);
+ assert!(r.min_y() == -5);
+ assert!(r.max_x() == 40);
+ assert!(r.min_x() == -10);
+ }
+
+ #[test]
+ fn test_width_height() {
+ let r = Rect::new(Point2D::new(-10, -5), Size2D::new(50, 40));
+ assert!(r.width() == 50);
+ assert!(r.height() == 40);
+ }
+
+ #[test]
+ fn test_is_empty() {
+ assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 0u32)).is_empty());
+ assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(10u32, 0u32)).is_empty());
+ assert!(Rect::new(Point2D::new(0u32, 0u32), Size2D::new(0u32, 10u32)).is_empty());
+ assert!(!Rect::new(Point2D::new(0u32, 0u32), Size2D::new(1u32, 1u32)).is_empty());
+ assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 0u32)).is_empty());
+ assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(10u32, 0u32)).is_empty());
+ assert!(Rect::new(Point2D::new(10u32, 10u32), Size2D::new(0u32, 10u32)).is_empty());
+ assert!(!Rect::new(Point2D::new(10u32, 10u32), Size2D::new(1u32, 1u32)).is_empty());
+ }
+
+ #[test]
+ fn test_round() {
+ let mut x = -2.0;
+ let mut y = -2.0;
+ let mut w = -2.0;
+ let mut h = -2.0;
+ while x < 2.0 {
+ while y < 2.0 {
+ while w < 2.0 {
+ while h < 2.0 {
+ let rect = Rect::new(Point2D::new(x, y), Size2D::new(w, h));
+
+ assert!(rect.contains_rect(&rect.round_in()));
+ assert!(rect.round_in().inflate(1.0, 1.0).contains_rect(&rect));
+
+ assert!(rect.round_out().contains_rect(&rect));
+ assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round_out()));
+
+ assert!(rect.inflate(1.0, 1.0).contains_rect(&rect.round()));
+ assert!(rect.round().inflate(1.0, 1.0).contains_rect(&rect));
+
+ h += 0.1;
+ }
+ w += 0.1;
+ }
+ y += 0.1;
+ }
+ x += 0.1
+ }
+ }
+
+ #[test]
+ fn test_center() {
+ let r: Rect<i32> = rect(-2, 5, 4, 10);
+ assert_eq!(r.center(), point2(0, 10));
+
+ let r: Rect<f32> = rect(1.0, 2.0, 3.0, 4.0);
+ assert_eq!(r.center(), point2(2.5, 4.0));
+ }
+
+ #[test]
+ fn test_nan() {
+ let r1: Rect<f32> = rect(-2.0, 5.0, 4.0, std::f32::NAN);
+ let r2: Rect<f32> = rect(std::f32::NAN, -1.0, 3.0, 10.0);
+
+ assert_eq!(r1.intersection(&r2), None);
+ }
+}
diff --git a/third_party/rust/euclid/src/rigid.rs b/third_party/rust/euclid/src/rigid.rs
new file mode 100644
index 0000000000..16e1b86e37
--- /dev/null
+++ b/third_party/rust/euclid/src/rigid.rs
@@ -0,0 +1,286 @@
+//! All matrix multiplication in this module is in row-vector notation,
+//! i.e. a vector `v` is transformed with `v * T`, and if you want to apply `T1`
+//! before `T2` you use `T1 * T2`
+
+use crate::approxeq::ApproxEq;
+use crate::trig::Trig;
+use crate::{Rotation3D, Transform3D, UnknownUnit, Vector3D};
+use num_traits::real::Real;
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A rigid transformation. All lengths are preserved under such a transformation.
+///
+///
+/// Internally, this is a rotation and a translation, with the rotation
+/// applied first (i.e. `Rotation * Translation`, in row-vector notation)
+///
+/// This can be more efficient to use over full matrices, especially if you
+/// have to deal with the decomposed quantities often.
+#[derive(Debug, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[repr(C)]
+pub struct RigidTransform3D<T, Src, Dst> {
+ pub rotation: Rotation3D<T, Src, Dst>,
+ pub translation: Vector3D<T, Dst>,
+}
+
+impl<T, Src, Dst> RigidTransform3D<T, Src, Dst> {
+ /// Construct a new rigid transformation, where the `rotation` applies first
+ #[inline]
+ pub const fn new(rotation: Rotation3D<T, Src, Dst>, translation: Vector3D<T, Dst>) -> Self {
+ Self {
+ rotation,
+ translation,
+ }
+ }
+}
+
+impl<T: Copy, Src, Dst> RigidTransform3D<T, Src, Dst> {
+ pub fn cast_unit<Src2, Dst2>(&self) -> RigidTransform3D<T, Src2, Dst2> {
+ RigidTransform3D {
+ rotation: self.rotation.cast_unit(),
+ translation: self.translation.cast_unit(),
+ }
+ }
+}
+
+impl<T: Real + ApproxEq<T>, Src, Dst> RigidTransform3D<T, Src, Dst> {
+ /// Construct an identity transform
+ #[inline]
+ pub fn identity() -> Self {
+ Self {
+ rotation: Rotation3D::identity(),
+ translation: Vector3D::zero(),
+ }
+ }
+
+ /// Construct a new rigid transformation, where the `translation` applies first
+ #[inline]
+ pub fn new_from_reversed(
+ translation: Vector3D<T, Src>,
+ rotation: Rotation3D<T, Src, Dst>,
+ ) -> Self {
+ // T * R
+ // = (R * R^-1) * T * R
+ // = R * (R^-1 * T * R)
+ // = R * T'
+ //
+ // T' = (R^-1 * T * R) is also a translation matrix
+ // It is equivalent to the translation matrix obtained by rotating the
+ // translation by R
+
+ let translation = rotation.transform_vector3d(translation);
+ Self {
+ rotation,
+ translation,
+ }
+ }
+
+ #[inline]
+ pub fn from_rotation(rotation: Rotation3D<T, Src, Dst>) -> Self {
+ Self {
+ rotation,
+ translation: Vector3D::zero(),
+ }
+ }
+
+ #[inline]
+ pub fn from_translation(translation: Vector3D<T, Dst>) -> Self {
+ Self {
+ translation,
+ rotation: Rotation3D::identity(),
+ }
+ }
+
+ /// Decompose this into a translation and an rotation to be applied in the opposite order
+ ///
+ /// i.e., the translation is applied _first_
+ #[inline]
+ pub fn decompose_reversed(&self) -> (Vector3D<T, Src>, Rotation3D<T, Src, Dst>) {
+ // self = R * T
+ // = R * T * (R^-1 * R)
+ // = (R * T * R^-1) * R)
+ // = T' * R
+ //
+ // T' = (R^ * T * R^-1) is T rotated by R^-1
+
+ let translation = self.rotation.inverse().transform_vector3d(self.translation);
+ (translation, self.rotation)
+ }
+
+ /// Returns the multiplication of the two transforms such that
+ /// other's transformation applies after self's transformation.
+ ///
+ /// i.e., this produces `self * other` in row-vector notation
+ #[inline]
+ pub fn then<Dst2>(
+ &self,
+ other: &RigidTransform3D<T, Dst, Dst2>,
+ ) -> RigidTransform3D<T, Src, Dst2> {
+ // self = R1 * T1
+ // other = R2 * T2
+ // result = R1 * T1 * R2 * T2
+ // = R1 * (R2 * R2^-1) * T1 * R2 * T2
+ // = (R1 * R2) * (R2^-1 * T1 * R2) * T2
+ // = R' * T' * T2
+ // = R' * T''
+ //
+ // (R2^-1 * T2 * R2^) = T' = T2 rotated by R2
+ // R1 * R2 = R'
+ // T' * T2 = T'' = vector addition of translations T2 and T'
+
+ let t_prime = other.rotation.transform_vector3d(self.translation);
+ let r_prime = self.rotation.then(&other.rotation);
+ let t_prime2 = t_prime + other.translation;
+ RigidTransform3D {
+ rotation: r_prime,
+ translation: t_prime2,
+ }
+ }
+
+ /// Inverts the transformation
+ #[inline]
+ pub fn inverse(&self) -> RigidTransform3D<T, Dst, Src> {
+ // result = (self)^-1
+ // = (R * T)^-1
+ // = T^-1 * R^-1
+ // = (R^-1 * R) * T^-1 * R^-1
+ // = R^-1 * (R * T^-1 * R^-1)
+ // = R' * T'
+ //
+ // T' = (R * T^-1 * R^-1) = (-T) rotated by R^-1
+ // R' = R^-1
+ //
+ // An easier way of writing this is to use new_from_reversed() with R^-1 and T^-1
+
+ RigidTransform3D::new_from_reversed(-self.translation, self.rotation.inverse())
+ }
+
+ pub fn to_transform(&self) -> Transform3D<T, Src, Dst>
+ where
+ T: Trig,
+ {
+ self.rotation.to_transform().then(&self.translation.to_transform())
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(&self) -> RigidTransform3D<T, UnknownUnit, UnknownUnit> {
+ RigidTransform3D {
+ rotation: self.rotation.to_untyped(),
+ translation: self.translation.to_untyped(),
+ }
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(transform: &RigidTransform3D<T, UnknownUnit, UnknownUnit>) -> Self {
+ RigidTransform3D {
+ rotation: Rotation3D::from_untyped(&transform.rotation),
+ translation: Vector3D::from_untyped(transform.translation),
+ }
+ }
+}
+
+impl<T: Copy, Src, Dst> Copy for RigidTransform3D<T, Src, Dst> {}
+
+impl<T: Clone, Src, Dst> Clone for RigidTransform3D<T, Src, Dst> {
+ fn clone(&self) -> Self {
+ RigidTransform3D {
+ rotation: self.rotation.clone(),
+ translation: self.translation.clone(),
+ }
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, Src, Dst> Zeroable for RigidTransform3D<T, Src, Dst> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for RigidTransform3D<T, Src, Dst> {}
+
+impl<T: Real + ApproxEq<T>, Src, Dst> From<Rotation3D<T, Src, Dst>>
+ for RigidTransform3D<T, Src, Dst>
+{
+ fn from(rot: Rotation3D<T, Src, Dst>) -> Self {
+ Self::from_rotation(rot)
+ }
+}
+
+impl<T: Real + ApproxEq<T>, Src, Dst> From<Vector3D<T, Dst>> for RigidTransform3D<T, Src, Dst> {
+ fn from(t: Vector3D<T, Dst>) -> Self {
+ Self::from_translation(t)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::RigidTransform3D;
+ use crate::default::{Rotation3D, Transform3D, Vector3D};
+
+ #[test]
+ fn test_rigid_construction() {
+ let translation = Vector3D::new(12.1, 17.8, -5.5);
+ let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
+
+ let rigid = RigidTransform3D::new(rotation, translation);
+ assert!(rigid.to_transform().approx_eq(
+ &rotation.to_transform().then(&translation.to_transform())
+ ));
+
+ let rigid = RigidTransform3D::new_from_reversed(translation, rotation);
+ assert!(rigid.to_transform().approx_eq(
+ &translation.to_transform().then(&rotation.to_transform())
+ ));
+ }
+
+ #[test]
+ fn test_rigid_decomposition() {
+ let translation = Vector3D::new(12.1, 17.8, -5.5);
+ let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
+
+ let rigid = RigidTransform3D::new(rotation, translation);
+ let (t2, r2) = rigid.decompose_reversed();
+ assert!(rigid
+ .to_transform()
+ .approx_eq(&t2.to_transform().then(&r2.to_transform())));
+ }
+
+ #[test]
+ fn test_rigid_inverse() {
+ let translation = Vector3D::new(12.1, 17.8, -5.5);
+ let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
+
+ let rigid = RigidTransform3D::new(rotation, translation);
+ let inverse = rigid.inverse();
+ assert!(rigid
+ .then(&inverse)
+ .to_transform()
+ .approx_eq(&Transform3D::identity()));
+ assert!(inverse
+ .to_transform()
+ .approx_eq(&rigid.to_transform().inverse().unwrap()));
+ }
+
+ #[test]
+ fn test_rigid_multiply() {
+ let translation = Vector3D::new(12.1, 17.8, -5.5);
+ let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
+ let translation2 = Vector3D::new(9.3, -3.9, 1.1);
+ let rotation2 = Rotation3D::unit_quaternion(0.1, 0.2, 0.3, -0.4);
+ let rigid = RigidTransform3D::new(rotation, translation);
+ let rigid2 = RigidTransform3D::new(rotation2, translation2);
+
+ assert!(rigid
+ .then(&rigid2)
+ .to_transform()
+ .approx_eq(&rigid.to_transform().then(&rigid2.to_transform())));
+ assert!(rigid2
+ .then(&rigid)
+ .to_transform()
+ .approx_eq(&rigid2.to_transform().then(&rigid.to_transform())));
+ }
+}
diff --git a/third_party/rust/euclid/src/rotation.rs b/third_party/rust/euclid/src/rotation.rs
new file mode 100644
index 0000000000..474984c007
--- /dev/null
+++ b/third_party/rust/euclid/src/rotation.rs
@@ -0,0 +1,1014 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::approxeq::ApproxEq;
+use crate::trig::Trig;
+use crate::{point2, point3, vec3, Angle, Point2D, Point3D, Vector2D, Vector3D};
+use crate::{Transform2D, Transform3D, UnknownUnit};
+use core::cmp::{Eq, PartialEq};
+use core::fmt;
+use core::hash::Hash;
+use core::marker::PhantomData;
+use core::ops::{Add, Mul, Neg, Sub};
+use num_traits::real::Real;
+use num_traits::{NumCast, One, Zero};
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A transform that can represent rotations in 2d, represented as an angle in radians.
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+ feature = "serde",
+ serde(bound(
+ serialize = "T: serde::Serialize",
+ deserialize = "T: serde::Deserialize<'de>"
+ ))
+)]
+pub struct Rotation2D<T, Src, Dst> {
+ /// Angle in radians
+ pub angle: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<(Src, Dst)>,
+}
+
+impl<T: Copy, Src, Dst> Copy for Rotation2D<T, Src, Dst> {}
+
+impl<T: Clone, Src, Dst> Clone for Rotation2D<T, Src, Dst> {
+ fn clone(&self) -> Self {
+ Rotation2D {
+ angle: self.angle.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+impl<T, Src, Dst> Eq for Rotation2D<T, Src, Dst> where T: Eq {}
+
+impl<T, Src, Dst> PartialEq for Rotation2D<T, Src, Dst>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.angle == other.angle
+ }
+}
+
+impl<T, Src, Dst> Hash for Rotation2D<T, Src, Dst>
+where
+ T: Hash,
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.angle.hash(h);
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, Src, Dst> Zeroable for Rotation2D<T, Src, Dst> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Rotation2D<T, Src, Dst> {}
+
+impl<T, Src, Dst> Rotation2D<T, Src, Dst> {
+ /// Creates a rotation from an angle in radians.
+ #[inline]
+ pub fn new(angle: Angle<T>) -> Self {
+ Rotation2D {
+ angle: angle.radians,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Creates a rotation from an angle in radians.
+ pub fn radians(angle: T) -> Self {
+ Self::new(Angle::radians(angle))
+ }
+
+ /// Creates the identity rotation.
+ #[inline]
+ pub fn identity() -> Self
+ where
+ T: Zero,
+ {
+ Self::radians(T::zero())
+ }
+}
+
+impl<T: Copy, Src, Dst> Rotation2D<T, Src, Dst> {
+ /// Cast the unit, preserving the numeric value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::Rotation2D;
+ /// enum Local {}
+ /// enum World {}
+ ///
+ /// enum Local2 {}
+ /// enum World2 {}
+ ///
+ /// let to_world: Rotation2D<_, Local, World> = Rotation2D::radians(42);
+ ///
+ /// assert_eq!(to_world.angle, to_world.cast_unit::<Local2, World2>().angle);
+ /// ```
+ #[inline]
+ pub fn cast_unit<Src2, Dst2>(&self) -> Rotation2D<T, Src2, Dst2> {
+ Rotation2D {
+ angle: self.angle,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::Rotation2D;
+ /// enum Local {}
+ /// enum World {}
+ ///
+ /// let to_world: Rotation2D<_, Local, World> = Rotation2D::radians(42);
+ ///
+ /// assert_eq!(to_world.angle, to_world.to_untyped().angle);
+ /// ```
+ #[inline]
+ pub fn to_untyped(&self) -> Rotation2D<T, UnknownUnit, UnknownUnit> {
+ self.cast_unit()
+ }
+
+ /// Tag a unitless value with units.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::Rotation2D;
+ /// use euclid::UnknownUnit;
+ /// enum Local {}
+ /// enum World {}
+ ///
+ /// let rot: Rotation2D<_, UnknownUnit, UnknownUnit> = Rotation2D::radians(42);
+ ///
+ /// assert_eq!(rot.angle, Rotation2D::<_, Local, World>::from_untyped(&rot).angle);
+ /// ```
+ #[inline]
+ pub fn from_untyped(r: &Rotation2D<T, UnknownUnit, UnknownUnit>) -> Self {
+ r.cast_unit()
+ }
+}
+
+impl<T, Src, Dst> Rotation2D<T, Src, Dst>
+where
+ T: Copy,
+{
+ /// Returns self.angle as a strongly typed `Angle<T>`.
+ pub fn get_angle(&self) -> Angle<T> {
+ Angle::radians(self.angle)
+ }
+}
+
+impl<T: Real, Src, Dst> Rotation2D<T, Src, Dst> {
+ /// Creates a 3d rotation (around the z axis) from this 2d rotation.
+ #[inline]
+ pub fn to_3d(&self) -> Rotation3D<T, Src, Dst> {
+ Rotation3D::around_z(self.get_angle())
+ }
+
+ /// Returns the inverse of this rotation.
+ #[inline]
+ pub fn inverse(&self) -> Rotation2D<T, Dst, Src> {
+ Rotation2D::radians(-self.angle)
+ }
+
+ /// Returns a rotation representing the other rotation followed by this rotation.
+ #[inline]
+ pub fn then<NewSrc>(
+ &self,
+ other: &Rotation2D<T, NewSrc, Src>,
+ ) -> Rotation2D<T, NewSrc, Dst> {
+ Rotation2D::radians(self.angle + other.angle)
+ }
+
+ /// Returns the given 2d point transformed by this rotation.
+ ///
+ /// The input point must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_point(&self, point: Point2D<T, Src>) -> Point2D<T, Dst> {
+ let (sin, cos) = Real::sin_cos(self.angle);
+ point2(point.x * cos - point.y * sin, point.y * cos + point.x * sin)
+ }
+
+ /// Returns the given 2d vector transformed by this rotation.
+ ///
+ /// The input point must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_vector(&self, vector: Vector2D<T, Src>) -> Vector2D<T, Dst> {
+ self.transform_point(vector.to_point()).to_vector()
+ }
+}
+
+impl<T, Src, Dst> Rotation2D<T, Src, Dst>
+where
+ T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Zero + Trig,
+{
+ /// Returns the matrix representation of this rotation.
+ #[inline]
+ pub fn to_transform(&self) -> Transform2D<T, Src, Dst> {
+ Transform2D::rotation(self.get_angle())
+ }
+}
+
+/// A transform that can represent rotations in 3d, represented as a quaternion.
+///
+/// Most methods expect the quaternion to be normalized.
+/// When in doubt, use `unit_quaternion` instead of `quaternion` to create
+/// a rotation as the former will ensure that its result is normalized.
+///
+/// Some people use the `x, y, z, w` (or `w, x, y, z`) notations. The equivalence is
+/// as follows: `x -> i`, `y -> j`, `z -> k`, `w -> r`.
+/// The memory layout of this type corresponds to the `x, y, z, w` notation
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+ feature = "serde",
+ serde(bound(
+ serialize = "T: serde::Serialize",
+ deserialize = "T: serde::Deserialize<'de>"
+ ))
+)]
+pub struct Rotation3D<T, Src, Dst> {
+ /// Component multiplied by the imaginary number `i`.
+ pub i: T,
+ /// Component multiplied by the imaginary number `j`.
+ pub j: T,
+ /// Component multiplied by the imaginary number `k`.
+ pub k: T,
+ /// The real part.
+ pub r: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<(Src, Dst)>,
+}
+
+impl<T: Copy, Src, Dst> Copy for Rotation3D<T, Src, Dst> {}
+
+impl<T: Clone, Src, Dst> Clone for Rotation3D<T, Src, Dst> {
+ fn clone(&self) -> Self {
+ Rotation3D {
+ i: self.i.clone(),
+ j: self.j.clone(),
+ k: self.k.clone(),
+ r: self.r.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+impl<T, Src, Dst> Eq for Rotation3D<T, Src, Dst> where T: Eq {}
+
+impl<T, Src, Dst> PartialEq for Rotation3D<T, Src, Dst>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.i == other.i && self.j == other.j && self.k == other.k && self.r == other.r
+ }
+}
+
+impl<T, Src, Dst> Hash for Rotation3D<T, Src, Dst>
+where
+ T: Hash,
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.i.hash(h);
+ self.j.hash(h);
+ self.k.hash(h);
+ self.r.hash(h);
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, Src, Dst> Zeroable for Rotation3D<T, Src, Dst> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Rotation3D<T, Src, Dst> {}
+
+impl<T, Src, Dst> Rotation3D<T, Src, Dst> {
+ /// Creates a rotation around from a quaternion representation.
+ ///
+ /// The parameters are a, b, c and r compose the quaternion `a*i + b*j + c*k + r`
+ /// where `a`, `b` and `c` describe the vector part and the last parameter `r` is
+ /// the real part.
+ ///
+ /// The resulting quaternion is not necessarily normalized. See [`unit_quaternion`].
+ ///
+ /// [`unit_quaternion`]: #method.unit_quaternion
+ #[inline]
+ pub fn quaternion(a: T, b: T, c: T, r: T) -> Self {
+ Rotation3D {
+ i: a,
+ j: b,
+ k: c,
+ r,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Creates the identity rotation.
+ #[inline]
+ pub fn identity() -> Self
+ where
+ T: Zero + One,
+ {
+ Self::quaternion(T::zero(), T::zero(), T::zero(), T::one())
+ }
+}
+
+impl<T, Src, Dst> Rotation3D<T, Src, Dst>
+where
+ T: Copy,
+{
+ /// Returns the vector part (i, j, k) of this quaternion.
+ #[inline]
+ pub fn vector_part(&self) -> Vector3D<T, UnknownUnit> {
+ vec3(self.i, self.j, self.k)
+ }
+
+ /// Cast the unit, preserving the numeric value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::Rotation3D;
+ /// enum Local {}
+ /// enum World {}
+ ///
+ /// enum Local2 {}
+ /// enum World2 {}
+ ///
+ /// let to_world: Rotation3D<_, Local, World> = Rotation3D::quaternion(1, 2, 3, 4);
+ ///
+ /// assert_eq!(to_world.i, to_world.cast_unit::<Local2, World2>().i);
+ /// assert_eq!(to_world.j, to_world.cast_unit::<Local2, World2>().j);
+ /// assert_eq!(to_world.k, to_world.cast_unit::<Local2, World2>().k);
+ /// assert_eq!(to_world.r, to_world.cast_unit::<Local2, World2>().r);
+ /// ```
+ #[inline]
+ pub fn cast_unit<Src2, Dst2>(&self) -> Rotation3D<T, Src2, Dst2> {
+ Rotation3D {
+ i: self.i,
+ j: self.j,
+ k: self.k,
+ r: self.r,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::Rotation3D;
+ /// enum Local {}
+ /// enum World {}
+ ///
+ /// let to_world: Rotation3D<_, Local, World> = Rotation3D::quaternion(1, 2, 3, 4);
+ ///
+ /// assert_eq!(to_world.i, to_world.to_untyped().i);
+ /// assert_eq!(to_world.j, to_world.to_untyped().j);
+ /// assert_eq!(to_world.k, to_world.to_untyped().k);
+ /// assert_eq!(to_world.r, to_world.to_untyped().r);
+ /// ```
+ #[inline]
+ pub fn to_untyped(&self) -> Rotation3D<T, UnknownUnit, UnknownUnit> {
+ self.cast_unit()
+ }
+
+ /// Tag a unitless value with units.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use euclid::Rotation3D;
+ /// use euclid::UnknownUnit;
+ /// enum Local {}
+ /// enum World {}
+ ///
+ /// let rot: Rotation3D<_, UnknownUnit, UnknownUnit> = Rotation3D::quaternion(1, 2, 3, 4);
+ ///
+ /// assert_eq!(rot.i, Rotation3D::<_, Local, World>::from_untyped(&rot).i);
+ /// assert_eq!(rot.j, Rotation3D::<_, Local, World>::from_untyped(&rot).j);
+ /// assert_eq!(rot.k, Rotation3D::<_, Local, World>::from_untyped(&rot).k);
+ /// assert_eq!(rot.r, Rotation3D::<_, Local, World>::from_untyped(&rot).r);
+ /// ```
+ #[inline]
+ pub fn from_untyped(r: &Rotation3D<T, UnknownUnit, UnknownUnit>) -> Self {
+ r.cast_unit()
+ }
+}
+
+impl<T, Src, Dst> Rotation3D<T, Src, Dst>
+where
+ T: Real,
+{
+ /// Creates a rotation around from a quaternion representation and normalizes it.
+ ///
+ /// The parameters are a, b, c and r compose the quaternion `a*i + b*j + c*k + r`
+ /// before normalization, where `a`, `b` and `c` describe the vector part and the
+ /// last parameter `r` is the real part.
+ #[inline]
+ pub fn unit_quaternion(i: T, j: T, k: T, r: T) -> Self {
+ Self::quaternion(i, j, k, r).normalize()
+ }
+
+ /// Creates a rotation around a given axis.
+ pub fn around_axis(axis: Vector3D<T, Src>, angle: Angle<T>) -> Self {
+ let axis = axis.normalize();
+ let two = T::one() + T::one();
+ let (sin, cos) = Angle::sin_cos(angle / two);
+ Self::quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos)
+ }
+
+ /// Creates a rotation around the x axis.
+ pub fn around_x(angle: Angle<T>) -> Self {
+ let zero = Zero::zero();
+ let two = T::one() + T::one();
+ let (sin, cos) = Angle::sin_cos(angle / two);
+ Self::quaternion(sin, zero, zero, cos)
+ }
+
+ /// Creates a rotation around the y axis.
+ pub fn around_y(angle: Angle<T>) -> Self {
+ let zero = Zero::zero();
+ let two = T::one() + T::one();
+ let (sin, cos) = Angle::sin_cos(angle / two);
+ Self::quaternion(zero, sin, zero, cos)
+ }
+
+ /// Creates a rotation around the z axis.
+ pub fn around_z(angle: Angle<T>) -> Self {
+ let zero = Zero::zero();
+ let two = T::one() + T::one();
+ let (sin, cos) = Angle::sin_cos(angle / two);
+ Self::quaternion(zero, zero, sin, cos)
+ }
+
+ /// Creates a rotation from Euler angles.
+ ///
+ /// The rotations are applied in roll then pitch then yaw order.
+ ///
+ /// - Roll (also called bank) is a rotation around the x axis.
+ /// - Pitch (also called bearing) is a rotation around the y axis.
+ /// - Yaw (also called heading) is a rotation around the z axis.
+ pub fn euler(roll: Angle<T>, pitch: Angle<T>, yaw: Angle<T>) -> Self {
+ let half = T::one() / (T::one() + T::one());
+
+ let (sy, cy) = Real::sin_cos(half * yaw.get());
+ let (sp, cp) = Real::sin_cos(half * pitch.get());
+ let (sr, cr) = Real::sin_cos(half * roll.get());
+
+ Self::quaternion(
+ cy * sr * cp - sy * cr * sp,
+ cy * cr * sp + sy * sr * cp,
+ sy * cr * cp - cy * sr * sp,
+ cy * cr * cp + sy * sr * sp,
+ )
+ }
+
+ /// Returns the inverse of this rotation.
+ #[inline]
+ pub fn inverse(&self) -> Rotation3D<T, Dst, Src> {
+ Rotation3D::quaternion(-self.i, -self.j, -self.k, self.r)
+ }
+
+ /// Computes the norm of this quaternion.
+ #[inline]
+ pub fn norm(&self) -> T {
+ self.square_norm().sqrt()
+ }
+
+ /// Computes the squared norm of this quaternion.
+ #[inline]
+ pub fn square_norm(&self) -> T {
+ self.i * self.i + self.j * self.j + self.k * self.k + self.r * self.r
+ }
+
+ /// Returns a [unit quaternion] from this one.
+ ///
+ /// [unit quaternion]: https://en.wikipedia.org/wiki/Quaternion#Unit_quaternion
+ #[inline]
+ pub fn normalize(&self) -> Self {
+ self.mul(T::one() / self.norm())
+ }
+
+ /// Returns `true` if [norm] of this quaternion is (approximately) one.
+ ///
+ /// [norm]: #method.norm
+ #[inline]
+ pub fn is_normalized(&self) -> bool
+ where
+ T: ApproxEq<T>,
+ {
+ let eps = NumCast::from(1.0e-5).unwrap();
+ self.square_norm().approx_eq_eps(&T::one(), &eps)
+ }
+
+ /// Spherical linear interpolation between this rotation and another rotation.
+ ///
+ /// `t` is expected to be between zero and one.
+ pub fn slerp(&self, other: &Self, t: T) -> Self
+ where
+ T: ApproxEq<T>,
+ {
+ debug_assert!(self.is_normalized());
+ debug_assert!(other.is_normalized());
+
+ let r1 = *self;
+ let mut r2 = *other;
+
+ let mut dot = r1.i * r2.i + r1.j * r2.j + r1.k * r2.k + r1.r * r2.r;
+
+ let one = T::one();
+
+ if dot.approx_eq(&T::one()) {
+ // If the inputs are too close, linearly interpolate to avoid precision issues.
+ return r1.lerp(&r2, t);
+ }
+
+ // If the dot product is negative, the quaternions
+ // have opposite handed-ness and slerp won't take
+ // the shorter path. Fix by reversing one quaternion.
+ if dot < T::zero() {
+ r2 = r2.mul(-T::one());
+ dot = -dot;
+ }
+
+ // For robustness, stay within the domain of acos.
+ dot = Real::min(dot, one);
+
+ // Angle between r1 and the result.
+ let theta = Real::acos(dot) * t;
+
+ // r1 and r3 form an orthonormal basis.
+ let r3 = r2.sub(r1.mul(dot)).normalize();
+ let (sin, cos) = Real::sin_cos(theta);
+ r1.mul(cos).add(r3.mul(sin))
+ }
+
+ /// Basic Linear interpolation between this rotation and another rotation.
+ #[inline]
+ pub fn lerp(&self, other: &Self, t: T) -> Self {
+ let one_t = T::one() - t;
+ self.mul(one_t).add(other.mul(t)).normalize()
+ }
+
+ /// Returns the given 3d point transformed by this rotation.
+ ///
+ /// The input point must be use the unit Src, and the returned point has the unit Dst.
+ pub fn transform_point3d(&self, point: Point3D<T, Src>) -> Point3D<T, Dst>
+ where
+ T: ApproxEq<T>,
+ {
+ debug_assert!(self.is_normalized());
+
+ let two = T::one() + T::one();
+ let cross = self.vector_part().cross(point.to_vector().to_untyped()) * two;
+
+ point3(
+ point.x + self.r * cross.x + self.j * cross.z - self.k * cross.y,
+ point.y + self.r * cross.y + self.k * cross.x - self.i * cross.z,
+ point.z + self.r * cross.z + self.i * cross.y - self.j * cross.x,
+ )
+ }
+
+ /// Returns the given 2d point transformed by this rotation then projected on the xy plane.
+ ///
+ /// The input point must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_point2d(&self, point: Point2D<T, Src>) -> Point2D<T, Dst>
+ where
+ T: ApproxEq<T>,
+ {
+ self.transform_point3d(point.to_3d()).xy()
+ }
+
+ /// Returns the given 3d vector transformed by this rotation.
+ ///
+ /// The input vector must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_vector3d(&self, vector: Vector3D<T, Src>) -> Vector3D<T, Dst>
+ where
+ T: ApproxEq<T>,
+ {
+ self.transform_point3d(vector.to_point()).to_vector()
+ }
+
+ /// Returns the given 2d vector transformed by this rotation then projected on the xy plane.
+ ///
+ /// The input vector must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_vector2d(&self, vector: Vector2D<T, Src>) -> Vector2D<T, Dst>
+ where
+ T: ApproxEq<T>,
+ {
+ self.transform_vector3d(vector.to_3d()).xy()
+ }
+
+ /// Returns the matrix representation of this rotation.
+ #[inline]
+ pub fn to_transform(&self) -> Transform3D<T, Src, Dst>
+ where
+ T: ApproxEq<T>,
+ {
+ debug_assert!(self.is_normalized());
+
+ let i2 = self.i + self.i;
+ let j2 = self.j + self.j;
+ let k2 = self.k + self.k;
+ let ii = self.i * i2;
+ let ij = self.i * j2;
+ let ik = self.i * k2;
+ let jj = self.j * j2;
+ let jk = self.j * k2;
+ let kk = self.k * k2;
+ let ri = self.r * i2;
+ let rj = self.r * j2;
+ let rk = self.r * k2;
+
+ let one = T::one();
+ let zero = T::zero();
+
+ let m11 = one - (jj + kk);
+ let m12 = ij + rk;
+ let m13 = ik - rj;
+
+ let m21 = ij - rk;
+ let m22 = one - (ii + kk);
+ let m23 = jk + ri;
+
+ let m31 = ik + rj;
+ let m32 = jk - ri;
+ let m33 = one - (ii + jj);
+
+ Transform3D::new(
+ m11, m12, m13, zero,
+ m21, m22, m23, zero,
+ m31, m32, m33, zero,
+ zero, zero, zero, one,
+ )
+ }
+
+ /// Returns a rotation representing this rotation followed by the other rotation.
+ #[inline]
+ pub fn then<NewDst>(
+ &self,
+ other: &Rotation3D<T, Dst, NewDst>,
+ ) -> Rotation3D<T, Src, NewDst>
+ where
+ T: ApproxEq<T>,
+ {
+ debug_assert!(self.is_normalized());
+ Rotation3D::quaternion(
+ other.i * self.r + other.r * self.i + other.j * self.k - other.k * self.j,
+ other.j * self.r + other.r * self.j + other.k * self.i - other.i * self.k,
+ other.k * self.r + other.r * self.k + other.i * self.j - other.j * self.i,
+ other.r * self.r - other.i * self.i - other.j * self.j - other.k * self.k,
+ )
+ }
+
+ // add, sub and mul are used internally for intermediate computation but aren't public
+ // because they don't carry real semantic meanings (I think?).
+
+ #[inline]
+ fn add(&self, other: Self) -> Self {
+ Self::quaternion(
+ self.i + other.i,
+ self.j + other.j,
+ self.k + other.k,
+ self.r + other.r,
+ )
+ }
+
+ #[inline]
+ fn sub(&self, other: Self) -> Self {
+ Self::quaternion(
+ self.i - other.i,
+ self.j - other.j,
+ self.k - other.k,
+ self.r - other.r,
+ )
+ }
+
+ #[inline]
+ fn mul(&self, factor: T) -> Self {
+ Self::quaternion(
+ self.i * factor,
+ self.j * factor,
+ self.k * factor,
+ self.r * factor,
+ )
+ }
+}
+
+impl<T: fmt::Debug, Src, Dst> fmt::Debug for Rotation3D<T, Src, Dst> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "Quat({:?}*i + {:?}*j + {:?}*k + {:?})",
+ self.i, self.j, self.k, self.r
+ )
+ }
+}
+
+impl<T, Src, Dst> ApproxEq<T> for Rotation3D<T, Src, Dst>
+where
+ T: Copy + Neg<Output = T> + ApproxEq<T>,
+{
+ fn approx_epsilon() -> T {
+ T::approx_epsilon()
+ }
+
+ fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
+ (self.i.approx_eq_eps(&other.i, eps)
+ && self.j.approx_eq_eps(&other.j, eps)
+ && self.k.approx_eq_eps(&other.k, eps)
+ && self.r.approx_eq_eps(&other.r, eps))
+ || (self.i.approx_eq_eps(&-other.i, eps)
+ && self.j.approx_eq_eps(&-other.j, eps)
+ && self.k.approx_eq_eps(&-other.k, eps)
+ && self.r.approx_eq_eps(&-other.r, eps))
+ }
+}
+
+#[test]
+fn simple_rotation_2d() {
+ use crate::default::Rotation2D;
+ use core::f32::consts::{FRAC_PI_2, PI};
+
+ let ri = Rotation2D::identity();
+ let r90 = Rotation2D::radians(FRAC_PI_2);
+ let rm90 = Rotation2D::radians(-FRAC_PI_2);
+ let r180 = Rotation2D::radians(PI);
+
+ assert!(ri
+ .transform_point(point2(1.0, 2.0))
+ .approx_eq(&point2(1.0, 2.0)));
+ assert!(r90
+ .transform_point(point2(1.0, 2.0))
+ .approx_eq(&point2(-2.0, 1.0)));
+ assert!(rm90
+ .transform_point(point2(1.0, 2.0))
+ .approx_eq(&point2(2.0, -1.0)));
+ assert!(r180
+ .transform_point(point2(1.0, 2.0))
+ .approx_eq(&point2(-1.0, -2.0)));
+
+ assert!(r90
+ .inverse()
+ .inverse()
+ .transform_point(point2(1.0, 2.0))
+ .approx_eq(&r90.transform_point(point2(1.0, 2.0))));
+}
+
+#[test]
+fn simple_rotation_3d_in_2d() {
+ use crate::default::Rotation3D;
+ use core::f32::consts::{FRAC_PI_2, PI};
+
+ let ri = Rotation3D::identity();
+ let r90 = Rotation3D::around_z(Angle::radians(FRAC_PI_2));
+ let rm90 = Rotation3D::around_z(Angle::radians(-FRAC_PI_2));
+ let r180 = Rotation3D::around_z(Angle::radians(PI));
+
+ assert!(ri
+ .transform_point2d(point2(1.0, 2.0))
+ .approx_eq(&point2(1.0, 2.0)));
+ assert!(r90
+ .transform_point2d(point2(1.0, 2.0))
+ .approx_eq(&point2(-2.0, 1.0)));
+ assert!(rm90
+ .transform_point2d(point2(1.0, 2.0))
+ .approx_eq(&point2(2.0, -1.0)));
+ assert!(r180
+ .transform_point2d(point2(1.0, 2.0))
+ .approx_eq(&point2(-1.0, -2.0)));
+
+ assert!(r90
+ .inverse()
+ .inverse()
+ .transform_point2d(point2(1.0, 2.0))
+ .approx_eq(&r90.transform_point2d(point2(1.0, 2.0))));
+}
+
+#[test]
+fn pre_post() {
+ use crate::default::Rotation3D;
+ use core::f32::consts::FRAC_PI_2;
+
+ let r1 = Rotation3D::around_x(Angle::radians(FRAC_PI_2));
+ let r2 = Rotation3D::around_y(Angle::radians(FRAC_PI_2));
+ let r3 = Rotation3D::around_z(Angle::radians(FRAC_PI_2));
+
+ let t1 = r1.to_transform();
+ let t2 = r2.to_transform();
+ let t3 = r3.to_transform();
+
+ let p = point3(1.0, 2.0, 3.0);
+
+ // Check that the order of transformations is correct (corresponds to what
+ // we do in Transform3D).
+ let p1 = r1.then(&r2).then(&r3).transform_point3d(p);
+ let p2 = t1
+ .then(&t2)
+ .then(&t3)
+ .transform_point3d(p);
+
+ assert!(p1.approx_eq(&p2.unwrap()));
+
+ // Check that changing the order indeed matters.
+ let p3 = t3
+ .then(&t1)
+ .then(&t2)
+ .transform_point3d(p);
+ assert!(!p1.approx_eq(&p3.unwrap()));
+}
+
+#[test]
+fn to_transform3d() {
+ use crate::default::Rotation3D;
+
+ use core::f32::consts::{FRAC_PI_2, PI};
+ let rotations = [
+ Rotation3D::identity(),
+ Rotation3D::around_x(Angle::radians(FRAC_PI_2)),
+ Rotation3D::around_x(Angle::radians(-FRAC_PI_2)),
+ Rotation3D::around_x(Angle::radians(PI)),
+ Rotation3D::around_y(Angle::radians(FRAC_PI_2)),
+ Rotation3D::around_y(Angle::radians(-FRAC_PI_2)),
+ Rotation3D::around_y(Angle::radians(PI)),
+ Rotation3D::around_z(Angle::radians(FRAC_PI_2)),
+ Rotation3D::around_z(Angle::radians(-FRAC_PI_2)),
+ Rotation3D::around_z(Angle::radians(PI)),
+ ];
+
+ let points = [
+ point3(0.0, 0.0, 0.0),
+ point3(1.0, 2.0, 3.0),
+ point3(-5.0, 3.0, -1.0),
+ point3(-0.5, -1.0, 1.5),
+ ];
+
+ for rotation in &rotations {
+ for &point in &points {
+ let p1 = rotation.transform_point3d(point);
+ let p2 = rotation.to_transform().transform_point3d(point);
+ assert!(p1.approx_eq(&p2.unwrap()));
+ }
+ }
+}
+
+#[test]
+fn slerp() {
+ use crate::default::Rotation3D;
+
+ let q1 = Rotation3D::quaternion(1.0, 0.0, 0.0, 0.0);
+ let q2 = Rotation3D::quaternion(0.0, 1.0, 0.0, 0.0);
+ let q3 = Rotation3D::quaternion(0.0, 0.0, -1.0, 0.0);
+
+ // The values below can be obtained with a python program:
+ // import numpy
+ // import quaternion
+ // q1 = numpy.quaternion(1, 0, 0, 0)
+ // q2 = numpy.quaternion(0, 1, 0, 0)
+ // quaternion.slerp_evaluate(q1, q2, 0.2)
+
+ assert!(q1.slerp(&q2, 0.0).approx_eq(&q1));
+ assert!(q1.slerp(&q2, 0.2).approx_eq(&Rotation3D::quaternion(
+ 0.951056516295154,
+ 0.309016994374947,
+ 0.0,
+ 0.0
+ )));
+ assert!(q1.slerp(&q2, 0.4).approx_eq(&Rotation3D::quaternion(
+ 0.809016994374947,
+ 0.587785252292473,
+ 0.0,
+ 0.0
+ )));
+ assert!(q1.slerp(&q2, 0.6).approx_eq(&Rotation3D::quaternion(
+ 0.587785252292473,
+ 0.809016994374947,
+ 0.0,
+ 0.0
+ )));
+ assert!(q1.slerp(&q2, 0.8).approx_eq(&Rotation3D::quaternion(
+ 0.309016994374947,
+ 0.951056516295154,
+ 0.0,
+ 0.0
+ )));
+ assert!(q1.slerp(&q2, 1.0).approx_eq(&q2));
+
+ assert!(q1.slerp(&q3, 0.0).approx_eq(&q1));
+ assert!(q1.slerp(&q3, 0.2).approx_eq(&Rotation3D::quaternion(
+ 0.951056516295154,
+ 0.0,
+ -0.309016994374947,
+ 0.0
+ )));
+ assert!(q1.slerp(&q3, 0.4).approx_eq(&Rotation3D::quaternion(
+ 0.809016994374947,
+ 0.0,
+ -0.587785252292473,
+ 0.0
+ )));
+ assert!(q1.slerp(&q3, 0.6).approx_eq(&Rotation3D::quaternion(
+ 0.587785252292473,
+ 0.0,
+ -0.809016994374947,
+ 0.0
+ )));
+ assert!(q1.slerp(&q3, 0.8).approx_eq(&Rotation3D::quaternion(
+ 0.309016994374947,
+ 0.0,
+ -0.951056516295154,
+ 0.0
+ )));
+ assert!(q1.slerp(&q3, 1.0).approx_eq(&q3));
+}
+
+#[test]
+fn around_axis() {
+ use crate::default::Rotation3D;
+ use core::f32::consts::{FRAC_PI_2, PI};
+
+ // Two sort of trivial cases:
+ let r1 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(PI));
+ let r2 = Rotation3D::around_axis(vec3(1.0, 1.0, 0.0), Angle::radians(FRAC_PI_2));
+ assert!(r1
+ .transform_point3d(point3(1.0, 2.0, 0.0))
+ .approx_eq(&point3(2.0, 1.0, 0.0)));
+ assert!(r2
+ .transform_point3d(point3(1.0, 0.0, 0.0))
+ .approx_eq(&point3(0.5, 0.5, -0.5.sqrt())));
+
+ // A more arbitrary test (made up with numpy):
+ let r3 = Rotation3D::around_axis(vec3(0.5, 1.0, 2.0), Angle::radians(2.291288));
+ assert!(r3
+ .transform_point3d(point3(1.0, 0.0, 0.0))
+ .approx_eq(&point3(-0.58071821, 0.81401868, -0.01182979)));
+}
+
+#[test]
+fn from_euler() {
+ use crate::default::Rotation3D;
+ use core::f32::consts::FRAC_PI_2;
+
+ // First test simple separate yaw pitch and roll rotations, because it is easy to come
+ // up with the corresponding quaternion.
+ // Since several quaternions can represent the same transformation we compare the result
+ // of transforming a point rather than the values of each quaternions.
+ let p = point3(1.0, 2.0, 3.0);
+
+ let angle = Angle::radians(FRAC_PI_2);
+ let zero = Angle::radians(0.0);
+
+ // roll
+ let roll_re = Rotation3D::euler(angle, zero, zero);
+ let roll_rq = Rotation3D::around_x(angle);
+ let roll_pe = roll_re.transform_point3d(p);
+ let roll_pq = roll_rq.transform_point3d(p);
+
+ // pitch
+ let pitch_re = Rotation3D::euler(zero, angle, zero);
+ let pitch_rq = Rotation3D::around_y(angle);
+ let pitch_pe = pitch_re.transform_point3d(p);
+ let pitch_pq = pitch_rq.transform_point3d(p);
+
+ // yaw
+ let yaw_re = Rotation3D::euler(zero, zero, angle);
+ let yaw_rq = Rotation3D::around_z(angle);
+ let yaw_pe = yaw_re.transform_point3d(p);
+ let yaw_pq = yaw_rq.transform_point3d(p);
+
+ assert!(roll_pe.approx_eq(&roll_pq));
+ assert!(pitch_pe.approx_eq(&pitch_pq));
+ assert!(yaw_pe.approx_eq(&yaw_pq));
+
+ // Now check that the yaw pitch and roll transformations when combined are applied in
+ // the proper order: roll -> pitch -> yaw.
+ let ypr_e = Rotation3D::euler(angle, angle, angle);
+ let ypr_q = roll_rq.then(&pitch_rq).then(&yaw_rq);
+ let ypr_pe = ypr_e.transform_point3d(p);
+ let ypr_pq = ypr_q.transform_point3d(p);
+
+ assert!(ypr_pe.approx_eq(&ypr_pq));
+}
diff --git a/third_party/rust/euclid/src/scale.rs b/third_party/rust/euclid/src/scale.rs
new file mode 100644
index 0000000000..eac377a7d0
--- /dev/null
+++ b/third_party/rust/euclid/src/scale.rs
@@ -0,0 +1,428 @@
+// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//! A type-checked scaling factor between units.
+
+use crate::num::One;
+
+use crate::{Point2D, Point3D, Rect, Size2D, Vector2D, Box2D, Box3D};
+use core::cmp::Ordering;
+use core::fmt;
+use core::hash::{Hash, Hasher};
+use core::marker::PhantomData;
+use core::ops::{Add, Div, Mul, Sub};
+use num_traits::NumCast;
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A scaling factor between two different units of measurement.
+///
+/// This is effectively a type-safe float, intended to be used in combination with other types like
+/// `length::Length` to enforce conversion between systems of measurement at compile time.
+///
+/// `Src` and `Dst` represent the units before and after multiplying a value by a `Scale`. They
+/// may be types without values, such as empty enums. For example:
+///
+/// ```rust
+/// use euclid::Scale;
+/// use euclid::Length;
+/// enum Mm {};
+/// enum Inch {};
+///
+/// let mm_per_inch: Scale<f32, Inch, Mm> = Scale::new(25.4);
+///
+/// let one_foot: Length<f32, Inch> = Length::new(12.0);
+/// let one_foot_in_mm: Length<f32, Mm> = one_foot * mm_per_inch;
+/// ```
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+ feature = "serde",
+ serde(bound(
+ serialize = "T: serde::Serialize",
+ deserialize = "T: serde::Deserialize<'de>"
+ ))
+)]
+pub struct Scale<T, Src, Dst>(pub T, #[doc(hidden)] pub PhantomData<(Src, Dst)>);
+
+impl<T, Src, Dst> Scale<T, Src, Dst> {
+ #[inline]
+ pub const fn new(x: T) -> Self {
+ Scale(x, PhantomData)
+ }
+
+ /// Creates an identity scale (1.0).
+ #[inline]
+ pub fn identity() -> Self
+ where
+ T: One
+ {
+ Scale::new(T::one())
+ }
+
+ /// Returns the given point transformed by this scale.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::{Scale, point2};
+ /// enum Mm {};
+ /// enum Cm {};
+ ///
+ /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
+ ///
+ /// assert_eq!(to_mm.transform_point(point2(42, -42)), point2(420, -420));
+ /// ```
+ #[inline]
+ pub fn transform_point(self, point: Point2D<T, Src>) -> Point2D<T::Output, Dst>
+ where
+ T: Copy + Mul,
+ {
+ Point2D::new(point.x * self.0, point.y * self.0)
+ }
+
+ /// Returns the given point transformed by this scale.
+ #[inline]
+ pub fn transform_point3d(self, point: Point3D<T, Src>) -> Point3D<T::Output, Dst>
+ where
+ T: Copy + Mul,
+ {
+ Point3D::new(point.x * self.0, point.y * self.0, point.z * self.0)
+ }
+
+ /// Returns the given vector transformed by this scale.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::{Scale, vec2};
+ /// enum Mm {};
+ /// enum Cm {};
+ ///
+ /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
+ ///
+ /// assert_eq!(to_mm.transform_vector(vec2(42, -42)), vec2(420, -420));
+ /// ```
+ #[inline]
+ pub fn transform_vector(self, vec: Vector2D<T, Src>) -> Vector2D<T::Output, Dst>
+ where
+ T: Copy + Mul,
+ {
+ Vector2D::new(vec.x * self.0, vec.y * self.0)
+ }
+
+ /// Returns the given vector transformed by this scale.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::{Scale, size2};
+ /// enum Mm {};
+ /// enum Cm {};
+ ///
+ /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
+ ///
+ /// assert_eq!(to_mm.transform_size(size2(42, -42)), size2(420, -420));
+ /// ```
+ #[inline]
+ pub fn transform_size(self, size: Size2D<T, Src>) -> Size2D<T::Output, Dst>
+ where
+ T: Copy + Mul,
+ {
+ Size2D::new(size.width * self.0, size.height * self.0)
+ }
+
+ /// Returns the given rect transformed by this scale.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::{Scale, rect};
+ /// enum Mm {};
+ /// enum Cm {};
+ ///
+ /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
+ ///
+ /// assert_eq!(to_mm.transform_rect(&rect(1, 2, 42, -42)), rect(10, 20, 420, -420));
+ /// ```
+ #[inline]
+ pub fn transform_rect(self, rect: &Rect<T, Src>) -> Rect<T::Output, Dst>
+ where
+ T: Copy + Mul,
+ {
+ Rect::new(
+ self.transform_point(rect.origin),
+ self.transform_size(rect.size),
+ )
+ }
+
+ /// Returns the given box transformed by this scale.
+ #[inline]
+ pub fn transform_box2d(self, b: &Box2D<T, Src>) -> Box2D<T::Output, Dst>
+ where
+ T: Copy + Mul,
+ {
+ Box2D {
+ min: self.transform_point(b.min),
+ max: self.transform_point(b.max),
+ }
+ }
+
+ /// Returns the given box transformed by this scale.
+ #[inline]
+ pub fn transform_box3d(self, b: &Box3D<T, Src>) -> Box3D<T::Output, Dst>
+ where
+ T: Copy + Mul,
+ {
+ Box3D {
+ min: self.transform_point3d(b.min),
+ max: self.transform_point3d(b.max),
+ }
+ }
+
+ /// Returns `true` if this scale has no effect.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::Scale;
+ /// use euclid::num::One;
+ /// enum Mm {};
+ /// enum Cm {};
+ ///
+ /// let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+ /// let mm_per_mm: Scale<f32, Mm, Mm> = Scale::new(1.0);
+ ///
+ /// assert_eq!(cm_per_mm.is_identity(), false);
+ /// assert_eq!(mm_per_mm.is_identity(), true);
+ /// assert_eq!(mm_per_mm, Scale::one());
+ /// ```
+ #[inline]
+ pub fn is_identity(self) -> bool
+ where
+ T: PartialEq + One,
+ {
+ self.0 == T::one()
+ }
+
+ /// Returns the underlying scalar scale factor.
+ #[inline]
+ pub fn get(self) -> T {
+ self.0
+ }
+
+ /// The inverse Scale (1.0 / self).
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::Scale;
+ /// enum Mm {};
+ /// enum Cm {};
+ ///
+ /// let cm_per_mm: Scale<f32, Cm, Mm> = Scale::new(0.1);
+ ///
+ /// assert_eq!(cm_per_mm.inverse(), Scale::new(10.0));
+ /// ```
+ pub fn inverse(self) -> Scale<T::Output, Dst, Src>
+ where
+ T: One + Div,
+ {
+ let one: T = One::one();
+ Scale::new(one / self.0)
+ }
+}
+
+impl<T: NumCast, Src, Dst> Scale<T, Src, Dst> {
+ /// Cast from one numeric representation to another, preserving the units.
+ ///
+ /// # Panics
+ ///
+ /// If the source value cannot be represented by the target type `NewT`, then
+ /// method panics. Use `try_cast` if that must be case.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::Scale;
+ /// enum Mm {};
+ /// enum Cm {};
+ ///
+ /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
+ ///
+ /// assert_eq!(to_mm.cast::<f32>(), Scale::new(10.0));
+ /// ```
+ /// That conversion will panic, because `i32` not enough to store such big numbers:
+ /// ```rust,should_panic
+ /// use euclid::Scale;
+ /// enum Mm {};// millimeter = 10^-2 meters
+ /// enum Em {};// exameter = 10^18 meters
+ ///
+ /// // Panics
+ /// let to_em: Scale<i32, Mm, Em> = Scale::new(10e20).cast();
+ /// ```
+ #[inline]
+ pub fn cast<NewT: NumCast>(self) -> Scale<NewT, Src, Dst> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ /// If the source value cannot be represented by the target type `NewT`, then `None`
+ /// is returned.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::Scale;
+ /// enum Mm {};
+ /// enum Cm {};
+ /// enum Em {};// Exameter = 10^18 meters
+ ///
+ /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
+ /// let to_em: Scale<f32, Mm, Em> = Scale::new(10e20);
+ ///
+ /// assert_eq!(to_mm.try_cast::<f32>(), Some(Scale::new(10.0)));
+ /// // Integer to small to store that number
+ /// assert_eq!(to_em.try_cast::<i32>(), None);
+ /// ```
+ pub fn try_cast<NewT: NumCast>(self) -> Option<Scale<NewT, Src, Dst>> {
+ NumCast::from(self.0).map(Scale::new)
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, Src, Dst> Zeroable for Scale<T, Src, Dst> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Scale<T, Src, Dst> {}
+
+// scale0 * scale1
+// (A,B) * (B,C) = (A,C)
+impl<T: Mul, A, B, C> Mul<Scale<T, B, C>> for Scale<T, A, B> {
+ type Output = Scale<T::Output, A, C>;
+
+ #[inline]
+ fn mul(self, other: Scale<T, B, C>) -> Self::Output {
+ Scale::new(self.0 * other.0)
+ }
+}
+
+// scale0 + scale1
+impl<T: Add, Src, Dst> Add for Scale<T, Src, Dst> {
+ type Output = Scale<T::Output, Src, Dst>;
+
+ #[inline]
+ fn add(self, other: Scale<T, Src, Dst>) -> Self::Output {
+ Scale::new(self.0 + other.0)
+ }
+}
+
+// scale0 - scale1
+impl<T: Sub, Src, Dst> Sub for Scale<T, Src, Dst> {
+ type Output = Scale<T::Output, Src, Dst>;
+
+ #[inline]
+ fn sub(self, other: Scale<T, Src, Dst>) -> Self::Output {
+ Scale::new(self.0 - other.0)
+ }
+}
+
+// FIXME: Switch to `derive(PartialEq, Clone)` after this Rust issue is fixed:
+// https://github.com/rust-lang/rust/issues/26925
+
+impl<T: PartialEq, Src, Dst> PartialEq for Scale<T, Src, Dst> {
+ fn eq(&self, other: &Scale<T, Src, Dst>) -> bool {
+ self.0 == other.0
+ }
+}
+
+impl<T: Eq, Src, Dst> Eq for Scale<T, Src, Dst> {}
+
+impl<T: PartialOrd, Src, Dst> PartialOrd for Scale<T, Src, Dst> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.0.partial_cmp(&other.0)
+ }
+}
+
+impl<T: Ord, Src, Dst> Ord for Scale<T, Src, Dst> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.0.cmp(&other.0)
+ }
+}
+
+impl<T: Clone, Src, Dst> Clone for Scale<T, Src, Dst> {
+ fn clone(&self) -> Scale<T, Src, Dst> {
+ Scale::new(self.0.clone())
+ }
+}
+
+impl<T: Copy, Src, Dst> Copy for Scale<T, Src, Dst> {}
+
+impl<T: fmt::Debug, Src, Dst> fmt::Debug for Scale<T, Src, Dst> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl<T: Default, Src, Dst> Default for Scale<T, Src, Dst> {
+ fn default() -> Self {
+ Self::new(T::default())
+ }
+}
+
+impl<T: Hash, Src, Dst> Hash for Scale<T, Src, Dst> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.0.hash(state)
+ }
+}
+
+impl<T: One, Src, Dst> One for Scale<T, Src, Dst> {
+ #[inline]
+ fn one() -> Self {
+ Scale::new(T::one())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Scale;
+
+ enum Inch {}
+ enum Cm {}
+ enum Mm {}
+
+ #[test]
+ fn test_scale() {
+ let mm_per_inch: Scale<f32, Inch, Mm> = Scale::new(25.4);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let mm_per_cm: Scale<f32, Cm, Mm> = cm_per_mm.inverse();
+ assert_eq!(mm_per_cm.get(), 10.0);
+
+ let one: Scale<f32, Mm, Mm> = cm_per_mm * mm_per_cm;
+ assert_eq!(one.get(), 1.0);
+
+ let one: Scale<f32, Cm, Cm> = mm_per_cm * cm_per_mm;
+ assert_eq!(one.get(), 1.0);
+
+ let cm_per_inch: Scale<f32, Inch, Cm> = mm_per_inch * cm_per_mm;
+ // mm cm cm
+ // ---- x ---- = ----
+ // inch mm inch
+ assert_eq!(cm_per_inch, Scale::new(2.54));
+
+ let a: Scale<isize, Inch, Inch> = Scale::new(2);
+ let b: Scale<isize, Inch, Inch> = Scale::new(3);
+ assert_ne!(a, b);
+ assert_eq!(a, a.clone());
+ assert_eq!(a.clone() + b.clone(), Scale::new(5));
+ assert_eq!(a - b, Scale::new(-1));
+ }
+}
diff --git a/third_party/rust/euclid/src/side_offsets.rs b/third_party/rust/euclid/src/side_offsets.rs
new file mode 100644
index 0000000000..ae00fef833
--- /dev/null
+++ b/third_party/rust/euclid/src/side_offsets.rs
@@ -0,0 +1,520 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! A group of side offsets, which correspond to top/left/bottom/right for borders, padding,
+//! and margins in CSS.
+
+use crate::length::Length;
+use crate::num::Zero;
+use crate::scale::Scale;
+use crate::Vector2D;
+use core::cmp::{Eq, PartialEq};
+use core::fmt;
+use core::hash::Hash;
+use core::marker::PhantomData;
+use core::ops::{Add, AddAssign, Sub, SubAssign, Div, DivAssign, Mul, MulAssign, Neg};
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A group of 2D side offsets, which correspond to top/right/bottom/left for borders, padding,
+/// and margins in CSS, optionally tagged with a unit.
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+ feature = "serde",
+ serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
+)]
+pub struct SideOffsets2D<T, U> {
+ pub top: T,
+ pub right: T,
+ pub bottom: T,
+ pub left: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<U>,
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a, T, U> arbitrary::Arbitrary<'a> for SideOffsets2D<T, U>
+where
+ T: arbitrary::Arbitrary<'a>,
+{
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>
+ {
+ let (top, right, bottom, left) = arbitrary::Arbitrary::arbitrary(u)?;
+ Ok(SideOffsets2D {
+ top,
+ right,
+ bottom,
+ left,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for SideOffsets2D<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for SideOffsets2D<T, U> {}
+
+impl<T: Copy, U> Copy for SideOffsets2D<T, U> {}
+
+impl<T: Clone, U> Clone for SideOffsets2D<T, U> {
+ fn clone(&self) -> Self {
+ SideOffsets2D {
+ top: self.top.clone(),
+ right: self.right.clone(),
+ bottom: self.bottom.clone(),
+ left: self.left.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+impl<T, U> Eq for SideOffsets2D<T, U> where T: Eq {}
+
+impl<T, U> PartialEq for SideOffsets2D<T, U>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.top == other.top
+ && self.right == other.right
+ && self.bottom == other.bottom
+ && self.left == other.left
+ }
+}
+
+impl<T, U> Hash for SideOffsets2D<T, U>
+where
+ T: Hash,
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.top.hash(h);
+ self.right.hash(h);
+ self.bottom.hash(h);
+ self.left.hash(h);
+ }
+}
+
+impl<T: fmt::Debug, U> fmt::Debug for SideOffsets2D<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "({:?},{:?},{:?},{:?})",
+ self.top, self.right, self.bottom, self.left
+ )
+ }
+}
+
+impl<T: Default, U> Default for SideOffsets2D<T, U> {
+ fn default() -> Self {
+ SideOffsets2D {
+ top: Default::default(),
+ right: Default::default(),
+ bottom: Default::default(),
+ left: Default::default(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+impl<T, U> SideOffsets2D<T, U> {
+ /// Constructor taking a scalar for each side.
+ ///
+ /// Sides are specified in top-right-bottom-left order following
+ /// CSS's convention.
+ pub const fn new(top: T, right: T, bottom: T, left: T) -> Self {
+ SideOffsets2D {
+ top,
+ right,
+ bottom,
+ left,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Constructor taking a typed Length for each side.
+ ///
+ /// Sides are specified in top-right-bottom-left order following
+ /// CSS's convention.
+ pub fn from_lengths(
+ top: Length<T, U>,
+ right: Length<T, U>,
+ bottom: Length<T, U>,
+ left: Length<T, U>,
+ ) -> Self {
+ SideOffsets2D::new(top.0, right.0, bottom.0, left.0)
+ }
+
+ /// Construct side offsets from min and a max vector offsets.
+ ///
+ /// The outer rect of the resulting side offsets is equivalent to translating
+ /// a rectangle's upper-left corner with the min vector and translating the
+ /// bottom-right corner with the max vector.
+ pub fn from_vectors_outer(min: Vector2D<T, U>, max: Vector2D<T, U>) -> Self
+ where
+ T: Neg<Output = T>,
+ {
+ SideOffsets2D {
+ left: -min.x,
+ top: -min.y,
+ right: max.x,
+ bottom: max.y,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Construct side offsets from min and a max vector offsets.
+ ///
+ /// The inner rect of the resulting side offsets is equivalent to translating
+ /// a rectangle's upper-left corner with the min vector and translating the
+ /// bottom-right corner with the max vector.
+ pub fn from_vectors_inner(min: Vector2D<T, U>, max: Vector2D<T, U>) -> Self
+ where
+ T: Neg<Output = T>,
+ {
+ SideOffsets2D {
+ left: min.x,
+ top: min.y,
+ right: -max.x,
+ bottom: -max.y,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Constructor, setting all sides to zero.
+ pub fn zero() -> Self
+ where T: Zero,
+ {
+ SideOffsets2D::new(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero())
+ }
+
+ /// Returns `true` if all side offsets are zero.
+ pub fn is_zero(&self) -> bool
+ where
+ T: Zero + PartialEq,
+ {
+ let zero = T::zero();
+ self.top == zero && self.right == zero && self.bottom == zero && self.left == zero
+ }
+
+ /// Constructor setting the same value to all sides, taking a scalar value directly.
+ pub fn new_all_same(all: T) -> Self
+ where T : Copy
+ {
+ SideOffsets2D::new(all, all, all, all)
+ }
+
+ /// Constructor setting the same value to all sides, taking a typed Length.
+ pub fn from_length_all_same(all: Length<T, U>) -> Self
+ where T : Copy
+ {
+ SideOffsets2D::new_all_same(all.0)
+ }
+
+ pub fn horizontal(&self) -> T
+ where T: Copy + Add<T, Output = T>
+ {
+ self.left + self.right
+ }
+
+ pub fn vertical(&self) -> T
+ where T: Copy + Add<T, Output = T>
+ {
+ self.top + self.bottom
+ }
+}
+
+impl<T, U> Add for SideOffsets2D<T, U>
+where
+ T: Add<T, Output = T>,
+{
+ type Output = Self;
+ fn add(self, other: Self) -> Self {
+ SideOffsets2D::new(
+ self.top + other.top,
+ self.right + other.right,
+ self.bottom + other.bottom,
+ self.left + other.left,
+ )
+ }
+}
+
+impl<T, U> AddAssign<Self> for SideOffsets2D<T, U>
+where
+ T: AddAssign<T>,
+{
+ fn add_assign(&mut self, other: Self) {
+ self.top += other.top;
+ self.right += other.right;
+ self.bottom += other.bottom;
+ self.left += other.left;
+ }
+}
+
+impl<T, U> Sub for SideOffsets2D<T, U>
+where
+ T: Sub<T, Output = T>,
+{
+ type Output = Self;
+ fn sub(self, other: Self) -> Self {
+ SideOffsets2D::new(
+ self.top - other.top,
+ self.right - other.right,
+ self.bottom - other.bottom,
+ self.left - other.left,
+ )
+ }
+}
+
+impl<T, U> SubAssign<Self> for SideOffsets2D<T, U>
+where
+ T: SubAssign<T>,
+{
+ fn sub_assign(&mut self, other: Self) {
+ self.top -= other.top;
+ self.right -= other.right;
+ self.bottom -= other.bottom;
+ self.left -= other.left;
+ }
+}
+
+impl<T, U> Neg for SideOffsets2D<T, U>
+where
+ T: Neg<Output = T>
+{
+ type Output = Self;
+ fn neg(self) -> Self {
+ SideOffsets2D {
+ top: -self.top,
+ right: -self.right,
+ bottom: -self.bottom,
+ left: -self.left,
+ _unit: PhantomData,
+ }
+ }
+}
+
+impl<T: Copy + Mul, U> Mul<T> for SideOffsets2D<T, U> {
+ type Output = SideOffsets2D<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ SideOffsets2D::new(
+ self.top * scale,
+ self.right * scale,
+ self.bottom * scale,
+ self.left * scale,
+ )
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<T> for SideOffsets2D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, other: T) {
+ self.top *= other;
+ self.right *= other;
+ self.bottom *= other;
+ self.left *= other;
+ }
+}
+
+impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for SideOffsets2D<T, U1> {
+ type Output = SideOffsets2D<T::Output, U2>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ SideOffsets2D::new(
+ self.top * scale.0,
+ self.right * scale.0,
+ self.bottom * scale.0,
+ self.left * scale.0,
+ )
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for SideOffsets2D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, other: Scale<T, U, U>) {
+ *self *= other.0;
+ }
+}
+
+impl<T: Copy + Div, U> Div<T> for SideOffsets2D<T, U> {
+ type Output = SideOffsets2D<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ SideOffsets2D::new(
+ self.top / scale,
+ self.right / scale,
+ self.bottom / scale,
+ self.left / scale,
+ )
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<T> for SideOffsets2D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, other: T) {
+ self.top /= other;
+ self.right /= other;
+ self.bottom /= other;
+ self.left /= other;
+ }
+}
+
+impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for SideOffsets2D<T, U2> {
+ type Output = SideOffsets2D<T::Output, U1>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ SideOffsets2D::new(
+ self.top / scale.0,
+ self.right / scale.0,
+ self.bottom / scale.0,
+ self.left / scale.0,
+ )
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for SideOffsets2D<T, U> {
+ fn div_assign(&mut self, other: Scale<T, U, U>) {
+ *self /= other.0;
+ }
+}
+
+#[test]
+fn from_vectors() {
+ use crate::{point2, vec2};
+ type Box2D = crate::default::Box2D<i32>;
+
+ let b = Box2D {
+ min: point2(10, 10),
+ max: point2(20, 20),
+ };
+
+ let outer = b.outer_box(SideOffsets2D::from_vectors_outer(vec2(-1, -2), vec2(3, 4)));
+ let inner = b.inner_box(SideOffsets2D::from_vectors_inner(vec2(1, 2), vec2(-3, -4)));
+
+ assert_eq!(
+ outer,
+ Box2D {
+ min: point2(9, 8),
+ max: point2(23, 24)
+ }
+ );
+ assert_eq!(
+ inner,
+ Box2D {
+ min: point2(11, 12),
+ max: point2(17, 16)
+ }
+ );
+}
+
+#[test]
+fn test_is_zero() {
+ let s1: SideOffsets2D<f32, ()> = SideOffsets2D::new_all_same(0.0);
+ assert!(s1.is_zero());
+
+ let s2: SideOffsets2D<f32, ()> = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0);
+ assert!(!s2.is_zero());
+}
+
+#[cfg(test)]
+mod ops {
+ use crate::Scale;
+
+ pub enum Mm {}
+ pub enum Cm {}
+
+ type SideOffsets2D<T> = crate::default::SideOffsets2D<T>;
+ type SideOffsets2DMm<T> = crate::SideOffsets2D<T, Mm>;
+ type SideOffsets2DCm<T> = crate::SideOffsets2D<T, Cm>;
+
+ #[test]
+ fn test_mul_scalar() {
+ let s = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0);
+
+ let result = s * 3.0;
+
+ assert_eq!(result, SideOffsets2D::new(3.0, 6.0, 9.0, 12.0));
+ }
+
+ #[test]
+ fn test_mul_assign_scalar() {
+ let mut s = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0);
+
+ s *= 2.0;
+
+ assert_eq!(s, SideOffsets2D::new(2.0, 4.0, 6.0, 8.0));
+ }
+
+ #[test]
+ fn test_mul_scale() {
+ let s = SideOffsets2DMm::new(0.0, 1.0, 3.0, 2.0);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = s * cm_per_mm;
+
+ assert_eq!(result, SideOffsets2DCm::new(0.0, 0.1, 0.3, 0.2));
+ }
+
+ #[test]
+ fn test_mul_assign_scale() {
+ let mut s = SideOffsets2DMm::new(2.0, 4.0, 6.0, 8.0);
+ let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
+
+ s *= scale;
+
+ assert_eq!(s, SideOffsets2DMm::new(0.2, 0.4, 0.6, 0.8));
+ }
+
+ #[test]
+ fn test_div_scalar() {
+ let s = SideOffsets2D::new(10.0, 20.0, 30.0, 40.0);
+
+ let result = s / 10.0;
+
+ assert_eq!(result, SideOffsets2D::new(1.0, 2.0, 3.0, 4.0));
+ }
+
+ #[test]
+ fn test_div_assign_scalar() {
+ let mut s = SideOffsets2D::new(10.0, 20.0, 30.0, 40.0);
+
+ s /= 10.0;
+
+ assert_eq!(s, SideOffsets2D::new(1.0, 2.0, 3.0, 4.0));
+ }
+
+ #[test]
+ fn test_div_scale() {
+ let s = SideOffsets2DCm::new(0.1, 0.2, 0.3, 0.4);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = s / cm_per_mm;
+
+ assert_eq!(result, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0));
+ }
+
+ #[test]
+ fn test_div_assign_scale() {
+ let mut s = SideOffsets2DMm::new(0.1, 0.2, 0.3, 0.4);
+ let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
+
+ s /= scale;
+
+ assert_eq!(s, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0));
+ }
+}
diff --git a/third_party/rust/euclid/src/size.rs b/third_party/rust/euclid/src/size.rs
new file mode 100644
index 0000000000..f634c1cfb6
--- /dev/null
+++ b/third_party/rust/euclid/src/size.rs
@@ -0,0 +1,1854 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::UnknownUnit;
+use crate::approxord::{max, min};
+use crate::length::Length;
+use crate::num::*;
+use crate::scale::Scale;
+use crate::vector::{vec2, BoolVector2D, Vector2D};
+use crate::vector::{vec3, BoolVector3D, Vector3D};
+#[cfg(feature = "mint")]
+use mint;
+
+use core::cmp::{Eq, PartialEq};
+use core::fmt;
+use core::hash::Hash;
+use core::iter::Sum;
+use core::marker::PhantomData;
+use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+use num_traits::{NumCast, Signed, Float};
+#[cfg(feature = "serde")]
+use serde;
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A 2d size tagged with a unit.
+#[repr(C)]
+pub struct Size2D<T, U> {
+ /// The extent of the element in the `U` units along the `x` axis (usually horizontal).
+ pub width: T,
+ /// The extent of the element in the `U` units along the `y` axis (usually vertical).
+ pub height: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<U>,
+}
+
+impl<T: Copy, U> Copy for Size2D<T, U> {}
+
+impl<T: Clone, U> Clone for Size2D<T, U> {
+ fn clone(&self) -> Self {
+ Size2D {
+ width: self.width.clone(),
+ height: self.height.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de, T, U> serde::Deserialize<'de> for Size2D<T, U>
+where
+ T: serde::Deserialize<'de>,
+{
+ /// Deserializes 2d size from tuple of width and height.
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let (width, height) = serde::Deserialize::deserialize(deserializer)?;
+ Ok(Size2D {
+ width,
+ height,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<T, U> serde::Serialize for Size2D<T, U>
+where
+ T: serde::Serialize,
+{
+ /// Serializes 2d size to tuple of width and height.
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ (&self.width, &self.height).serialize(serializer)
+ }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a, T, U> arbitrary::Arbitrary<'a> for Size2D<T, U>
+where
+ T: arbitrary::Arbitrary<'a>,
+{
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>
+ {
+ let (width, height) = arbitrary::Arbitrary::arbitrary(u)?;
+ Ok(Size2D {
+ width,
+ height,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for Size2D<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for Size2D<T, U> {}
+
+impl<T, U> Eq for Size2D<T, U> where T: Eq {}
+
+impl<T, U> PartialEq for Size2D<T, U>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.width == other.width && self.height == other.height
+ }
+}
+
+impl<T, U> Hash for Size2D<T, U>
+where
+ T: Hash,
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.width.hash(h);
+ self.height.hash(h);
+ }
+}
+
+impl<T: fmt::Debug, U> fmt::Debug for Size2D<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(&self.width, f)?;
+ write!(f, "x")?;
+ fmt::Debug::fmt(&self.height, f)
+ }
+}
+
+impl<T: Default, U> Default for Size2D<T, U> {
+ fn default() -> Self {
+ Size2D::new(Default::default(), Default::default())
+ }
+}
+
+impl<T, U> Size2D<T, U> {
+ /// The same as [`Zero::zero()`] but available without importing trait.
+ ///
+ /// [`Zero::zero()`]: ./num/trait.Zero.html#tymethod.zero
+ #[inline]
+ pub fn zero() -> Self
+ where
+ T: Zero,
+ {
+ Size2D::new(Zero::zero(), Zero::zero())
+ }
+
+ /// Constructor taking scalar values.
+ #[inline]
+ pub const fn new(width: T, height: T) -> Self {
+ Size2D {
+ width,
+ height,
+ _unit: PhantomData,
+ }
+ }
+ /// Constructor taking scalar strongly typed lengths.
+ #[inline]
+ pub fn from_lengths(width: Length<T, U>, height: Length<T, U>) -> Self {
+ Size2D::new(width.0, height.0)
+ }
+
+ /// Constructor setting all components to the same value.
+ #[inline]
+ pub fn splat(v: T) -> Self
+ where
+ T: Clone,
+ {
+ Size2D {
+ width: v.clone(),
+ height: v,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(p: Size2D<T, UnknownUnit>) -> Self {
+ Size2D::new(p.width, p.height)
+ }
+}
+
+impl<T: Copy, U> Size2D<T, U> {
+ /// Return this size as an array of two elements (width, then height).
+ #[inline]
+ pub fn to_array(self) -> [T; 2] {
+ [self.width, self.height]
+ }
+
+ /// Return this size as a tuple of two elements (width, then height).
+ #[inline]
+ pub fn to_tuple(self) -> (T, T) {
+ (self.width, self.height)
+ }
+
+ /// Return this size as a vector with width and height.
+ #[inline]
+ pub fn to_vector(self) -> Vector2D<T, U> {
+ vec2(self.width, self.height)
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(self) -> Size2D<T, UnknownUnit> {
+ self.cast_unit()
+ }
+
+ /// Cast the unit
+ #[inline]
+ pub fn cast_unit<V>(self) -> Size2D<T, V> {
+ Size2D::new(self.width, self.height)
+ }
+
+ /// Rounds each component to the nearest integer value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::size2;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(size2::<_, Mm>(-0.1, -0.8).round(), size2::<_, Mm>(0.0, -1.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn round(self) -> Self
+ where
+ T: Round,
+ {
+ Size2D::new(self.width.round(), self.height.round())
+ }
+
+ /// Rounds each component to the smallest integer equal or greater than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::size2;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(size2::<_, Mm>(-0.1, -0.8).ceil(), size2::<_, Mm>(0.0, 0.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn ceil(self) -> Self
+ where
+ T: Ceil,
+ {
+ Size2D::new(self.width.ceil(), self.height.ceil())
+ }
+
+ /// Rounds each component to the biggest integer equal or lower than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::size2;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(size2::<_, Mm>(-0.1, -0.8).floor(), size2::<_, Mm>(-1.0, -1.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn floor(self) -> Self
+ where
+ T: Floor,
+ {
+ Size2D::new(self.width.floor(), self.height.floor())
+ }
+
+ /// Returns result of multiplication of both components
+ pub fn area(self) -> T::Output
+ where
+ T: Mul,
+ {
+ self.width * self.height
+ }
+
+ /// Linearly interpolate each component between this size and another size.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::size2;
+ /// use euclid::default::Size2D;
+ ///
+ /// let from: Size2D<_> = size2(0.0, 10.0);
+ /// let to: Size2D<_> = size2(8.0, -4.0);
+ ///
+ /// assert_eq!(from.lerp(to, -1.0), size2(-8.0, 24.0));
+ /// assert_eq!(from.lerp(to, 0.0), size2( 0.0, 10.0));
+ /// assert_eq!(from.lerp(to, 0.5), size2( 4.0, 3.0));
+ /// assert_eq!(from.lerp(to, 1.0), size2( 8.0, -4.0));
+ /// assert_eq!(from.lerp(to, 2.0), size2(16.0, -18.0));
+ /// ```
+ #[inline]
+ pub fn lerp(self, other: Self, t: T) -> Self
+ where
+ T: One + Sub<Output = T> + Mul<Output = T> + Add<Output = T>,
+ {
+ let one_t = T::one() - t;
+ self * one_t + other * t
+ }
+}
+
+impl<T: NumCast + Copy, U> Size2D<T, U> {
+ /// Cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ #[inline]
+ pub fn cast<NewT: NumCast>(self) -> Size2D<NewT, U> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ pub fn try_cast<NewT: NumCast>(self) -> Option<Size2D<NewT, U>> {
+ match (NumCast::from(self.width), NumCast::from(self.height)) {
+ (Some(w), Some(h)) => Some(Size2D::new(w, h)),
+ _ => None,
+ }
+ }
+
+ // Convenience functions for common casts
+
+ /// Cast into an `f32` size.
+ #[inline]
+ pub fn to_f32(self) -> Size2D<f32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `f64` size.
+ #[inline]
+ pub fn to_f64(self) -> Size2D<f64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `uint` size, truncating decimals if any.
+ ///
+ /// When casting from floating point sizes, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_usize(self) -> Size2D<usize, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u32` size, truncating decimals if any.
+ ///
+ /// When casting from floating point sizes, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_u32(self) -> Size2D<u32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u64` size, truncating decimals if any.
+ ///
+ /// When casting from floating point sizes, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_u64(self) -> Size2D<u64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i32` size, truncating decimals if any.
+ ///
+ /// When casting from floating point sizes, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i32(self) -> Size2D<i32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i64` size, truncating decimals if any.
+ ///
+ /// When casting from floating point sizes, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i64(self) -> Size2D<i64, U> {
+ self.cast()
+ }
+}
+
+impl<T: Float, U> Size2D<T, U> {
+ /// Returns true if all members are finite.
+ #[inline]
+ pub fn is_finite(self) -> bool {
+ self.width.is_finite() && self.height.is_finite()
+ }
+}
+
+impl<T: Signed, U> Size2D<T, U> {
+ /// Computes the absolute value of each component.
+ ///
+ /// For `f32` and `f64`, `NaN` will be returned for component if the component is `NaN`.
+ ///
+ /// For signed integers, `::MIN` will be returned for component if the component is `::MIN`.
+ pub fn abs(self) -> Self {
+ size2(self.width.abs(), self.height.abs())
+ }
+
+ /// Returns `true` if both components is positive and `false` any component is zero or negative.
+ pub fn is_positive(self) -> bool {
+ self.width.is_positive() && self.height.is_positive()
+ }
+}
+
+impl<T: PartialOrd, U> Size2D<T, U> {
+ /// Returns the size each component of which are minimum of this size and another.
+ #[inline]
+ pub fn min(self, other: Self) -> Self {
+ size2(min(self.width, other.width), min(self.height, other.height))
+ }
+
+ /// Returns the size each component of which are maximum of this size and another.
+ #[inline]
+ pub fn max(self, other: Self) -> Self {
+ size2(max(self.width, other.width), max(self.height, other.height))
+ }
+
+ /// Returns the size each component of which clamped by corresponding
+ /// components of `start` and `end`.
+ ///
+ /// Shortcut for `self.max(start).min(end)`.
+ #[inline]
+ pub fn clamp(self, start: Self, end: Self) -> Self
+ where
+ T: Copy,
+ {
+ self.max(start).min(end)
+ }
+
+ // Returns true if this size is larger or equal to the other size in all dimensions.
+ #[inline]
+ pub fn contains(self, other: Self) -> bool {
+ self.width >= other.width && self.height >= other.height
+ }
+
+ /// Returns vector with results of "greater then" operation on each component.
+ pub fn greater_than(self, other: Self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.width > other.width,
+ y: self.height > other.height,
+ }
+ }
+
+ /// Returns vector with results of "lower then" operation on each component.
+ pub fn lower_than(self, other: Self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.width < other.width,
+ y: self.height < other.height,
+ }
+ }
+
+ /// Returns `true` if any component of size is zero, negative, or NaN.
+ pub fn is_empty(self) -> bool
+ where
+ T: Zero,
+ {
+ let zero = T::zero();
+ // The condition is experessed this way so that we return true in
+ // the presence of NaN.
+ !(self.width > zero && self.height > zero)
+ }
+}
+
+impl<T: PartialEq, U> Size2D<T, U> {
+ /// Returns vector with results of "equal" operation on each component.
+ pub fn equal(self, other: Self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.width == other.width,
+ y: self.height == other.height,
+ }
+ }
+
+ /// Returns vector with results of "not equal" operation on each component.
+ pub fn not_equal(self, other: Self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.width != other.width,
+ y: self.height != other.height,
+ }
+ }
+}
+
+impl<T: Round, U> Round for Size2D<T, U> {
+ /// See [`Size2D::round()`](#method.round).
+ #[inline]
+ fn round(self) -> Self {
+ self.round()
+ }
+}
+
+impl<T: Ceil, U> Ceil for Size2D<T, U> {
+ /// See [`Size2D::ceil()`](#method.ceil).
+ #[inline]
+ fn ceil(self) -> Self {
+ self.ceil()
+ }
+}
+
+impl<T: Floor, U> Floor for Size2D<T, U> {
+ /// See [`Size2D::floor()`](#method.floor).
+ #[inline]
+ fn floor(self) -> Self {
+ self.floor()
+ }
+}
+
+impl<T: Zero, U> Zero for Size2D<T, U> {
+ #[inline]
+ fn zero() -> Self {
+ Size2D::new(Zero::zero(), Zero::zero())
+ }
+}
+
+impl<T: Neg, U> Neg for Size2D<T, U> {
+ type Output = Size2D<T::Output, U>;
+
+ #[inline]
+ fn neg(self) -> Self::Output {
+ Size2D::new(-self.width, -self.height)
+ }
+}
+
+impl<T: Add, U> Add for Size2D<T, U> {
+ type Output = Size2D<T::Output, U>;
+
+ #[inline]
+ fn add(self, other: Self) -> Self::Output {
+ Size2D::new(self.width + other.width, self.height + other.height)
+ }
+}
+
+impl<T: Copy + Add<T, Output = T>, U> Add<&Self> for Size2D<T, U> {
+ type Output = Self;
+ fn add(self, other: &Self) -> Self {
+ Size2D::new(self.width + other.width, self.height + other.height)
+ }
+}
+
+impl<T: Add<Output = T> + Zero, U> Sum for Size2D<T, U> {
+ fn sum<I: Iterator<Item=Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+impl<'a, T: 'a + Add<Output = T> + Copy + Zero, U: 'a> Sum<&'a Self> for Size2D<T, U> {
+ fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+impl<T: AddAssign, U> AddAssign for Size2D<T, U> {
+ #[inline]
+ fn add_assign(&mut self, other: Self) {
+ self.width += other.width;
+ self.height += other.height;
+ }
+}
+
+impl<T: Sub, U> Sub for Size2D<T, U> {
+ type Output = Size2D<T::Output, U>;
+
+ #[inline]
+ fn sub(self, other: Self) -> Self::Output {
+ Size2D::new(self.width - other.width, self.height - other.height)
+ }
+}
+
+impl<T: SubAssign, U> SubAssign for Size2D<T, U> {
+ #[inline]
+ fn sub_assign(&mut self, other: Self) {
+ self.width -= other.width;
+ self.height -= other.height;
+ }
+}
+
+impl<T: Copy + Mul, U> Mul<T> for Size2D<T, U> {
+ type Output = Size2D<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ Size2D::new(self.width * scale, self.height * scale)
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<T> for Size2D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, other: T) {
+ self.width *= other;
+ self.height *= other;
+ }
+}
+
+impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Size2D<T, U1> {
+ type Output = Size2D<T::Output, U2>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ Size2D::new(self.width * scale.0, self.height * scale.0)
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Size2D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, other: Scale<T, U, U>) {
+ *self *= other.0;
+ }
+}
+
+impl<T: Copy + Div, U> Div<T> for Size2D<T, U> {
+ type Output = Size2D<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ Size2D::new(self.width / scale, self.height / scale)
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<T> for Size2D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, other: T) {
+ self.width /= other;
+ self.height /= other;
+ }
+}
+
+impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Size2D<T, U2> {
+ type Output = Size2D<T::Output, U1>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ Size2D::new(self.width / scale.0, self.height / scale.0)
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Size2D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, other: Scale<T, U, U>) {
+ *self /= other.0;
+ }
+}
+
+/// Shorthand for `Size2D::new(w, h)`.
+#[inline]
+pub const fn size2<T, U>(w: T, h: T) -> Size2D<T, U> {
+ Size2D::new(w, h)
+}
+
+#[cfg(feature = "mint")]
+impl<T, U> From<mint::Vector2<T>> for Size2D<T, U> {
+ #[inline]
+ fn from(v: mint::Vector2<T>) -> Self {
+ Size2D {
+ width: v.x,
+ height: v.y,
+ _unit: PhantomData,
+ }
+ }
+}
+#[cfg(feature = "mint")]
+impl<T, U> Into<mint::Vector2<T>> for Size2D<T, U> {
+ #[inline]
+ fn into(self) -> mint::Vector2<T> {
+ mint::Vector2 {
+ x: self.width,
+ y: self.height,
+ }
+ }
+}
+
+impl<T, U> From<Vector2D<T, U>> for Size2D<T, U> {
+ #[inline]
+ fn from(v: Vector2D<T, U>) -> Self {
+ size2(v.x, v.y)
+ }
+}
+
+impl<T, U> Into<[T; 2]> for Size2D<T, U> {
+ #[inline]
+ fn into(self) -> [T; 2] {
+ [self.width, self.height]
+ }
+}
+
+impl<T, U> From<[T; 2]> for Size2D<T, U> {
+ #[inline]
+ fn from([w, h]: [T; 2]) -> Self {
+ size2(w, h)
+ }
+}
+
+impl<T, U> Into<(T, T)> for Size2D<T, U> {
+ #[inline]
+ fn into(self) -> (T, T) {
+ (self.width, self.height)
+ }
+}
+
+impl<T, U> From<(T, T)> for Size2D<T, U> {
+ #[inline]
+ fn from(tuple: (T, T)) -> Self {
+ size2(tuple.0, tuple.1)
+ }
+}
+
+#[cfg(test)]
+mod size2d {
+ use crate::default::Size2D;
+ #[cfg(feature = "mint")]
+ use mint;
+
+ #[test]
+ pub fn test_area() {
+ let p = Size2D::new(1.5, 2.0);
+ assert_eq!(p.area(), 3.0);
+ }
+
+ #[cfg(feature = "mint")]
+ #[test]
+ pub fn test_mint() {
+ let s1 = Size2D::new(1.0, 2.0);
+ let sm: mint::Vector2<_> = s1.into();
+ let s2 = Size2D::from(sm);
+
+ assert_eq!(s1, s2);
+ }
+
+ mod ops {
+ use crate::default::Size2D;
+ use crate::scale::Scale;
+
+ pub enum Mm {}
+ pub enum Cm {}
+
+ pub type Size2DMm<T> = crate::Size2D<T, Mm>;
+ pub type Size2DCm<T> = crate::Size2D<T, Cm>;
+
+ #[test]
+ pub fn test_neg() {
+ assert_eq!(-Size2D::new(1.0, 2.0), Size2D::new(-1.0, -2.0));
+ assert_eq!(-Size2D::new(0.0, 0.0), Size2D::new(-0.0, -0.0));
+ assert_eq!(-Size2D::new(-1.0, -2.0), Size2D::new(1.0, 2.0));
+ }
+
+ #[test]
+ pub fn test_add() {
+ let s1 = Size2D::new(1.0, 2.0);
+ let s2 = Size2D::new(3.0, 4.0);
+ assert_eq!(s1 + s2, Size2D::new(4.0, 6.0));
+ assert_eq!(s1 + &s2, Size2D::new(4.0, 6.0));
+
+ let s1 = Size2D::new(1.0, 2.0);
+ let s2 = Size2D::new(0.0, 0.0);
+ assert_eq!(s1 + s2, Size2D::new(1.0, 2.0));
+ assert_eq!(s1 + &s2, Size2D::new(1.0, 2.0));
+
+ let s1 = Size2D::new(1.0, 2.0);
+ let s2 = Size2D::new(-3.0, -4.0);
+ assert_eq!(s1 + s2, Size2D::new(-2.0, -2.0));
+ assert_eq!(s1 + &s2, Size2D::new(-2.0, -2.0));
+
+ let s1 = Size2D::new(0.0, 0.0);
+ let s2 = Size2D::new(0.0, 0.0);
+ assert_eq!(s1 + s2, Size2D::new(0.0, 0.0));
+ assert_eq!(s1 + &s2, Size2D::new(0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_add_assign() {
+ let mut s = Size2D::new(1.0, 2.0);
+ s += Size2D::new(3.0, 4.0);
+ assert_eq!(s, Size2D::new(4.0, 6.0));
+
+ let mut s = Size2D::new(1.0, 2.0);
+ s += Size2D::new(0.0, 0.0);
+ assert_eq!(s, Size2D::new(1.0, 2.0));
+
+ let mut s = Size2D::new(1.0, 2.0);
+ s += Size2D::new(-3.0, -4.0);
+ assert_eq!(s, Size2D::new(-2.0, -2.0));
+
+ let mut s = Size2D::new(0.0, 0.0);
+ s += Size2D::new(0.0, 0.0);
+ assert_eq!(s, Size2D::new(0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_sum() {
+ let sizes = [
+ Size2D::new(0.0, 1.0),
+ Size2D::new(1.0, 2.0),
+ Size2D::new(2.0, 3.0)
+ ];
+ let sum = Size2D::new(3.0, 6.0);
+ assert_eq!(sizes.iter().sum::<Size2D<_>>(), sum);
+ }
+
+ #[test]
+ pub fn test_sub() {
+ let s1 = Size2D::new(1.0, 2.0);
+ let s2 = Size2D::new(3.0, 4.0);
+ assert_eq!(s1 - s2, Size2D::new(-2.0, -2.0));
+
+ let s1 = Size2D::new(1.0, 2.0);
+ let s2 = Size2D::new(0.0, 0.0);
+ assert_eq!(s1 - s2, Size2D::new(1.0, 2.0));
+
+ let s1 = Size2D::new(1.0, 2.0);
+ let s2 = Size2D::new(-3.0, -4.0);
+ assert_eq!(s1 - s2, Size2D::new(4.0, 6.0));
+
+ let s1 = Size2D::new(0.0, 0.0);
+ let s2 = Size2D::new(0.0, 0.0);
+ assert_eq!(s1 - s2, Size2D::new(0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_sub_assign() {
+ let mut s = Size2D::new(1.0, 2.0);
+ s -= Size2D::new(3.0, 4.0);
+ assert_eq!(s, Size2D::new(-2.0, -2.0));
+
+ let mut s = Size2D::new(1.0, 2.0);
+ s -= Size2D::new(0.0, 0.0);
+ assert_eq!(s, Size2D::new(1.0, 2.0));
+
+ let mut s = Size2D::new(1.0, 2.0);
+ s -= Size2D::new(-3.0, -4.0);
+ assert_eq!(s, Size2D::new(4.0, 6.0));
+
+ let mut s = Size2D::new(0.0, 0.0);
+ s -= Size2D::new(0.0, 0.0);
+ assert_eq!(s, Size2D::new(0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_mul_scalar() {
+ let s1: Size2D<f32> = Size2D::new(3.0, 5.0);
+
+ let result = s1 * 5.0;
+
+ assert_eq!(result, Size2D::new(15.0, 25.0));
+ }
+
+ #[test]
+ pub fn test_mul_assign_scalar() {
+ let mut s1 = Size2D::new(3.0, 5.0);
+
+ s1 *= 5.0;
+
+ assert_eq!(s1, Size2D::new(15.0, 25.0));
+ }
+
+ #[test]
+ pub fn test_mul_scale() {
+ let s1 = Size2DMm::new(1.0, 2.0);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = s1 * cm_per_mm;
+
+ assert_eq!(result, Size2DCm::new(0.1, 0.2));
+ }
+
+ #[test]
+ pub fn test_mul_assign_scale() {
+ let mut s1 = Size2DMm::new(1.0, 2.0);
+ let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
+
+ s1 *= scale;
+
+ assert_eq!(s1, Size2DMm::new(0.1, 0.2));
+ }
+
+ #[test]
+ pub fn test_div_scalar() {
+ let s1: Size2D<f32> = Size2D::new(15.0, 25.0);
+
+ let result = s1 / 5.0;
+
+ assert_eq!(result, Size2D::new(3.0, 5.0));
+ }
+
+ #[test]
+ pub fn test_div_assign_scalar() {
+ let mut s1: Size2D<f32> = Size2D::new(15.0, 25.0);
+
+ s1 /= 5.0;
+
+ assert_eq!(s1, Size2D::new(3.0, 5.0));
+ }
+
+ #[test]
+ pub fn test_div_scale() {
+ let s1 = Size2DCm::new(0.1, 0.2);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = s1 / cm_per_mm;
+
+ assert_eq!(result, Size2DMm::new(1.0, 2.0));
+ }
+
+ #[test]
+ pub fn test_div_assign_scale() {
+ let mut s1 = Size2DMm::new(0.1, 0.2);
+ let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
+
+ s1 /= scale;
+
+ assert_eq!(s1, Size2DMm::new(1.0, 2.0));
+ }
+
+ #[test]
+ pub fn test_nan_empty() {
+ use std::f32::NAN;
+ assert!(Size2D::new(NAN, 2.0).is_empty());
+ assert!(Size2D::new(0.0, NAN).is_empty());
+ assert!(Size2D::new(NAN, -2.0).is_empty());
+ }
+ }
+}
+
+/// A 3d size tagged with a unit.
+#[repr(C)]
+pub struct Size3D<T, U> {
+ /// The extent of the element in the `U` units along the `x` axis.
+ pub width: T,
+ /// The extent of the element in the `U` units along the `y` axis.
+ pub height: T,
+ /// The extent of the element in the `U` units along the `z` axis.
+ pub depth: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<U>,
+}
+
+impl<T: Copy, U> Copy for Size3D<T, U> {}
+
+impl<T: Clone, U> Clone for Size3D<T, U> {
+ fn clone(&self) -> Self {
+ Size3D {
+ width: self.width.clone(),
+ height: self.height.clone(),
+ depth: self.depth.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de, T, U> serde::Deserialize<'de> for Size3D<T, U>
+where
+ T: serde::Deserialize<'de>,
+{
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let (width, height, depth) = serde::Deserialize::deserialize(deserializer)?;
+ Ok(Size3D {
+ width,
+ height,
+ depth,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<T, U> serde::Serialize for Size3D<T, U>
+where
+ T: serde::Serialize,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ (&self.width, &self.height, &self.depth).serialize(serializer)
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for Size3D<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for Size3D<T, U> {}
+
+impl<T, U> Eq for Size3D<T, U> where T: Eq {}
+
+impl<T, U> PartialEq for Size3D<T, U>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.width == other.width && self.height == other.height && self.depth == other.depth
+ }
+}
+
+impl<T, U> Hash for Size3D<T, U>
+where
+ T: Hash,
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.width.hash(h);
+ self.height.hash(h);
+ self.depth.hash(h);
+ }
+}
+
+impl<T: fmt::Debug, U> fmt::Debug for Size3D<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(&self.width, f)?;
+ write!(f, "x")?;
+ fmt::Debug::fmt(&self.height, f)?;
+ write!(f, "x")?;
+ fmt::Debug::fmt(&self.depth, f)
+ }
+}
+
+impl<T: Default, U> Default for Size3D<T, U> {
+ fn default() -> Self {
+ Size3D::new(Default::default(), Default::default(), Default::default())
+ }
+}
+
+impl<T, U> Size3D<T, U> {
+ /// The same as [`Zero::zero()`] but available without importing trait.
+ ///
+ /// [`Zero::zero()`]: ./num/trait.Zero.html#tymethod.zero
+ pub fn zero() -> Self
+ where
+ T: Zero,
+ {
+ Size3D::new(Zero::zero(), Zero::zero(), Zero::zero())
+ }
+
+ /// Constructor taking scalar values.
+ #[inline]
+ pub const fn new(width: T, height: T, depth: T) -> Self {
+ Size3D {
+ width,
+ height,
+ depth,
+ _unit: PhantomData,
+ }
+ }
+ /// Constructor taking scalar strongly typed lengths.
+ #[inline]
+ pub fn from_lengths(width: Length<T, U>, height: Length<T, U>, depth: Length<T, U>) -> Self {
+ Size3D::new(width.0, height.0, depth.0)
+ }
+
+ /// Constructor setting all components to the same value.
+ #[inline]
+ pub fn splat(v: T) -> Self
+ where
+ T: Clone,
+ {
+ Size3D {
+ width: v.clone(),
+ height: v.clone(),
+ depth: v,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(p: Size3D<T, UnknownUnit>) -> Self {
+ Size3D::new(p.width, p.height, p.depth)
+ }
+}
+
+impl<T: Copy, U> Size3D<T, U> {
+ /// Return this size as an array of three elements (width, then height, then depth).
+ #[inline]
+ pub fn to_array(self) -> [T; 3] {
+ [self.width, self.height, self.depth]
+ }
+
+ /// Return this size as an array of three elements (width, then height, then depth).
+ #[inline]
+ pub fn to_tuple(self) -> (T, T, T) {
+ (self.width, self.height, self.depth)
+ }
+
+ /// Return this size as a vector with width, height and depth.
+ #[inline]
+ pub fn to_vector(self) -> Vector3D<T, U> {
+ vec3(self.width, self.height, self.depth)
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(self) -> Size3D<T, UnknownUnit> {
+ self.cast_unit()
+ }
+
+ /// Cast the unit
+ #[inline]
+ pub fn cast_unit<V>(self) -> Size3D<T, V> {
+ Size3D::new(self.width, self.height, self.depth)
+ }
+
+ /// Rounds each component to the nearest integer value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::size3;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(size3::<_, Mm>(-0.1, -0.8, 0.4).round(), size3::<_, Mm>(0.0, -1.0, 0.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn round(self) -> Self
+ where
+ T: Round,
+ {
+ Size3D::new(self.width.round(), self.height.round(), self.depth.round())
+ }
+
+ /// Rounds each component to the smallest integer equal or greater than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::size3;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(size3::<_, Mm>(-0.1, -0.8, 0.4).ceil(), size3::<_, Mm>(0.0, 0.0, 1.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn ceil(self) -> Self
+ where
+ T: Ceil,
+ {
+ Size3D::new(self.width.ceil(), self.height.ceil(), self.depth.ceil())
+ }
+
+ /// Rounds each component to the biggest integer equal or lower than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::size3;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(size3::<_, Mm>(-0.1, -0.8, 0.4).floor(), size3::<_, Mm>(-1.0, -1.0, 0.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn floor(self) -> Self
+ where
+ T: Floor,
+ {
+ Size3D::new(self.width.floor(), self.height.floor(), self.depth.floor())
+ }
+
+ /// Returns result of multiplication of all components
+ pub fn volume(self) -> T
+ where
+ T: Mul<Output = T>,
+ {
+ self.width * self.height * self.depth
+ }
+
+ /// Linearly interpolate between this size and another size.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::size3;
+ /// use euclid::default::Size3D;
+ ///
+ /// let from: Size3D<_> = size3(0.0, 10.0, -1.0);
+ /// let to: Size3D<_> = size3(8.0, -4.0, 0.0);
+ ///
+ /// assert_eq!(from.lerp(to, -1.0), size3(-8.0, 24.0, -2.0));
+ /// assert_eq!(from.lerp(to, 0.0), size3( 0.0, 10.0, -1.0));
+ /// assert_eq!(from.lerp(to, 0.5), size3( 4.0, 3.0, -0.5));
+ /// assert_eq!(from.lerp(to, 1.0), size3( 8.0, -4.0, 0.0));
+ /// assert_eq!(from.lerp(to, 2.0), size3(16.0, -18.0, 1.0));
+ /// ```
+ #[inline]
+ pub fn lerp(self, other: Self, t: T) -> Self
+ where
+ T: One + Sub<Output = T> + Mul<Output = T> + Add<Output = T>,
+ {
+ let one_t = T::one() - t;
+ self * one_t + other * t
+ }
+}
+
+impl<T: NumCast + Copy, U> Size3D<T, U> {
+ /// Cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ #[inline]
+ pub fn cast<NewT: NumCast>(self) -> Size3D<NewT, U> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating point to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ pub fn try_cast<NewT: NumCast>(self) -> Option<Size3D<NewT, U>> {
+ match (
+ NumCast::from(self.width),
+ NumCast::from(self.height),
+ NumCast::from(self.depth),
+ ) {
+ (Some(w), Some(h), Some(d)) => Some(Size3D::new(w, h, d)),
+ _ => None,
+ }
+ }
+
+ // Convenience functions for common casts
+
+ /// Cast into an `f32` size.
+ #[inline]
+ pub fn to_f32(self) -> Size3D<f32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `f64` size.
+ #[inline]
+ pub fn to_f64(self) -> Size3D<f64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `uint` size, truncating decimals if any.
+ ///
+ /// When casting from floating point sizes, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_usize(self) -> Size3D<usize, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u32` size, truncating decimals if any.
+ ///
+ /// When casting from floating point sizes, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_u32(self) -> Size3D<u32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i32` size, truncating decimals if any.
+ ///
+ /// When casting from floating point sizes, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i32(self) -> Size3D<i32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i64` size, truncating decimals if any.
+ ///
+ /// When casting from floating point sizes, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i64(self) -> Size3D<i64, U> {
+ self.cast()
+ }
+}
+
+impl<T: Float, U> Size3D<T, U> {
+ /// Returns true if all members are finite.
+ #[inline]
+ pub fn is_finite(self) -> bool {
+ self.width.is_finite() && self.height.is_finite() && self.depth.is_finite()
+ }
+}
+
+impl<T: Signed, U> Size3D<T, U> {
+ /// Computes the absolute value of each component.
+ ///
+ /// For `f32` and `f64`, `NaN` will be returned for component if the component is `NaN`.
+ ///
+ /// For signed integers, `::MIN` will be returned for component if the component is `::MIN`.
+ pub fn abs(self) -> Self {
+ size3(self.width.abs(), self.height.abs(), self.depth.abs())
+ }
+
+ /// Returns `true` if all components is positive and `false` any component is zero or negative.
+ pub fn is_positive(self) -> bool {
+ self.width.is_positive() && self.height.is_positive() && self.depth.is_positive()
+ }
+}
+
+impl<T: PartialOrd, U> Size3D<T, U> {
+ /// Returns the size each component of which are minimum of this size and another.
+ #[inline]
+ pub fn min(self, other: Self) -> Self {
+ size3(
+ min(self.width, other.width),
+ min(self.height, other.height),
+ min(self.depth, other.depth),
+ )
+ }
+
+ /// Returns the size each component of which are maximum of this size and another.
+ #[inline]
+ pub fn max(self, other: Self) -> Self {
+ size3(
+ max(self.width, other.width),
+ max(self.height, other.height),
+ max(self.depth, other.depth),
+ )
+ }
+
+ /// Returns the size each component of which clamped by corresponding
+ /// components of `start` and `end`.
+ ///
+ /// Shortcut for `self.max(start).min(end)`.
+ #[inline]
+ pub fn clamp(self, start: Self, end: Self) -> Self
+ where
+ T: Copy,
+ {
+ self.max(start).min(end)
+ }
+
+ // Returns true if this size is larger or equal to the other size in all dimensions.
+ #[inline]
+ pub fn contains(self, other: Self) -> bool {
+ self.width >= other.width && self.height >= other.height && self.depth >= other.depth
+ }
+
+
+ /// Returns vector with results of "greater than" operation on each component.
+ pub fn greater_than(self, other: Self) -> BoolVector3D {
+ BoolVector3D {
+ x: self.width > other.width,
+ y: self.height > other.height,
+ z: self.depth > other.depth,
+ }
+ }
+
+ /// Returns vector with results of "lower than" operation on each component.
+ pub fn lower_than(self, other: Self) -> BoolVector3D {
+ BoolVector3D {
+ x: self.width < other.width,
+ y: self.height < other.height,
+ z: self.depth < other.depth,
+ }
+ }
+
+ /// Returns `true` if any component of size is zero, negative or NaN.
+ pub fn is_empty(self) -> bool
+ where
+ T: Zero,
+ {
+ let zero = T::zero();
+ !(self.width > zero && self.height > zero && self.depth <= zero)
+ }
+}
+
+impl<T: PartialEq, U> Size3D<T, U> {
+ /// Returns vector with results of "equal" operation on each component.
+ pub fn equal(self, other: Self) -> BoolVector3D {
+ BoolVector3D {
+ x: self.width == other.width,
+ y: self.height == other.height,
+ z: self.depth == other.depth,
+ }
+ }
+
+ /// Returns vector with results of "not equal" operation on each component.
+ pub fn not_equal(self, other: Self) -> BoolVector3D {
+ BoolVector3D {
+ x: self.width != other.width,
+ y: self.height != other.height,
+ z: self.depth != other.depth,
+ }
+ }
+}
+
+impl<T: Round, U> Round for Size3D<T, U> {
+ /// See [`Size3D::round()`](#method.round).
+ #[inline]
+ fn round(self) -> Self {
+ self.round()
+ }
+}
+
+impl<T: Ceil, U> Ceil for Size3D<T, U> {
+ /// See [`Size3D::ceil()`](#method.ceil).
+ #[inline]
+ fn ceil(self) -> Self {
+ self.ceil()
+ }
+}
+
+impl<T: Floor, U> Floor for Size3D<T, U> {
+ /// See [`Size3D::floor()`](#method.floor).
+ #[inline]
+ fn floor(self) -> Self {
+ self.floor()
+ }
+}
+
+impl<T: Zero, U> Zero for Size3D<T, U> {
+ #[inline]
+ fn zero() -> Self {
+ Size3D::new(Zero::zero(), Zero::zero(), Zero::zero())
+ }
+}
+
+impl<T: Neg, U> Neg for Size3D<T, U> {
+ type Output = Size3D<T::Output, U>;
+
+ #[inline]
+ fn neg(self) -> Self::Output {
+ Size3D::new(-self.width, -self.height, -self.depth)
+ }
+}
+
+impl<T: Add, U> Add for Size3D<T, U> {
+ type Output = Size3D<T::Output, U>;
+
+ #[inline]
+ fn add(self, other: Self) -> Self::Output {
+ Size3D::new(
+ self.width + other.width,
+ self.height + other.height,
+ self.depth + other.depth,
+ )
+ }
+}
+
+impl<T: Copy + Add<T, Output = T>, U> Add<&Self> for Size3D<T, U> {
+ type Output = Self;
+ fn add(self, other: &Self) -> Self {
+ Size3D::new(
+ self.width + other.width,
+ self.height + other.height,
+ self.depth + other.depth,
+ )
+ }
+}
+
+impl<T: Add<Output = T> + Zero, U> Sum for Size3D<T, U> {
+ fn sum<I: Iterator<Item=Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+impl<'a, T: 'a + Add<Output = T> + Copy + Zero, U: 'a> Sum<&'a Self> for Size3D<T, U> {
+ fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+impl<T: AddAssign, U> AddAssign for Size3D<T, U> {
+ #[inline]
+ fn add_assign(&mut self, other: Self) {
+ self.width += other.width;
+ self.height += other.height;
+ self.depth += other.depth;
+ }
+}
+
+impl<T: Sub, U> Sub for Size3D<T, U> {
+ type Output = Size3D<T::Output, U>;
+
+ #[inline]
+ fn sub(self, other: Self) -> Self::Output {
+ Size3D::new(
+ self.width - other.width,
+ self.height - other.height,
+ self.depth - other.depth,
+ )
+ }
+}
+
+impl<T: SubAssign, U> SubAssign for Size3D<T, U> {
+ #[inline]
+ fn sub_assign(&mut self, other: Self) {
+ self.width -= other.width;
+ self.height -= other.height;
+ self.depth -= other.depth;
+ }
+}
+
+impl<T: Copy + Mul, U> Mul<T> for Size3D<T, U> {
+ type Output = Size3D<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ Size3D::new(
+ self.width * scale,
+ self.height * scale,
+ self.depth * scale,
+ )
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<T> for Size3D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, other: T) {
+ self.width *= other;
+ self.height *= other;
+ self.depth *= other;
+ }
+}
+
+impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Size3D<T, U1> {
+ type Output = Size3D<T::Output, U2>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ Size3D::new(
+ self.width * scale.0,
+ self.height * scale.0,
+ self.depth * scale.0,
+ )
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Size3D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, other: Scale<T, U, U>) {
+ *self *= other.0;
+ }
+}
+
+impl<T: Copy + Div, U> Div<T> for Size3D<T, U> {
+ type Output = Size3D<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ Size3D::new(
+ self.width / scale,
+ self.height / scale,
+ self.depth / scale,
+ )
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<T> for Size3D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, other: T) {
+ self.width /= other;
+ self.height /= other;
+ self.depth /= other;
+ }
+}
+
+impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Size3D<T, U2> {
+ type Output = Size3D<T::Output, U1>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ Size3D::new(
+ self.width / scale.0,
+ self.height / scale.0,
+ self.depth / scale.0,
+ )
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Size3D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, other: Scale<T, U, U>) {
+ *self /= other.0;
+ }
+}
+
+#[cfg(feature = "mint")]
+impl<T, U> From<mint::Vector3<T>> for Size3D<T, U> {
+ #[inline]
+ fn from(v: mint::Vector3<T>) -> Self {
+ size3(v.x, v.y, v.z)
+ }
+}
+#[cfg(feature = "mint")]
+impl<T, U> Into<mint::Vector3<T>> for Size3D<T, U> {
+ #[inline]
+ fn into(self) -> mint::Vector3<T> {
+ mint::Vector3 {
+ x: self.width,
+ y: self.height,
+ z: self.depth,
+ }
+ }
+}
+
+impl<T, U> From<Vector3D<T, U>> for Size3D<T, U> {
+ #[inline]
+ fn from(v: Vector3D<T, U>) -> Self {
+ size3(v.x, v.y, v.z)
+ }
+}
+
+impl<T, U> Into<[T; 3]> for Size3D<T, U> {
+ #[inline]
+ fn into(self) -> [T; 3] {
+ [self.width, self.height, self.depth]
+ }
+}
+
+impl<T, U> From<[T; 3]> for Size3D<T, U> {
+ #[inline]
+ fn from([w, h, d]: [T; 3]) -> Self {
+ size3(w, h, d)
+ }
+}
+
+impl<T, U> Into<(T, T, T)> for Size3D<T, U> {
+ #[inline]
+ fn into(self) -> (T, T, T) {
+ (self.width, self.height, self.depth)
+ }
+}
+
+impl<T, U> From<(T, T, T)> for Size3D<T, U> {
+ #[inline]
+ fn from(tuple: (T, T, T)) -> Self {
+ size3(tuple.0, tuple.1, tuple.2)
+ }
+}
+
+/// Shorthand for `Size3D::new(w, h, d)`.
+#[inline]
+pub const fn size3<T, U>(w: T, h: T, d: T) -> Size3D<T, U> {
+ Size3D::new(w, h, d)
+}
+
+#[cfg(test)]
+mod size3d {
+ mod ops {
+ use crate::default::Size3D;
+ use crate::scale::Scale;
+
+ pub enum Mm {}
+ pub enum Cm {}
+
+ pub type Size3DMm<T> = crate::Size3D<T, Mm>;
+ pub type Size3DCm<T> = crate::Size3D<T, Cm>;
+
+ #[test]
+ pub fn test_neg() {
+ assert_eq!(-Size3D::new(1.0, 2.0, 3.0), Size3D::new(-1.0, -2.0, -3.0));
+ assert_eq!(-Size3D::new(0.0, 0.0, 0.0), Size3D::new(-0.0, -0.0, -0.0));
+ assert_eq!(-Size3D::new(-1.0, -2.0, -3.0), Size3D::new(1.0, 2.0, 3.0));
+ }
+
+ #[test]
+ pub fn test_add() {
+ let s1 = Size3D::new(1.0, 2.0, 3.0);
+ let s2 = Size3D::new(4.0, 5.0, 6.0);
+ assert_eq!(s1 + s2, Size3D::new(5.0, 7.0, 9.0));
+ assert_eq!(s1 + &s2, Size3D::new(5.0, 7.0, 9.0));
+
+ let s1 = Size3D::new(1.0, 2.0, 3.0);
+ let s2 = Size3D::new(0.0, 0.0, 0.0);
+ assert_eq!(s1 + s2, Size3D::new(1.0, 2.0, 3.0));
+ assert_eq!(s1 + &s2, Size3D::new(1.0, 2.0, 3.0));
+
+ let s1 = Size3D::new(1.0, 2.0, 3.0);
+ let s2 = Size3D::new(-4.0, -5.0, -6.0);
+ assert_eq!(s1 + s2, Size3D::new(-3.0, -3.0, -3.0));
+ assert_eq!(s1 + &s2, Size3D::new(-3.0, -3.0, -3.0));
+
+ let s1 = Size3D::new(0.0, 0.0, 0.0);
+ let s2 = Size3D::new(0.0, 0.0, 0.0);
+ assert_eq!(s1 + s2, Size3D::new(0.0, 0.0, 0.0));
+ assert_eq!(s1 + &s2, Size3D::new(0.0, 0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_sum() {
+ let sizes = [
+ Size3D::new(0.0, 1.0, 2.0),
+ Size3D::new(1.0, 2.0, 3.0),
+ Size3D::new(2.0, 3.0, 4.0)
+ ];
+ let sum = Size3D::new(3.0, 6.0, 9.0);
+ assert_eq!(sizes.iter().sum::<Size3D<_>>(), sum);
+ }
+
+ #[test]
+ pub fn test_add_assign() {
+ let mut s = Size3D::new(1.0, 2.0, 3.0);
+ s += Size3D::new(4.0, 5.0, 6.0);
+ assert_eq!(s, Size3D::new(5.0, 7.0, 9.0));
+
+ let mut s = Size3D::new(1.0, 2.0, 3.0);
+ s += Size3D::new(0.0, 0.0, 0.0);
+ assert_eq!(s, Size3D::new(1.0, 2.0, 3.0));
+
+ let mut s = Size3D::new(1.0, 2.0, 3.0);
+ s += Size3D::new(-4.0, -5.0, -6.0);
+ assert_eq!(s, Size3D::new(-3.0, -3.0, -3.0));
+
+ let mut s = Size3D::new(0.0, 0.0, 0.0);
+ s += Size3D::new(0.0, 0.0, 0.0);
+ assert_eq!(s, Size3D::new(0.0, 0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_sub() {
+ let s1 = Size3D::new(1.0, 2.0, 3.0);
+ let s2 = Size3D::new(4.0, 5.0, 6.0);
+ assert_eq!(s1 - s2, Size3D::new(-3.0, -3.0, -3.0));
+
+ let s1 = Size3D::new(1.0, 2.0, 3.0);
+ let s2 = Size3D::new(0.0, 0.0, 0.0);
+ assert_eq!(s1 - s2, Size3D::new(1.0, 2.0, 3.0));
+
+ let s1 = Size3D::new(1.0, 2.0, 3.0);
+ let s2 = Size3D::new(-4.0, -5.0, -6.0);
+ assert_eq!(s1 - s2, Size3D::new(5.0, 7.0, 9.0));
+
+ let s1 = Size3D::new(0.0, 0.0, 0.0);
+ let s2 = Size3D::new(0.0, 0.0, 0.0);
+ assert_eq!(s1 - s2, Size3D::new(0.0, 0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_sub_assign() {
+ let mut s = Size3D::new(1.0, 2.0, 3.0);
+ s -= Size3D::new(4.0, 5.0, 6.0);
+ assert_eq!(s, Size3D::new(-3.0, -3.0, -3.0));
+
+ let mut s = Size3D::new(1.0, 2.0, 3.0);
+ s -= Size3D::new(0.0, 0.0, 0.0);
+ assert_eq!(s, Size3D::new(1.0, 2.0, 3.0));
+
+ let mut s = Size3D::new(1.0, 2.0, 3.0);
+ s -= Size3D::new(-4.0, -5.0, -6.0);
+ assert_eq!(s, Size3D::new(5.0, 7.0, 9.0));
+
+ let mut s = Size3D::new(0.0, 0.0, 0.0);
+ s -= Size3D::new(0.0, 0.0, 0.0);
+ assert_eq!(s, Size3D::new(0.0, 0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_mul_scalar() {
+ let s1: Size3D<f32> = Size3D::new(3.0, 5.0, 7.0);
+
+ let result = s1 * 5.0;
+
+ assert_eq!(result, Size3D::new(15.0, 25.0, 35.0));
+ }
+
+ #[test]
+ pub fn test_mul_assign_scalar() {
+ let mut s1: Size3D<f32> = Size3D::new(3.0, 5.0, 7.0);
+
+ s1 *= 5.0;
+
+ assert_eq!(s1, Size3D::new(15.0, 25.0, 35.0));
+ }
+
+ #[test]
+ pub fn test_mul_scale() {
+ let s1 = Size3DMm::new(1.0, 2.0, 3.0);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = s1 * cm_per_mm;
+
+ assert_eq!(result, Size3DCm::new(0.1, 0.2, 0.3));
+ }
+
+ #[test]
+ pub fn test_mul_assign_scale() {
+ let mut s1 = Size3DMm::new(1.0, 2.0, 3.0);
+ let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
+
+ s1 *= scale;
+
+ assert_eq!(s1, Size3DMm::new(0.1, 0.2, 0.3));
+ }
+
+ #[test]
+ pub fn test_div_scalar() {
+ let s1: Size3D<f32> = Size3D::new(15.0, 25.0, 35.0);
+
+ let result = s1 / 5.0;
+
+ assert_eq!(result, Size3D::new(3.0, 5.0, 7.0));
+ }
+
+ #[test]
+ pub fn test_div_assign_scalar() {
+ let mut s1: Size3D<f32> = Size3D::new(15.0, 25.0, 35.0);
+
+ s1 /= 5.0;
+
+ assert_eq!(s1, Size3D::new(3.0, 5.0, 7.0));
+ }
+
+ #[test]
+ pub fn test_div_scale() {
+ let s1 = Size3DCm::new(0.1, 0.2, 0.3);
+ let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
+
+ let result = s1 / cm_per_mm;
+
+ assert_eq!(result, Size3DMm::new(1.0, 2.0, 3.0));
+ }
+
+ #[test]
+ pub fn test_div_assign_scale() {
+ let mut s1 = Size3DMm::new(0.1, 0.2, 0.3);
+ let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
+
+ s1 /= scale;
+
+ assert_eq!(s1, Size3DMm::new(1.0, 2.0, 3.0));
+ }
+
+ #[test]
+ pub fn test_nan_empty() {
+ use std::f32::NAN;
+ assert!(Size3D::new(NAN, 2.0, 3.0).is_empty());
+ assert!(Size3D::new(0.0, NAN, 0.0).is_empty());
+ assert!(Size3D::new(1.0, 2.0, NAN).is_empty());
+ }
+ }
+}
diff --git a/third_party/rust/euclid/src/transform2d.rs b/third_party/rust/euclid/src/transform2d.rs
new file mode 100644
index 0000000000..85eb426b6c
--- /dev/null
+++ b/third_party/rust/euclid/src/transform2d.rs
@@ -0,0 +1,809 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![cfg_attr(feature = "cargo-clippy", allow(just_underscores_and_digits))]
+
+use super::{UnknownUnit, Angle};
+#[cfg(feature = "mint")]
+use mint;
+use crate::num::{One, Zero};
+use crate::point::{Point2D, point2};
+use crate::vector::{Vector2D, vec2};
+use crate::rect::Rect;
+use crate::box2d::Box2D;
+use crate::transform3d::Transform3D;
+use core::ops::{Add, Mul, Div, Sub};
+use core::marker::PhantomData;
+use core::cmp::{Eq, PartialEq};
+use core::hash::{Hash};
+use crate::approxeq::ApproxEq;
+use crate::trig::Trig;
+use core::fmt;
+use num_traits::NumCast;
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A 2d transform represented by a column-major 3 by 3 matrix, compressed down to 3 by 2.
+///
+/// Transforms can be parametrized over the source and destination units, to describe a
+/// transformation from a space to another.
+/// For example, `Transform2D<f32, WorldSpace, ScreenSpace>::transform_point4d`
+/// takes a `Point2D<f32, WorldSpace>` and returns a `Point2D<f32, ScreenSpace>`.
+///
+/// Transforms expose a set of convenience methods for pre- and post-transformations.
+/// Pre-transformations (`pre_*` methods) correspond to adding an operation that is
+/// applied before the rest of the transformation, while post-transformations (`then_*`
+/// methods) add an operation that is applied after.
+///
+/// The matrix representation is conceptually equivalent to a 3 by 3 matrix transformation
+/// compressed to 3 by 2 with the components that aren't needed to describe the set of 2d
+/// transformations we are interested in implicitly defined:
+///
+/// ```text
+/// | m11 m12 0 | |x| |x'|
+/// | m21 m22 0 | x |y| = |y'|
+/// | m31 m32 1 | |1| |w |
+/// ```
+///
+/// When translating Transform2D into general matrix representations, consider that the
+/// representation follows the column-major notation with column vectors.
+///
+/// The translation terms are m31 and m32.
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+ feature = "serde",
+ serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
+)]
+pub struct Transform2D<T, Src, Dst> {
+ pub m11: T, pub m12: T,
+ pub m21: T, pub m22: T,
+ pub m31: T, pub m32: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<(Src, Dst)>,
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Transform2D<T, Src, Dst>
+where
+ T: arbitrary::Arbitrary<'a>,
+{
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>
+ {
+ let (m11, m12, m21, m22, m31, m32) = arbitrary::Arbitrary::arbitrary(u)?;
+ Ok(Transform2D {
+ m11, m12, m21, m22, m31, m32,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, Src, Dst> Zeroable for Transform2D<T, Src, Dst> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Transform2D<T, Src, Dst> {}
+
+impl<T: Copy, Src, Dst> Copy for Transform2D<T, Src, Dst> {}
+
+impl<T: Clone, Src, Dst> Clone for Transform2D<T, Src, Dst> {
+ fn clone(&self) -> Self {
+ Transform2D {
+ m11: self.m11.clone(),
+ m12: self.m12.clone(),
+ m21: self.m21.clone(),
+ m22: self.m22.clone(),
+ m31: self.m31.clone(),
+ m32: self.m32.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+impl<T, Src, Dst> Eq for Transform2D<T, Src, Dst> where T: Eq {}
+
+impl<T, Src, Dst> PartialEq for Transform2D<T, Src, Dst>
+ where T: PartialEq
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.m11 == other.m11 &&
+ self.m12 == other.m12 &&
+ self.m21 == other.m21 &&
+ self.m22 == other.m22 &&
+ self.m31 == other.m31 &&
+ self.m32 == other.m32
+ }
+}
+
+impl<T, Src, Dst> Hash for Transform2D<T, Src, Dst>
+ where T: Hash
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.m11.hash(h);
+ self.m12.hash(h);
+ self.m21.hash(h);
+ self.m22.hash(h);
+ self.m31.hash(h);
+ self.m32.hash(h);
+ }
+}
+
+
+impl<T, Src, Dst> Transform2D<T, Src, Dst> {
+ /// Create a transform specifying its components in using the column-major-column-vector
+ /// matrix notation.
+ ///
+ /// For example, the translation terms m31 and m32 are the last two parameters parameters.
+ ///
+ /// ```
+ /// use euclid::default::Transform2D;
+ /// let tx = 1.0;
+ /// let ty = 2.0;
+ /// let translation = Transform2D::new(
+ /// 1.0, 0.0,
+ /// 0.0, 1.0,
+ /// tx, ty,
+ /// );
+ /// ```
+ pub const fn new(m11: T, m12: T, m21: T, m22: T, m31: T, m32: T) -> Self {
+ Transform2D {
+ m11, m12,
+ m21, m22,
+ m31, m32,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Returns true is this transform is approximately equal to the other one, using
+ /// T's default epsilon value.
+ ///
+ /// The same as [`ApproxEq::approx_eq()`] but available without importing trait.
+ ///
+ /// [`ApproxEq::approx_eq()`]: ./approxeq/trait.ApproxEq.html#method.approx_eq
+ #[inline]
+ pub fn approx_eq(&self, other: &Self) -> bool
+ where T : ApproxEq<T> {
+ <Self as ApproxEq<T>>::approx_eq(&self, &other)
+ }
+
+ /// Returns true is this transform is approximately equal to the other one, using
+ /// a provided epsilon value.
+ ///
+ /// The same as [`ApproxEq::approx_eq_eps()`] but available without importing trait.
+ ///
+ /// [`ApproxEq::approx_eq_eps()`]: ./approxeq/trait.ApproxEq.html#method.approx_eq_eps
+ #[inline]
+ pub fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool
+ where T : ApproxEq<T> {
+ <Self as ApproxEq<T>>::approx_eq_eps(&self, &other, &eps)
+ }
+}
+
+impl<T: Copy, Src, Dst> Transform2D<T, Src, Dst> {
+ /// Returns an array containing this transform's terms.
+ ///
+ /// The terms are laid out in the same order as they are
+ /// specified in `Transform2D::new`, that is following the
+ /// column-major-column-vector matrix notation.
+ ///
+ /// For example the translation terms are found in the
+ /// last two slots of the array.
+ #[inline]
+ pub fn to_array(&self) -> [T; 6] {
+ [
+ self.m11, self.m12,
+ self.m21, self.m22,
+ self.m31, self.m32
+ ]
+ }
+
+ /// Returns an array containing this transform's terms transposed.
+ ///
+ /// The terms are laid out in transposed order from the same order of
+ /// `Transform3D::new` and `Transform3D::to_array`, that is following
+ /// the row-major-column-vector matrix notation.
+ ///
+ /// For example the translation terms are found at indices 2 and 5
+ /// in the array.
+ #[inline]
+ pub fn to_array_transposed(&self) -> [T; 6] {
+ [
+ self.m11, self.m21, self.m31,
+ self.m12, self.m22, self.m32
+ ]
+ }
+
+ /// Equivalent to `to_array` with elements packed two at a time
+ /// in an array of arrays.
+ #[inline]
+ pub fn to_arrays(&self) -> [[T; 2]; 3] {
+ [
+ [self.m11, self.m12],
+ [self.m21, self.m22],
+ [self.m31, self.m32],
+ ]
+ }
+
+ /// Create a transform providing its components via an array
+ /// of 6 elements instead of as individual parameters.
+ ///
+ /// The order of the components corresponds to the
+ /// column-major-column-vector matrix notation (the same order
+ /// as `Transform2D::new`).
+ #[inline]
+ pub fn from_array(array: [T; 6]) -> Self {
+ Self::new(
+ array[0], array[1],
+ array[2], array[3],
+ array[4], array[5],
+ )
+ }
+
+ /// Equivalent to `from_array` with elements packed two at a time
+ /// in an array of arrays.
+ ///
+ /// The order of the components corresponds to the
+ /// column-major-column-vector matrix notation (the same order
+ /// as `Transform3D::new`).
+ #[inline]
+ pub fn from_arrays(array: [[T; 2]; 3]) -> Self {
+ Self::new(
+ array[0][0], array[0][1],
+ array[1][0], array[1][1],
+ array[2][0], array[2][1],
+ )
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(&self) -> Transform2D<T, UnknownUnit, UnknownUnit> {
+ Transform2D::new(
+ self.m11, self.m12,
+ self.m21, self.m22,
+ self.m31, self.m32
+ )
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(p: &Transform2D<T, UnknownUnit, UnknownUnit>) -> Self {
+ Transform2D::new(
+ p.m11, p.m12,
+ p.m21, p.m22,
+ p.m31, p.m32
+ )
+ }
+
+ /// Returns the same transform with a different source unit.
+ #[inline]
+ pub fn with_source<NewSrc>(&self) -> Transform2D<T, NewSrc, Dst> {
+ Transform2D::new(
+ self.m11, self.m12,
+ self.m21, self.m22,
+ self.m31, self.m32,
+ )
+ }
+
+ /// Returns the same transform with a different destination unit.
+ #[inline]
+ pub fn with_destination<NewDst>(&self) -> Transform2D<T, Src, NewDst> {
+ Transform2D::new(
+ self.m11, self.m12,
+ self.m21, self.m22,
+ self.m31, self.m32,
+ )
+ }
+
+ /// Create a 3D transform from the current transform
+ pub fn to_3d(&self) -> Transform3D<T, Src, Dst>
+ where
+ T: Zero + One,
+ {
+ Transform3D::new_2d(self.m11, self.m12, self.m21, self.m22, self.m31, self.m32)
+ }
+}
+
+impl<T: NumCast + Copy, Src, Dst> Transform2D<T, Src, Dst> {
+ /// Cast from one numeric representation to another, preserving the units.
+ #[inline]
+ pub fn cast<NewT: NumCast>(&self) -> Transform2D<NewT, Src, Dst> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ pub fn try_cast<NewT: NumCast>(&self) -> Option<Transform2D<NewT, Src, Dst>> {
+ match (NumCast::from(self.m11), NumCast::from(self.m12),
+ NumCast::from(self.m21), NumCast::from(self.m22),
+ NumCast::from(self.m31), NumCast::from(self.m32)) {
+ (Some(m11), Some(m12),
+ Some(m21), Some(m22),
+ Some(m31), Some(m32)) => {
+ Some(Transform2D::new(
+ m11, m12,
+ m21, m22,
+ m31, m32
+ ))
+ },
+ _ => None
+ }
+ }
+}
+
+impl<T, Src, Dst> Transform2D<T, Src, Dst>
+where
+ T: Zero + One,
+{
+ /// Create an identity matrix:
+ ///
+ /// ```text
+ /// 1 0
+ /// 0 1
+ /// 0 0
+ /// ```
+ #[inline]
+ pub fn identity() -> Self {
+ Self::translation(T::zero(), T::zero())
+ }
+
+ /// Intentional not public, because it checks for exact equivalence
+ /// while most consumers will probably want some sort of approximate
+ /// equivalence to deal with floating-point errors.
+ fn is_identity(&self) -> bool
+ where
+ T: PartialEq,
+ {
+ *self == Self::identity()
+ }
+}
+
+
+/// Methods for combining generic transformations
+impl<T, Src, Dst> Transform2D<T, Src, Dst>
+where
+ T: Copy + Add<Output = T> + Mul<Output = T>,
+{
+ /// Returns the multiplication of the two matrices such that mat's transformation
+ /// applies after self's transformation.
+ #[must_use]
+ pub fn then<NewDst>(&self, mat: &Transform2D<T, Dst, NewDst>) -> Transform2D<T, Src, NewDst> {
+ Transform2D::new(
+ self.m11 * mat.m11 + self.m12 * mat.m21,
+ self.m11 * mat.m12 + self.m12 * mat.m22,
+
+ self.m21 * mat.m11 + self.m22 * mat.m21,
+ self.m21 * mat.m12 + self.m22 * mat.m22,
+
+ self.m31 * mat.m11 + self.m32 * mat.m21 + mat.m31,
+ self.m31 * mat.m12 + self.m32 * mat.m22 + mat.m32,
+ )
+ }
+}
+
+/// Methods for creating and combining translation transformations
+impl<T, Src, Dst> Transform2D<T, Src, Dst>
+where
+ T: Zero + One,
+{
+ /// Create a 2d translation transform:
+ ///
+ /// ```text
+ /// 1 0
+ /// 0 1
+ /// x y
+ /// ```
+ #[inline]
+ pub fn translation(x: T, y: T) -> Self {
+ let _0 = || T::zero();
+ let _1 = || T::one();
+
+ Self::new(
+ _1(), _0(),
+ _0(), _1(),
+ x, y,
+ )
+ }
+
+ /// Applies a translation after self's transformation and returns the resulting transform.
+ #[inline]
+ #[must_use]
+ pub fn then_translate(&self, v: Vector2D<T, Dst>) -> Self
+ where
+ T: Copy + Add<Output = T> + Mul<Output = T>,
+ {
+ self.then(&Transform2D::translation(v.x, v.y))
+ }
+
+ /// Applies a translation before self's transformation and returns the resulting transform.
+ #[inline]
+ #[must_use]
+ pub fn pre_translate(&self, v: Vector2D<T, Src>) -> Self
+ where
+ T: Copy + Add<Output = T> + Mul<Output = T>,
+ {
+ Transform2D::translation(v.x, v.y).then(self)
+ }
+}
+
+/// Methods for creating and combining rotation transformations
+impl<T, Src, Dst> Transform2D<T, Src, Dst>
+where
+ T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Zero + Trig,
+{
+ /// Returns a rotation transform.
+ #[inline]
+ pub fn rotation(theta: Angle<T>) -> Self {
+ let _0 = Zero::zero();
+ let cos = theta.get().cos();
+ let sin = theta.get().sin();
+ Transform2D::new(
+ cos, sin,
+ _0 - sin, cos,
+ _0, _0
+ )
+ }
+
+ /// Applies a rotation after self's transformation and returns the resulting transform.
+ #[inline]
+ #[must_use]
+ pub fn then_rotate(&self, theta: Angle<T>) -> Self {
+ self.then(&Transform2D::rotation(theta))
+ }
+
+ /// Applies a rotation before self's transformation and returns the resulting transform.
+ #[inline]
+ #[must_use]
+ pub fn pre_rotate(&self, theta: Angle<T>) -> Self {
+ Transform2D::rotation(theta).then(self)
+ }
+}
+
+/// Methods for creating and combining scale transformations
+impl<T, Src, Dst> Transform2D<T, Src, Dst> {
+ /// Create a 2d scale transform:
+ ///
+ /// ```text
+ /// x 0
+ /// 0 y
+ /// 0 0
+ /// ```
+ #[inline]
+ pub fn scale(x: T, y: T) -> Self
+ where
+ T: Zero,
+ {
+ let _0 = || Zero::zero();
+
+ Self::new(
+ x, _0(),
+ _0(), y,
+ _0(), _0(),
+ )
+ }
+
+ /// Applies a scale after self's transformation and returns the resulting transform.
+ #[inline]
+ #[must_use]
+ pub fn then_scale(&self, x: T, y: T) -> Self
+ where
+ T: Copy + Add<Output = T> + Mul<Output = T> + Zero,
+ {
+ self.then(&Transform2D::scale(x, y))
+ }
+
+ /// Applies a scale before self's transformation and returns the resulting transform.
+ #[inline]
+ #[must_use]
+ pub fn pre_scale(&self, x: T, y: T) -> Self
+ where
+ T: Copy + Mul<Output = T>,
+ {
+ Transform2D::new(
+ self.m11 * x, self.m12 * x,
+ self.m21 * y, self.m22 * y,
+ self.m31, self.m32
+ )
+ }
+}
+
+/// Methods for apply transformations to objects
+impl<T, Src, Dst> Transform2D<T, Src, Dst>
+where
+ T: Copy + Add<Output = T> + Mul<Output = T>,
+{
+ /// Returns the given point transformed by this transform.
+ #[inline]
+ #[must_use]
+ pub fn transform_point(&self, point: Point2D<T, Src>) -> Point2D<T, Dst> {
+ Point2D::new(
+ point.x * self.m11 + point.y * self.m21 + self.m31,
+ point.x * self.m12 + point.y * self.m22 + self.m32
+ )
+ }
+
+ /// Returns the given vector transformed by this matrix.
+ #[inline]
+ #[must_use]
+ pub fn transform_vector(&self, vec: Vector2D<T, Src>) -> Vector2D<T, Dst> {
+ vec2(vec.x * self.m11 + vec.y * self.m21,
+ vec.x * self.m12 + vec.y * self.m22)
+ }
+
+ /// Returns a rectangle that encompasses the result of transforming the given rectangle by this
+ /// transform.
+ #[inline]
+ #[must_use]
+ pub fn outer_transformed_rect(&self, rect: &Rect<T, Src>) -> Rect<T, Dst>
+ where
+ T: Sub<Output = T> + Zero + PartialOrd,
+ {
+ let min = rect.min();
+ let max = rect.max();
+ Rect::from_points(&[
+ self.transform_point(min),
+ self.transform_point(max),
+ self.transform_point(point2(max.x, min.y)),
+ self.transform_point(point2(min.x, max.y)),
+ ])
+ }
+
+
+ /// Returns a box that encompasses the result of transforming the given box by this
+ /// transform.
+ #[inline]
+ #[must_use]
+ pub fn outer_transformed_box(&self, b: &Box2D<T, Src>) -> Box2D<T, Dst>
+ where
+ T: Sub<Output = T> + Zero + PartialOrd,
+ {
+ Box2D::from_points(&[
+ self.transform_point(b.min),
+ self.transform_point(b.max),
+ self.transform_point(point2(b.max.x, b.min.y)),
+ self.transform_point(point2(b.min.x, b.max.y)),
+ ])
+ }
+}
+
+
+impl<T, Src, Dst> Transform2D<T, Src, Dst>
+where
+ T: Copy + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + PartialEq + Zero + One,
+{
+ /// Computes and returns the determinant of this transform.
+ pub fn determinant(&self) -> T {
+ self.m11 * self.m22 - self.m12 * self.m21
+ }
+
+ /// Returns whether it is possible to compute the inverse transform.
+ #[inline]
+ pub fn is_invertible(&self) -> bool {
+ self.determinant() != Zero::zero()
+ }
+
+ /// Returns the inverse transform if possible.
+ #[must_use]
+ pub fn inverse(&self) -> Option<Transform2D<T, Dst, Src>> {
+ let det = self.determinant();
+
+ let _0: T = Zero::zero();
+ let _1: T = One::one();
+
+ if det == _0 {
+ return None;
+ }
+
+ let inv_det = _1 / det;
+ Some(Transform2D::new(
+ inv_det * self.m22,
+ inv_det * (_0 - self.m12),
+ inv_det * (_0 - self.m21),
+ inv_det * self.m11,
+ inv_det * (self.m21 * self.m32 - self.m22 * self.m31),
+ inv_det * (self.m31 * self.m12 - self.m11 * self.m32),
+ ))
+ }
+}
+
+impl <T, Src, Dst> Default for Transform2D<T, Src, Dst>
+ where T: Zero + One
+{
+ /// Returns the [identity transform](#method.identity).
+ fn default() -> Self {
+ Self::identity()
+ }
+}
+
+impl<T: ApproxEq<T>, Src, Dst> ApproxEq<T> for Transform2D<T, Src, Dst> {
+ #[inline]
+ fn approx_epsilon() -> T { T::approx_epsilon() }
+
+ /// Returns true is this transform is approximately equal to the other one, using
+ /// a provided epsilon value.
+ fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
+ self.m11.approx_eq_eps(&other.m11, eps) && self.m12.approx_eq_eps(&other.m12, eps) &&
+ self.m21.approx_eq_eps(&other.m21, eps) && self.m22.approx_eq_eps(&other.m22, eps) &&
+ self.m31.approx_eq_eps(&other.m31, eps) && self.m32.approx_eq_eps(&other.m32, eps)
+ }
+}
+
+impl<T, Src, Dst> fmt::Debug for Transform2D<T, Src, Dst>
+where T: Copy + fmt::Debug +
+ PartialEq +
+ One + Zero {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.is_identity() {
+ write!(f, "[I]")
+ } else {
+ self.to_array().fmt(f)
+ }
+ }
+}
+
+#[cfg(feature = "mint")]
+impl<T, Src, Dst> From<mint::RowMatrix3x2<T>> for Transform2D<T, Src, Dst> {
+ fn from(m: mint::RowMatrix3x2<T>) -> Self {
+ Transform2D {
+ m11: m.x.x, m12: m.x.y,
+ m21: m.y.x, m22: m.y.y,
+ m31: m.z.x, m32: m.z.y,
+ _unit: PhantomData,
+ }
+ }
+}
+#[cfg(feature = "mint")]
+impl<T, Src, Dst> Into<mint::RowMatrix3x2<T>> for Transform2D<T, Src, Dst> {
+ fn into(self) -> mint::RowMatrix3x2<T> {
+ mint::RowMatrix3x2 {
+ x: mint::Vector2 { x: self.m11, y: self.m12 },
+ y: mint::Vector2 { x: self.m21, y: self.m22 },
+ z: mint::Vector2 { x: self.m31, y: self.m32 },
+ }
+ }
+}
+
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::default;
+ use crate::approxeq::ApproxEq;
+ #[cfg(feature = "mint")]
+ use mint;
+
+ use core::f32::consts::FRAC_PI_2;
+
+ type Mat = default::Transform2D<f32>;
+
+ fn rad(v: f32) -> Angle<f32> { Angle::radians(v) }
+
+ #[test]
+ pub fn test_translation() {
+ let t1 = Mat::translation(1.0, 2.0);
+ let t2 = Mat::identity().pre_translate(vec2(1.0, 2.0));
+ let t3 = Mat::identity().then_translate(vec2(1.0, 2.0));
+ assert_eq!(t1, t2);
+ assert_eq!(t1, t3);
+
+ assert_eq!(t1.transform_point(Point2D::new(1.0, 1.0)), Point2D::new(2.0, 3.0));
+
+ assert_eq!(t1.then(&t1), Mat::translation(2.0, 4.0));
+ }
+
+ #[test]
+ pub fn test_rotation() {
+ let r1 = Mat::rotation(rad(FRAC_PI_2));
+ let r2 = Mat::identity().pre_rotate(rad(FRAC_PI_2));
+ let r3 = Mat::identity().then_rotate(rad(FRAC_PI_2));
+ assert_eq!(r1, r2);
+ assert_eq!(r1, r3);
+
+ assert!(r1.transform_point(Point2D::new(1.0, 2.0)).approx_eq(&Point2D::new(-2.0, 1.0)));
+
+ assert!(r1.then(&r1).approx_eq(&Mat::rotation(rad(FRAC_PI_2*2.0))));
+ }
+
+ #[test]
+ pub fn test_scale() {
+ let s1 = Mat::scale(2.0, 3.0);
+ let s2 = Mat::identity().pre_scale(2.0, 3.0);
+ let s3 = Mat::identity().then_scale(2.0, 3.0);
+ assert_eq!(s1, s2);
+ assert_eq!(s1, s3);
+
+ assert!(s1.transform_point(Point2D::new(2.0, 2.0)).approx_eq(&Point2D::new(4.0, 6.0)));
+ }
+
+
+ #[test]
+ pub fn test_pre_then_scale() {
+ let m = Mat::rotation(rad(FRAC_PI_2)).then_translate(vec2(6.0, 7.0));
+ let s = Mat::scale(2.0, 3.0);
+ assert_eq!(m.then(&s), m.then_scale(2.0, 3.0));
+ }
+
+ #[test]
+ pub fn test_inverse_simple() {
+ let m1 = Mat::identity();
+ let m2 = m1.inverse().unwrap();
+ assert!(m1.approx_eq(&m2));
+ }
+
+ #[test]
+ pub fn test_inverse_scale() {
+ let m1 = Mat::scale(1.5, 0.3);
+ let m2 = m1.inverse().unwrap();
+ assert!(m1.then(&m2).approx_eq(&Mat::identity()));
+ assert!(m2.then(&m1).approx_eq(&Mat::identity()));
+ }
+
+ #[test]
+ pub fn test_inverse_translate() {
+ let m1 = Mat::translation(-132.0, 0.3);
+ let m2 = m1.inverse().unwrap();
+ assert!(m1.then(&m2).approx_eq(&Mat::identity()));
+ assert!(m2.then(&m1).approx_eq(&Mat::identity()));
+ }
+
+ #[test]
+ fn test_inverse_none() {
+ assert!(Mat::scale(2.0, 0.0).inverse().is_none());
+ assert!(Mat::scale(2.0, 2.0).inverse().is_some());
+ }
+
+ #[test]
+ pub fn test_pre_post() {
+ let m1 = default::Transform2D::identity().then_scale(1.0, 2.0).then_translate(vec2(1.0, 2.0));
+ let m2 = default::Transform2D::identity().pre_translate(vec2(1.0, 2.0)).pre_scale(1.0, 2.0);
+ assert!(m1.approx_eq(&m2));
+
+ let r = Mat::rotation(rad(FRAC_PI_2));
+ let t = Mat::translation(2.0, 3.0);
+
+ let a = Point2D::new(1.0, 1.0);
+
+ assert!(r.then(&t).transform_point(a).approx_eq(&Point2D::new(1.0, 4.0)));
+ assert!(t.then(&r).transform_point(a).approx_eq(&Point2D::new(-4.0, 3.0)));
+ assert!(t.then(&r).transform_point(a).approx_eq(&r.transform_point(t.transform_point(a))));
+ }
+
+ #[test]
+ fn test_size_of() {
+ use core::mem::size_of;
+ assert_eq!(size_of::<default::Transform2D<f32>>(), 6*size_of::<f32>());
+ assert_eq!(size_of::<default::Transform2D<f64>>(), 6*size_of::<f64>());
+ }
+
+ #[test]
+ pub fn test_is_identity() {
+ let m1 = default::Transform2D::identity();
+ assert!(m1.is_identity());
+ let m2 = m1.then_translate(vec2(0.1, 0.0));
+ assert!(!m2.is_identity());
+ }
+
+ #[test]
+ pub fn test_transform_vector() {
+ // Translation does not apply to vectors.
+ let m1 = Mat::translation(1.0, 1.0);
+ let v1 = vec2(10.0, -10.0);
+ assert_eq!(v1, m1.transform_vector(v1));
+ }
+
+ #[cfg(feature = "mint")]
+ #[test]
+ pub fn test_mint() {
+ let m1 = Mat::rotation(rad(FRAC_PI_2));
+ let mm: mint::RowMatrix3x2<_> = m1.into();
+ let m2 = Mat::from(mm);
+
+ assert_eq!(m1, m2);
+ }
+}
diff --git a/third_party/rust/euclid/src/transform3d.rs b/third_party/rust/euclid/src/transform3d.rs
new file mode 100644
index 0000000000..2ea4730ad2
--- /dev/null
+++ b/third_party/rust/euclid/src/transform3d.rs
@@ -0,0 +1,1436 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![cfg_attr(feature = "cargo-clippy", allow(just_underscores_and_digits))]
+
+use super::{UnknownUnit, Angle};
+use crate::approxeq::ApproxEq;
+use crate::homogen::HomogeneousVector;
+#[cfg(feature = "mint")]
+use mint;
+use crate::trig::Trig;
+use crate::point::{Point2D, point2, Point3D, point3};
+use crate::vector::{Vector2D, Vector3D, vec2, vec3};
+use crate::rect::Rect;
+use crate::box2d::Box2D;
+use crate::box3d::Box3D;
+use crate::transform2d::Transform2D;
+use crate::scale::Scale;
+use crate::num::{One, Zero};
+use core::ops::{Add, Mul, Sub, Div, Neg};
+use core::marker::PhantomData;
+use core::fmt;
+use core::cmp::{Eq, PartialEq};
+use core::hash::{Hash};
+use num_traits::NumCast;
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A 3d transform stored as a column-major 4 by 4 matrix.
+///
+/// Transforms can be parametrized over the source and destination units, to describe a
+/// transformation from a space to another.
+/// For example, `Transform3D<f32, WorldSpace, ScreenSpace>::transform_point3d`
+/// takes a `Point3D<f32, WorldSpace>` and returns a `Point3D<f32, ScreenSpace>`.
+///
+/// Transforms expose a set of convenience methods for pre- and post-transformations.
+/// Pre-transformations (`pre_*` methods) correspond to adding an operation that is
+/// applied before the rest of the transformation, while post-transformations (`then_*`
+/// methods) add an operation that is applied after.
+///
+/// When translating Transform3D into general matrix representations, consider that the
+/// representation follows the column major notation with column vectors.
+///
+/// ```text
+/// |x'| | m11 m12 m13 m14 | |x|
+/// |y'| | m21 m22 m23 m24 | |y|
+/// |z'| = | m31 m32 m33 m34 | x |y|
+/// |w | | m41 m42 m43 m44 | |1|
+/// ```
+///
+/// The translation terms are m41, m42 and m43.
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+ feature = "serde",
+ serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
+)]
+pub struct Transform3D<T, Src, Dst> {
+ pub m11: T, pub m12: T, pub m13: T, pub m14: T,
+ pub m21: T, pub m22: T, pub m23: T, pub m24: T,
+ pub m31: T, pub m32: T, pub m33: T, pub m34: T,
+ pub m41: T, pub m42: T, pub m43: T, pub m44: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<(Src, Dst)>,
+}
+
+
+#[cfg(feature = "arbitrary")]
+impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Transform3D<T, Src, Dst>
+where
+ T: arbitrary::Arbitrary<'a>,
+{
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>
+ {
+ let (m11, m12, m13, m14) = arbitrary::Arbitrary::arbitrary(u)?;
+ let (m21, m22, m23, m24) = arbitrary::Arbitrary::arbitrary(u)?;
+ let (m31, m32, m33, m34) = arbitrary::Arbitrary::arbitrary(u)?;
+ let (m41, m42, m43, m44) = arbitrary::Arbitrary::arbitrary(u)?;
+
+ Ok(Transform3D {
+ m11,
+ m12,
+ m13,
+ m14,
+ m21,
+ m22,
+ m23,
+ m24,
+ m31,
+ m32,
+ m33,
+ m34,
+ m41,
+ m42,
+ m43,
+ m44,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, Src, Dst> Zeroable for Transform3D<T, Src, Dst> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Transform3D<T, Src, Dst> {}
+
+impl<T: Copy, Src, Dst> Copy for Transform3D<T, Src, Dst> {}
+
+impl<T: Clone, Src, Dst> Clone for Transform3D<T, Src, Dst> {
+ fn clone(&self) -> Self {
+ Transform3D {
+ m11: self.m11.clone(),
+ m12: self.m12.clone(),
+ m13: self.m13.clone(),
+ m14: self.m14.clone(),
+ m21: self.m21.clone(),
+ m22: self.m22.clone(),
+ m23: self.m23.clone(),
+ m24: self.m24.clone(),
+ m31: self.m31.clone(),
+ m32: self.m32.clone(),
+ m33: self.m33.clone(),
+ m34: self.m34.clone(),
+ m41: self.m41.clone(),
+ m42: self.m42.clone(),
+ m43: self.m43.clone(),
+ m44: self.m44.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+impl<T, Src, Dst> Eq for Transform3D<T, Src, Dst> where T: Eq {}
+
+impl<T, Src, Dst> PartialEq for Transform3D<T, Src, Dst>
+ where T: PartialEq
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.m11 == other.m11 &&
+ self.m12 == other.m12 &&
+ self.m13 == other.m13 &&
+ self.m14 == other.m14 &&
+ self.m21 == other.m21 &&
+ self.m22 == other.m22 &&
+ self.m23 == other.m23 &&
+ self.m24 == other.m24 &&
+ self.m31 == other.m31 &&
+ self.m32 == other.m32 &&
+ self.m33 == other.m33 &&
+ self.m34 == other.m34 &&
+ self.m41 == other.m41 &&
+ self.m42 == other.m42 &&
+ self.m43 == other.m43 &&
+ self.m44 == other.m44
+ }
+}
+
+impl<T, Src, Dst> Hash for Transform3D<T, Src, Dst>
+ where T: Hash
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.m11.hash(h);
+ self.m12.hash(h);
+ self.m13.hash(h);
+ self.m14.hash(h);
+ self.m21.hash(h);
+ self.m22.hash(h);
+ self.m23.hash(h);
+ self.m24.hash(h);
+ self.m31.hash(h);
+ self.m32.hash(h);
+ self.m33.hash(h);
+ self.m34.hash(h);
+ self.m41.hash(h);
+ self.m42.hash(h);
+ self.m43.hash(h);
+ self.m44.hash(h);
+ }
+}
+
+
+impl<T, Src, Dst> Transform3D<T, Src, Dst> {
+ /// Create a transform specifying all of it's component as a 4 by 4 matrix.
+ ///
+ /// Components are specified following column-major-column-vector matrix notation.
+ /// For example, the translation terms m41, m42, m43 are the 13rd, 14th and 15th parameters.
+ ///
+ /// ```
+ /// use euclid::default::Transform3D;
+ /// let tx = 1.0;
+ /// let ty = 2.0;
+ /// let tz = 3.0;
+ /// let translation = Transform3D::new(
+ /// 1.0, 0.0, 0.0, 0.0,
+ /// 0.0, 1.0, 0.0, 0.0,
+ /// 0.0, 0.0, 1.0, 0.0,
+ /// tx, ty, tz, 1.0,
+ /// );
+ /// ```
+ #[inline]
+ #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
+ pub const fn new(
+ m11: T, m12: T, m13: T, m14: T,
+ m21: T, m22: T, m23: T, m24: T,
+ m31: T, m32: T, m33: T, m34: T,
+ m41: T, m42: T, m43: T, m44: T,
+ ) -> Self {
+ Transform3D {
+ m11, m12, m13, m14,
+ m21, m22, m23, m24,
+ m31, m32, m33, m34,
+ m41, m42, m43, m44,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Create a transform representing a 2d transformation from the components
+ /// of a 2 by 3 matrix transformation.
+ ///
+ /// Components follow the column-major-column-vector notation (m41 and m42
+ /// representating the translation terms).
+ ///
+ /// ```text
+ /// m11 m12 0 0
+ /// m21 m22 0 0
+ /// 0 0 1 0
+ /// m41 m42 0 1
+ /// ```
+ #[inline]
+ pub fn new_2d(m11: T, m12: T, m21: T, m22: T, m41: T, m42: T) -> Self
+ where
+ T: Zero + One,
+ {
+ let _0 = || T::zero();
+ let _1 = || T::one();
+
+ Self::new(
+ m11, m12, _0(), _0(),
+ m21, m22, _0(), _0(),
+ _0(), _0(), _1(), _0(),
+ m41, m42, _0(), _1()
+ )
+ }
+
+
+ /// Returns `true` if this transform can be represented with a `Transform2D`.
+ ///
+ /// See <https://drafts.csswg.org/css-transforms/#2d-transform>
+ #[inline]
+ pub fn is_2d(&self) -> bool
+ where
+ T: Zero + One + PartialEq,
+ {
+ let (_0, _1): (T, T) = (Zero::zero(), One::one());
+ self.m31 == _0 && self.m32 == _0 &&
+ self.m13 == _0 && self.m23 == _0 &&
+ self.m43 == _0 && self.m14 == _0 &&
+ self.m24 == _0 && self.m34 == _0 &&
+ self.m33 == _1 && self.m44 == _1
+ }
+}
+
+impl<T: Copy, Src, Dst> Transform3D<T, Src, Dst> {
+ /// Returns an array containing this transform's terms.
+ ///
+ /// The terms are laid out in the same order as they are
+ /// specified in `Transform3D::new`, that is following the
+ /// column-major-column-vector matrix notation.
+ ///
+ /// For example the translation terms are found on the
+ /// 13th, 14th and 15th slots of the array.
+ #[inline]
+ pub fn to_array(&self) -> [T; 16] {
+ [
+ self.m11, self.m12, self.m13, self.m14,
+ self.m21, self.m22, self.m23, self.m24,
+ self.m31, self.m32, self.m33, self.m34,
+ self.m41, self.m42, self.m43, self.m44
+ ]
+ }
+
+ /// Returns an array containing this transform's terms transposed.
+ ///
+ /// The terms are laid out in transposed order from the same order of
+ /// `Transform3D::new` and `Transform3D::to_array`, that is following
+ /// the row-major-column-vector matrix notation.
+ ///
+ /// For example the translation terms are found at indices 3, 7 and 11
+ /// of the array.
+ #[inline]
+ pub fn to_array_transposed(&self) -> [T; 16] {
+ [
+ self.m11, self.m21, self.m31, self.m41,
+ self.m12, self.m22, self.m32, self.m42,
+ self.m13, self.m23, self.m33, self.m43,
+ self.m14, self.m24, self.m34, self.m44
+ ]
+ }
+
+ /// Equivalent to `to_array` with elements packed four at a time
+ /// in an array of arrays.
+ #[inline]
+ pub fn to_arrays(&self) -> [[T; 4]; 4] {
+ [
+ [self.m11, self.m12, self.m13, self.m14],
+ [self.m21, self.m22, self.m23, self.m24],
+ [self.m31, self.m32, self.m33, self.m34],
+ [self.m41, self.m42, self.m43, self.m44]
+ ]
+ }
+
+ /// Equivalent to `to_array_transposed` with elements packed
+ /// four at a time in an array of arrays.
+ #[inline]
+ pub fn to_arrays_transposed(&self) -> [[T; 4]; 4] {
+ [
+ [self.m11, self.m21, self.m31, self.m41],
+ [self.m12, self.m22, self.m32, self.m42],
+ [self.m13, self.m23, self.m33, self.m43],
+ [self.m14, self.m24, self.m34, self.m44]
+ ]
+ }
+
+ /// Create a transform providing its components via an array
+ /// of 16 elements instead of as individual parameters.
+ ///
+ /// The order of the components corresponds to the
+ /// column-major-column-vector matrix notation (the same order
+ /// as `Transform3D::new`).
+ #[inline]
+ pub fn from_array(array: [T; 16]) -> Self {
+ Self::new(
+ array[0], array[1], array[2], array[3],
+ array[4], array[5], array[6], array[7],
+ array[8], array[9], array[10], array[11],
+ array[12], array[13], array[14], array[15],
+ )
+ }
+
+ /// Equivalent to `from_array` with elements packed four at a time
+ /// in an array of arrays.
+ ///
+ /// The order of the components corresponds to the
+ /// column-major-column-vector matrix notation (the same order
+ /// as `Transform3D::new`).
+ #[inline]
+ pub fn from_arrays(array: [[T; 4]; 4]) -> Self {
+ Self::new(
+ array[0][0], array[0][1], array[0][2], array[0][3],
+ array[1][0], array[1][1], array[1][2], array[1][3],
+ array[2][0], array[2][1], array[2][2], array[2][3],
+ array[3][0], array[3][1], array[3][2], array[3][3],
+ )
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(m: &Transform3D<T, UnknownUnit, UnknownUnit>) -> Self {
+ Transform3D::new(
+ m.m11, m.m12, m.m13, m.m14,
+ m.m21, m.m22, m.m23, m.m24,
+ m.m31, m.m32, m.m33, m.m34,
+ m.m41, m.m42, m.m43, m.m44,
+ )
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(&self) -> Transform3D<T, UnknownUnit, UnknownUnit> {
+ Transform3D::new(
+ self.m11, self.m12, self.m13, self.m14,
+ self.m21, self.m22, self.m23, self.m24,
+ self.m31, self.m32, self.m33, self.m34,
+ self.m41, self.m42, self.m43, self.m44,
+ )
+ }
+
+ /// Returns the same transform with a different source unit.
+ #[inline]
+ pub fn with_source<NewSrc>(&self) -> Transform3D<T, NewSrc, Dst> {
+ Transform3D::new(
+ self.m11, self.m12, self.m13, self.m14,
+ self.m21, self.m22, self.m23, self.m24,
+ self.m31, self.m32, self.m33, self.m34,
+ self.m41, self.m42, self.m43, self.m44,
+ )
+ }
+
+ /// Returns the same transform with a different destination unit.
+ #[inline]
+ pub fn with_destination<NewDst>(&self) -> Transform3D<T, Src, NewDst> {
+ Transform3D::new(
+ self.m11, self.m12, self.m13, self.m14,
+ self.m21, self.m22, self.m23, self.m24,
+ self.m31, self.m32, self.m33, self.m34,
+ self.m41, self.m42, self.m43, self.m44,
+ )
+ }
+
+ /// Create a 2D transform picking the relevant terms from this transform.
+ ///
+ /// This method assumes that self represents a 2d transformation, callers
+ /// should check that [`self.is_2d()`] returns `true` beforehand.
+ ///
+ /// [`self.is_2d()`]: #method.is_2d
+ pub fn to_2d(&self) -> Transform2D<T, Src, Dst> {
+ Transform2D::new(
+ self.m11, self.m12,
+ self.m21, self.m22,
+ self.m41, self.m42
+ )
+ }
+}
+
+impl <T, Src, Dst> Transform3D<T, Src, Dst>
+where
+ T: Zero + One,
+{
+ /// Creates an identity matrix:
+ ///
+ /// ```text
+ /// 1 0 0 0
+ /// 0 1 0 0
+ /// 0 0 1 0
+ /// 0 0 0 1
+ /// ```
+ #[inline]
+ pub fn identity() -> Self {
+ Self::translation(T::zero(), T::zero(), T::zero())
+ }
+
+ /// Intentional not public, because it checks for exact equivalence
+ /// while most consumers will probably want some sort of approximate
+ /// equivalence to deal with floating-point errors.
+ #[inline]
+ fn is_identity(&self) -> bool
+ where
+ T: PartialEq,
+ {
+ *self == Self::identity()
+ }
+
+ /// Create a 2d skew transform.
+ ///
+ /// See <https://drafts.csswg.org/css-transforms/#funcdef-skew>
+ pub fn skew(alpha: Angle<T>, beta: Angle<T>) -> Self
+ where
+ T: Trig,
+ {
+ let _0 = || T::zero();
+ let _1 = || T::one();
+ let (sx, sy) = (beta.radians.tan(), alpha.radians.tan());
+
+ Self::new(
+ _1(), sx, _0(), _0(),
+ sy, _1(), _0(), _0(),
+ _0(), _0(), _1(), _0(),
+ _0(), _0(), _0(), _1(),
+ )
+ }
+
+ /// Create a simple perspective transform, projecting to the plane `z = -d`.
+ ///
+ /// ```text
+ /// 1 0 0 0
+ /// 0 1 0 0
+ /// 0 0 1 -1/d
+ /// 0 0 0 1
+ /// ```
+ ///
+ /// See <https://drafts.csswg.org/css-transforms-2/#PerspectiveDefined>.
+ pub fn perspective(d: T) -> Self
+ where
+ T: Neg<Output = T> + Div<Output = T>,
+ {
+ let _0 = || T::zero();
+ let _1 = || T::one();
+
+ Self::new(
+ _1(), _0(), _0(), _0(),
+ _0(), _1(), _0(), _0(),
+ _0(), _0(), _1(), -_1() / d,
+ _0(), _0(), _0(), _1(),
+ )
+ }
+}
+
+
+/// Methods for combining generic transformations
+impl <T, Src, Dst> Transform3D<T, Src, Dst>
+where
+ T: Copy + Add<Output = T> + Mul<Output = T>,
+{
+ /// Returns the multiplication of the two matrices such that mat's transformation
+ /// applies after self's transformation.
+ ///
+ /// Assuming row vectors, this is equivalent to self * mat
+ #[must_use]
+ pub fn then<NewDst>(&self, other: &Transform3D<T, Dst, NewDst>) -> Transform3D<T, Src, NewDst> {
+ Transform3D::new(
+ self.m11 * other.m11 + self.m12 * other.m21 + self.m13 * other.m31 + self.m14 * other.m41,
+ self.m11 * other.m12 + self.m12 * other.m22 + self.m13 * other.m32 + self.m14 * other.m42,
+ self.m11 * other.m13 + self.m12 * other.m23 + self.m13 * other.m33 + self.m14 * other.m43,
+ self.m11 * other.m14 + self.m12 * other.m24 + self.m13 * other.m34 + self.m14 * other.m44,
+
+ self.m21 * other.m11 + self.m22 * other.m21 + self.m23 * other.m31 + self.m24 * other.m41,
+ self.m21 * other.m12 + self.m22 * other.m22 + self.m23 * other.m32 + self.m24 * other.m42,
+ self.m21 * other.m13 + self.m22 * other.m23 + self.m23 * other.m33 + self.m24 * other.m43,
+ self.m21 * other.m14 + self.m22 * other.m24 + self.m23 * other.m34 + self.m24 * other.m44,
+
+ self.m31 * other.m11 + self.m32 * other.m21 + self.m33 * other.m31 + self.m34 * other.m41,
+ self.m31 * other.m12 + self.m32 * other.m22 + self.m33 * other.m32 + self.m34 * other.m42,
+ self.m31 * other.m13 + self.m32 * other.m23 + self.m33 * other.m33 + self.m34 * other.m43,
+ self.m31 * other.m14 + self.m32 * other.m24 + self.m33 * other.m34 + self.m34 * other.m44,
+
+ self.m41 * other.m11 + self.m42 * other.m21 + self.m43 * other.m31 + self.m44 * other.m41,
+ self.m41 * other.m12 + self.m42 * other.m22 + self.m43 * other.m32 + self.m44 * other.m42,
+ self.m41 * other.m13 + self.m42 * other.m23 + self.m43 * other.m33 + self.m44 * other.m43,
+ self.m41 * other.m14 + self.m42 * other.m24 + self.m43 * other.m34 + self.m44 * other.m44,
+ )
+ }
+}
+
+/// Methods for creating and combining translation transformations
+impl <T, Src, Dst> Transform3D<T, Src, Dst>
+where
+ T: Zero + One,
+{
+ /// Create a 3d translation transform:
+ ///
+ /// ```text
+ /// 1 0 0 0
+ /// 0 1 0 0
+ /// 0 0 1 0
+ /// x y z 1
+ /// ```
+ #[inline]
+ pub fn translation(x: T, y: T, z: T) -> Self {
+ let _0 = || T::zero();
+ let _1 = || T::one();
+
+ Self::new(
+ _1(), _0(), _0(), _0(),
+ _0(), _1(), _0(), _0(),
+ _0(), _0(), _1(), _0(),
+ x, y, z, _1(),
+ )
+ }
+
+ /// Returns a transform with a translation applied before self's transformation.
+ #[must_use]
+ pub fn pre_translate(&self, v: Vector3D<T, Src>) -> Self
+ where
+ T: Copy + Add<Output = T> + Mul<Output = T>,
+ {
+ Transform3D::translation(v.x, v.y, v.z).then(self)
+ }
+
+ /// Returns a transform with a translation applied after self's transformation.
+ #[must_use]
+ pub fn then_translate(&self, v: Vector3D<T, Dst>) -> Self
+ where
+ T: Copy + Add<Output = T> + Mul<Output = T>,
+ {
+ self.then(&Transform3D::translation(v.x, v.y, v.z))
+ }
+}
+
+/// Methods for creating and combining rotation transformations
+impl<T, Src, Dst> Transform3D<T, Src, Dst>
+where
+ T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + Zero + One + Trig,
+{
+ /// Create a 3d rotation transform from an angle / axis.
+ /// The supplied axis must be normalized.
+ pub fn rotation(x: T, y: T, z: T, theta: Angle<T>) -> Self {
+ let (_0, _1): (T, T) = (Zero::zero(), One::one());
+ let _2 = _1 + _1;
+
+ let xx = x * x;
+ let yy = y * y;
+ let zz = z * z;
+
+ let half_theta = theta.get() / _2;
+ let sc = half_theta.sin() * half_theta.cos();
+ let sq = half_theta.sin() * half_theta.sin();
+
+ Transform3D::new(
+ _1 - _2 * (yy + zz) * sq,
+ _2 * (x * y * sq + z * sc),
+ _2 * (x * z * sq - y * sc),
+ _0,
+
+
+ _2 * (x * y * sq - z * sc),
+ _1 - _2 * (xx + zz) * sq,
+ _2 * (y * z * sq + x * sc),
+ _0,
+
+ _2 * (x * z * sq + y * sc),
+ _2 * (y * z * sq - x * sc),
+ _1 - _2 * (xx + yy) * sq,
+ _0,
+
+ _0,
+ _0,
+ _0,
+ _1
+ )
+ }
+
+ /// Returns a transform with a rotation applied after self's transformation.
+ #[must_use]
+ pub fn then_rotate(&self, x: T, y: T, z: T, theta: Angle<T>) -> Self {
+ self.then(&Transform3D::rotation(x, y, z, theta))
+ }
+
+ /// Returns a transform with a rotation applied before self's transformation.
+ #[must_use]
+ pub fn pre_rotate(&self, x: T, y: T, z: T, theta: Angle<T>) -> Self {
+ Transform3D::rotation(x, y, z, theta).then(self)
+ }
+}
+
+/// Methods for creating and combining scale transformations
+impl<T, Src, Dst> Transform3D<T, Src, Dst>
+where
+ T: Zero + One,
+{
+ /// Create a 3d scale transform:
+ ///
+ /// ```text
+ /// x 0 0 0
+ /// 0 y 0 0
+ /// 0 0 z 0
+ /// 0 0 0 1
+ /// ```
+ #[inline]
+ pub fn scale(x: T, y: T, z: T) -> Self {
+ let _0 = || T::zero();
+ let _1 = || T::one();
+
+ Self::new(
+ x, _0(), _0(), _0(),
+ _0(), y, _0(), _0(),
+ _0(), _0(), z, _0(),
+ _0(), _0(), _0(), _1(),
+ )
+ }
+
+ /// Returns a transform with a scale applied before self's transformation.
+ #[must_use]
+ pub fn pre_scale(&self, x: T, y: T, z: T) -> Self
+ where
+ T: Copy + Add<Output = T> + Mul<Output = T>,
+ {
+ Transform3D::new(
+ self.m11 * x, self.m12 * x, self.m13 * x, self.m14 * x,
+ self.m21 * y, self.m22 * y, self.m23 * y, self.m24 * y,
+ self.m31 * z, self.m32 * z, self.m33 * z, self.m34 * z,
+ self.m41 , self.m42, self.m43, self.m44
+ )
+ }
+
+ /// Returns a transform with a scale applied after self's transformation.
+ #[must_use]
+ pub fn then_scale(&self, x: T, y: T, z: T) -> Self
+ where
+ T: Copy + Add<Output = T> + Mul<Output = T>,
+ {
+ self.then(&Transform3D::scale(x, y, z))
+ }
+}
+
+/// Methods for apply transformations to objects
+impl<T, Src, Dst> Transform3D<T, Src, Dst>
+where
+ T: Copy + Add<Output = T> + Mul<Output = T>,
+{
+ /// Returns the homogeneous vector corresponding to the transformed 2d point.
+ ///
+ /// The input point must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_point2d_homogeneous(
+ &self, p: Point2D<T, Src>
+ ) -> HomogeneousVector<T, Dst> {
+ let x = p.x * self.m11 + p.y * self.m21 + self.m41;
+ let y = p.x * self.m12 + p.y * self.m22 + self.m42;
+ let z = p.x * self.m13 + p.y * self.m23 + self.m43;
+ let w = p.x * self.m14 + p.y * self.m24 + self.m44;
+
+ HomogeneousVector::new(x, y, z, w)
+ }
+
+ /// Returns the given 2d point transformed by this transform, if the transform makes sense,
+ /// or `None` otherwise.
+ ///
+ /// The input point must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_point2d(&self, p: Point2D<T, Src>) -> Option<Point2D<T, Dst>>
+ where
+ T: Div<Output = T> + Zero + PartialOrd,
+ {
+ //Note: could use `transform_point2d_homogeneous()` but it would waste the calculus of `z`
+ let w = p.x * self.m14 + p.y * self.m24 + self.m44;
+ if w > T::zero() {
+ let x = p.x * self.m11 + p.y * self.m21 + self.m41;
+ let y = p.x * self.m12 + p.y * self.m22 + self.m42;
+
+ Some(Point2D::new(x / w, y / w))
+ } else {
+ None
+ }
+ }
+
+ /// Returns the given 2d vector transformed by this matrix.
+ ///
+ /// The input point must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_vector2d(&self, v: Vector2D<T, Src>) -> Vector2D<T, Dst> {
+ vec2(
+ v.x * self.m11 + v.y * self.m21,
+ v.x * self.m12 + v.y * self.m22,
+ )
+ }
+
+ /// Returns the homogeneous vector corresponding to the transformed 3d point.
+ ///
+ /// The input point must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_point3d_homogeneous(
+ &self, p: Point3D<T, Src>
+ ) -> HomogeneousVector<T, Dst> {
+ let x = p.x * self.m11 + p.y * self.m21 + p.z * self.m31 + self.m41;
+ let y = p.x * self.m12 + p.y * self.m22 + p.z * self.m32 + self.m42;
+ let z = p.x * self.m13 + p.y * self.m23 + p.z * self.m33 + self.m43;
+ let w = p.x * self.m14 + p.y * self.m24 + p.z * self.m34 + self.m44;
+
+ HomogeneousVector::new(x, y, z, w)
+ }
+
+ /// Returns the given 3d point transformed by this transform, if the transform makes sense,
+ /// or `None` otherwise.
+ ///
+ /// The input point must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_point3d(&self, p: Point3D<T, Src>) -> Option<Point3D<T, Dst>>
+ where
+ T: Div<Output = T> + Zero + PartialOrd,
+ {
+ self.transform_point3d_homogeneous(p).to_point3d()
+ }
+
+ /// Returns the given 3d vector transformed by this matrix.
+ ///
+ /// The input point must be use the unit Src, and the returned point has the unit Dst.
+ #[inline]
+ pub fn transform_vector3d(&self, v: Vector3D<T, Src>) -> Vector3D<T, Dst> {
+ vec3(
+ v.x * self.m11 + v.y * self.m21 + v.z * self.m31,
+ v.x * self.m12 + v.y * self.m22 + v.z * self.m32,
+ v.x * self.m13 + v.y * self.m23 + v.z * self.m33,
+ )
+ }
+
+ /// Returns a rectangle that encompasses the result of transforming the given rectangle by this
+ /// transform, if the transform makes sense for it, or `None` otherwise.
+ pub fn outer_transformed_rect(&self, rect: &Rect<T, Src>) -> Option<Rect<T, Dst>>
+ where
+ T: Sub<Output = T> + Div<Output = T> + Zero + PartialOrd,
+ {
+ let min = rect.min();
+ let max = rect.max();
+ Some(Rect::from_points(&[
+ self.transform_point2d(min)?,
+ self.transform_point2d(max)?,
+ self.transform_point2d(point2(max.x, min.y))?,
+ self.transform_point2d(point2(min.x, max.y))?,
+ ]))
+ }
+
+ /// Returns a 2d box that encompasses the result of transforming the given box by this
+ /// transform, if the transform makes sense for it, or `None` otherwise.
+ pub fn outer_transformed_box2d(&self, b: &Box2D<T, Src>) -> Option<Box2D<T, Dst>>
+ where
+ T: Sub<Output = T> + Div<Output = T> + Zero + PartialOrd,
+ {
+ Some(Box2D::from_points(&[
+ self.transform_point2d(b.min)?,
+ self.transform_point2d(b.max)?,
+ self.transform_point2d(point2(b.max.x, b.min.y))?,
+ self.transform_point2d(point2(b.min.x, b.max.y))?,
+ ]))
+ }
+
+ /// Returns a 3d box that encompasses the result of transforming the given box by this
+ /// transform, if the transform makes sense for it, or `None` otherwise.
+ pub fn outer_transformed_box3d(&self, b: &Box3D<T, Src>) -> Option<Box3D<T, Dst>>
+ where
+ T: Sub<Output = T> + Div<Output = T> + Zero + PartialOrd,
+ {
+ Some(Box3D::from_points(&[
+ self.transform_point3d(point3(b.min.x, b.min.y, b.min.z))?,
+ self.transform_point3d(point3(b.min.x, b.min.y, b.max.z))?,
+ self.transform_point3d(point3(b.min.x, b.max.y, b.min.z))?,
+ self.transform_point3d(point3(b.min.x, b.max.y, b.max.z))?,
+ self.transform_point3d(point3(b.max.x, b.min.y, b.min.z))?,
+ self.transform_point3d(point3(b.max.x, b.min.y, b.max.z))?,
+ self.transform_point3d(point3(b.max.x, b.max.y, b.min.z))?,
+ self.transform_point3d(point3(b.max.x, b.max.y, b.max.z))?,
+ ]))
+ }
+}
+
+
+impl <T, Src, Dst> Transform3D<T, Src, Dst>
+where T: Copy +
+ Add<T, Output=T> +
+ Sub<T, Output=T> +
+ Mul<T, Output=T> +
+ Div<T, Output=T> +
+ Neg<Output=T> +
+ PartialOrd +
+ One + Zero {
+
+ /// Create an orthogonal projection transform.
+ pub fn ortho(left: T, right: T,
+ bottom: T, top: T,
+ near: T, far: T) -> Self {
+ let tx = -((right + left) / (right - left));
+ let ty = -((top + bottom) / (top - bottom));
+ let tz = -((far + near) / (far - near));
+
+ let (_0, _1): (T, T) = (Zero::zero(), One::one());
+ let _2 = _1 + _1;
+ Transform3D::new(
+ _2 / (right - left), _0 , _0 , _0,
+ _0 , _2 / (top - bottom), _0 , _0,
+ _0 , _0 , -_2 / (far - near), _0,
+ tx , ty , tz , _1
+ )
+ }
+
+ /// Check whether shapes on the XY plane with Z pointing towards the
+ /// screen transformed by this matrix would be facing back.
+ pub fn is_backface_visible(&self) -> bool {
+ // inverse().m33 < 0;
+ let det = self.determinant();
+ let m33 = self.m12 * self.m24 * self.m41 - self.m14 * self.m22 * self.m41 +
+ self.m14 * self.m21 * self.m42 - self.m11 * self.m24 * self.m42 -
+ self.m12 * self.m21 * self.m44 + self.m11 * self.m22 * self.m44;
+ let _0: T = Zero::zero();
+ (m33 * det) < _0
+ }
+
+ /// Returns whether it is possible to compute the inverse transform.
+ #[inline]
+ pub fn is_invertible(&self) -> bool {
+ self.determinant() != Zero::zero()
+ }
+
+ /// Returns the inverse transform if possible.
+ pub fn inverse(&self) -> Option<Transform3D<T, Dst, Src>> {
+ let det = self.determinant();
+
+ if det == Zero::zero() {
+ return None;
+ }
+
+ // todo(gw): this could be made faster by special casing
+ // for simpler transform types.
+ let m = Transform3D::new(
+ self.m23*self.m34*self.m42 - self.m24*self.m33*self.m42 +
+ self.m24*self.m32*self.m43 - self.m22*self.m34*self.m43 -
+ self.m23*self.m32*self.m44 + self.m22*self.m33*self.m44,
+
+ self.m14*self.m33*self.m42 - self.m13*self.m34*self.m42 -
+ self.m14*self.m32*self.m43 + self.m12*self.m34*self.m43 +
+ self.m13*self.m32*self.m44 - self.m12*self.m33*self.m44,
+
+ self.m13*self.m24*self.m42 - self.m14*self.m23*self.m42 +
+ self.m14*self.m22*self.m43 - self.m12*self.m24*self.m43 -
+ self.m13*self.m22*self.m44 + self.m12*self.m23*self.m44,
+
+ self.m14*self.m23*self.m32 - self.m13*self.m24*self.m32 -
+ self.m14*self.m22*self.m33 + self.m12*self.m24*self.m33 +
+ self.m13*self.m22*self.m34 - self.m12*self.m23*self.m34,
+
+ self.m24*self.m33*self.m41 - self.m23*self.m34*self.m41 -
+ self.m24*self.m31*self.m43 + self.m21*self.m34*self.m43 +
+ self.m23*self.m31*self.m44 - self.m21*self.m33*self.m44,
+
+ self.m13*self.m34*self.m41 - self.m14*self.m33*self.m41 +
+ self.m14*self.m31*self.m43 - self.m11*self.m34*self.m43 -
+ self.m13*self.m31*self.m44 + self.m11*self.m33*self.m44,
+
+ self.m14*self.m23*self.m41 - self.m13*self.m24*self.m41 -
+ self.m14*self.m21*self.m43 + self.m11*self.m24*self.m43 +
+ self.m13*self.m21*self.m44 - self.m11*self.m23*self.m44,
+
+ self.m13*self.m24*self.m31 - self.m14*self.m23*self.m31 +
+ self.m14*self.m21*self.m33 - self.m11*self.m24*self.m33 -
+ self.m13*self.m21*self.m34 + self.m11*self.m23*self.m34,
+
+ self.m22*self.m34*self.m41 - self.m24*self.m32*self.m41 +
+ self.m24*self.m31*self.m42 - self.m21*self.m34*self.m42 -
+ self.m22*self.m31*self.m44 + self.m21*self.m32*self.m44,
+
+ self.m14*self.m32*self.m41 - self.m12*self.m34*self.m41 -
+ self.m14*self.m31*self.m42 + self.m11*self.m34*self.m42 +
+ self.m12*self.m31*self.m44 - self.m11*self.m32*self.m44,
+
+ self.m12*self.m24*self.m41 - self.m14*self.m22*self.m41 +
+ self.m14*self.m21*self.m42 - self.m11*self.m24*self.m42 -
+ self.m12*self.m21*self.m44 + self.m11*self.m22*self.m44,
+
+ self.m14*self.m22*self.m31 - self.m12*self.m24*self.m31 -
+ self.m14*self.m21*self.m32 + self.m11*self.m24*self.m32 +
+ self.m12*self.m21*self.m34 - self.m11*self.m22*self.m34,
+
+ self.m23*self.m32*self.m41 - self.m22*self.m33*self.m41 -
+ self.m23*self.m31*self.m42 + self.m21*self.m33*self.m42 +
+ self.m22*self.m31*self.m43 - self.m21*self.m32*self.m43,
+
+ self.m12*self.m33*self.m41 - self.m13*self.m32*self.m41 +
+ self.m13*self.m31*self.m42 - self.m11*self.m33*self.m42 -
+ self.m12*self.m31*self.m43 + self.m11*self.m32*self.m43,
+
+ self.m13*self.m22*self.m41 - self.m12*self.m23*self.m41 -
+ self.m13*self.m21*self.m42 + self.m11*self.m23*self.m42 +
+ self.m12*self.m21*self.m43 - self.m11*self.m22*self.m43,
+
+ self.m12*self.m23*self.m31 - self.m13*self.m22*self.m31 +
+ self.m13*self.m21*self.m32 - self.m11*self.m23*self.m32 -
+ self.m12*self.m21*self.m33 + self.m11*self.m22*self.m33
+ );
+
+ let _1: T = One::one();
+ Some(m.mul_s(_1 / det))
+ }
+
+ /// Compute the determinant of the transform.
+ pub fn determinant(&self) -> T {
+ self.m14 * self.m23 * self.m32 * self.m41 -
+ self.m13 * self.m24 * self.m32 * self.m41 -
+ self.m14 * self.m22 * self.m33 * self.m41 +
+ self.m12 * self.m24 * self.m33 * self.m41 +
+ self.m13 * self.m22 * self.m34 * self.m41 -
+ self.m12 * self.m23 * self.m34 * self.m41 -
+ self.m14 * self.m23 * self.m31 * self.m42 +
+ self.m13 * self.m24 * self.m31 * self.m42 +
+ self.m14 * self.m21 * self.m33 * self.m42 -
+ self.m11 * self.m24 * self.m33 * self.m42 -
+ self.m13 * self.m21 * self.m34 * self.m42 +
+ self.m11 * self.m23 * self.m34 * self.m42 +
+ self.m14 * self.m22 * self.m31 * self.m43 -
+ self.m12 * self.m24 * self.m31 * self.m43 -
+ self.m14 * self.m21 * self.m32 * self.m43 +
+ self.m11 * self.m24 * self.m32 * self.m43 +
+ self.m12 * self.m21 * self.m34 * self.m43 -
+ self.m11 * self.m22 * self.m34 * self.m43 -
+ self.m13 * self.m22 * self.m31 * self.m44 +
+ self.m12 * self.m23 * self.m31 * self.m44 +
+ self.m13 * self.m21 * self.m32 * self.m44 -
+ self.m11 * self.m23 * self.m32 * self.m44 -
+ self.m12 * self.m21 * self.m33 * self.m44 +
+ self.m11 * self.m22 * self.m33 * self.m44
+ }
+
+ /// Multiplies all of the transform's component by a scalar and returns the result.
+ #[must_use]
+ pub fn mul_s(&self, x: T) -> Self {
+ Transform3D::new(
+ self.m11 * x, self.m12 * x, self.m13 * x, self.m14 * x,
+ self.m21 * x, self.m22 * x, self.m23 * x, self.m24 * x,
+ self.m31 * x, self.m32 * x, self.m33 * x, self.m34 * x,
+ self.m41 * x, self.m42 * x, self.m43 * x, self.m44 * x
+ )
+ }
+
+ /// Convenience function to create a scale transform from a `Scale`.
+ pub fn from_scale(scale: Scale<T, Src, Dst>) -> Self {
+ Transform3D::scale(scale.get(), scale.get(), scale.get())
+ }
+}
+
+impl <T, Src, Dst> Transform3D<T, Src, Dst>
+where
+ T: Copy + Mul<Output = T> + Div<Output = T> + Zero + One + PartialEq,
+{
+ /// Returns a projection of this transform in 2d space.
+ pub fn project_to_2d(&self) -> Self {
+ let (_0, _1): (T, T) = (Zero::zero(), One::one());
+
+ let mut result = self.clone();
+
+ result.m31 = _0;
+ result.m32 = _0;
+ result.m13 = _0;
+ result.m23 = _0;
+ result.m33 = _1;
+ result.m43 = _0;
+ result.m34 = _0;
+
+ // Try to normalize perspective when possible to convert to a 2d matrix.
+ // Some matrices, such as those derived from perspective transforms, can
+ // modify m44 from 1, while leaving the rest of the fourth column
+ // (m14, m24) at 0. In this case, after resetting the third row and
+ // third column above, the value of m44 functions only to scale the
+ // coordinate transform divide by W. The matrix can be converted to
+ // a true 2D matrix by normalizing out the scaling effect of m44 on
+ // the remaining components ahead of time.
+ if self.m14 == _0 && self.m24 == _0 && self.m44 != _0 && self.m44 != _1 {
+ let scale = _1 / self.m44;
+ result.m11 = result.m11 * scale;
+ result.m12 = result.m12 * scale;
+ result.m21 = result.m21 * scale;
+ result.m22 = result.m22 * scale;
+ result.m41 = result.m41 * scale;
+ result.m42 = result.m42 * scale;
+ result.m44 = _1;
+ }
+
+ result
+ }
+}
+
+impl<T: NumCast + Copy, Src, Dst> Transform3D<T, Src, Dst> {
+ /// Cast from one numeric representation to another, preserving the units.
+ #[inline]
+ pub fn cast<NewT: NumCast>(&self) -> Transform3D<NewT, Src, Dst> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ pub fn try_cast<NewT: NumCast>(&self) -> Option<Transform3D<NewT, Src, Dst>> {
+ match (NumCast::from(self.m11), NumCast::from(self.m12),
+ NumCast::from(self.m13), NumCast::from(self.m14),
+ NumCast::from(self.m21), NumCast::from(self.m22),
+ NumCast::from(self.m23), NumCast::from(self.m24),
+ NumCast::from(self.m31), NumCast::from(self.m32),
+ NumCast::from(self.m33), NumCast::from(self.m34),
+ NumCast::from(self.m41), NumCast::from(self.m42),
+ NumCast::from(self.m43), NumCast::from(self.m44)) {
+ (Some(m11), Some(m12), Some(m13), Some(m14),
+ Some(m21), Some(m22), Some(m23), Some(m24),
+ Some(m31), Some(m32), Some(m33), Some(m34),
+ Some(m41), Some(m42), Some(m43), Some(m44)) => {
+ Some(Transform3D::new(m11, m12, m13, m14,
+ m21, m22, m23, m24,
+ m31, m32, m33, m34,
+ m41, m42, m43, m44))
+ },
+ _ => None
+ }
+ }
+}
+
+impl<T: ApproxEq<T>, Src, Dst> Transform3D<T, Src, Dst> {
+ /// Returns true is this transform is approximately equal to the other one, using
+ /// T's default epsilon value.
+ ///
+ /// The same as [`ApproxEq::approx_eq()`] but available without importing trait.
+ ///
+ /// [`ApproxEq::approx_eq()`]: ./approxeq/trait.ApproxEq.html#method.approx_eq
+ #[inline]
+ pub fn approx_eq(&self, other: &Self) -> bool {
+ <Self as ApproxEq<T>>::approx_eq(&self, &other)
+ }
+
+ /// Returns true is this transform is approximately equal to the other one, using
+ /// a provided epsilon value.
+ ///
+ /// The same as [`ApproxEq::approx_eq_eps()`] but available without importing trait.
+ ///
+ /// [`ApproxEq::approx_eq_eps()`]: ./approxeq/trait.ApproxEq.html#method.approx_eq_eps
+ #[inline]
+ pub fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
+ <Self as ApproxEq<T>>::approx_eq_eps(&self, &other, &eps)
+ }
+}
+
+
+impl<T: ApproxEq<T>, Src, Dst> ApproxEq<T> for Transform3D<T, Src, Dst> {
+ #[inline]
+ fn approx_epsilon() -> T { T::approx_epsilon() }
+
+ fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
+ self.m11.approx_eq_eps(&other.m11, eps) && self.m12.approx_eq_eps(&other.m12, eps) &&
+ self.m13.approx_eq_eps(&other.m13, eps) && self.m14.approx_eq_eps(&other.m14, eps) &&
+ self.m21.approx_eq_eps(&other.m21, eps) && self.m22.approx_eq_eps(&other.m22, eps) &&
+ self.m23.approx_eq_eps(&other.m23, eps) && self.m24.approx_eq_eps(&other.m24, eps) &&
+ self.m31.approx_eq_eps(&other.m31, eps) && self.m32.approx_eq_eps(&other.m32, eps) &&
+ self.m33.approx_eq_eps(&other.m33, eps) && self.m34.approx_eq_eps(&other.m34, eps) &&
+ self.m41.approx_eq_eps(&other.m41, eps) && self.m42.approx_eq_eps(&other.m42, eps) &&
+ self.m43.approx_eq_eps(&other.m43, eps) && self.m44.approx_eq_eps(&other.m44, eps)
+ }
+}
+
+impl <T, Src, Dst> Default for Transform3D<T, Src, Dst>
+ where T: Zero + One
+{
+ /// Returns the [identity transform](#method.identity).
+ fn default() -> Self {
+ Self::identity()
+ }
+}
+
+impl<T, Src, Dst> fmt::Debug for Transform3D<T, Src, Dst>
+where T: Copy + fmt::Debug +
+ PartialEq +
+ One + Zero {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.is_identity() {
+ write!(f, "[I]")
+ } else {
+ self.to_array().fmt(f)
+ }
+ }
+}
+
+#[cfg(feature = "mint")]
+impl<T, Src, Dst> From<mint::RowMatrix4<T>> for Transform3D<T, Src, Dst> {
+ fn from(m: mint::RowMatrix4<T>) -> Self {
+ Transform3D {
+ m11: m.x.x, m12: m.x.y, m13: m.x.z, m14: m.x.w,
+ m21: m.y.x, m22: m.y.y, m23: m.y.z, m24: m.y.w,
+ m31: m.z.x, m32: m.z.y, m33: m.z.z, m34: m.z.w,
+ m41: m.w.x, m42: m.w.y, m43: m.w.z, m44: m.w.w,
+ _unit: PhantomData,
+ }
+ }
+}
+#[cfg(feature = "mint")]
+impl<T, Src, Dst> Into<mint::RowMatrix4<T>> for Transform3D<T, Src, Dst> {
+ fn into(self) -> mint::RowMatrix4<T> {
+ mint::RowMatrix4 {
+ x: mint::Vector4 { x: self.m11, y: self.m12, z: self.m13, w: self.m14 },
+ y: mint::Vector4 { x: self.m21, y: self.m22, z: self.m23, w: self.m24 },
+ z: mint::Vector4 { x: self.m31, y: self.m32, z: self.m33, w: self.m34 },
+ w: mint::Vector4 { x: self.m41, y: self.m42, z: self.m43, w: self.m44 },
+ }
+ }
+}
+
+
+#[cfg(test)]
+mod tests {
+ use crate::approxeq::ApproxEq;
+ use super::*;
+ use crate::{point2, point3};
+ use crate::default;
+
+ use core::f32::consts::{FRAC_PI_2, PI};
+
+ type Mf32 = default::Transform3D<f32>;
+
+ // For convenience.
+ fn rad(v: f32) -> Angle<f32> { Angle::radians(v) }
+
+ #[test]
+ pub fn test_translation() {
+ let t1 = Mf32::translation(1.0, 2.0, 3.0);
+ let t2 = Mf32::identity().pre_translate(vec3(1.0, 2.0, 3.0));
+ let t3 = Mf32::identity().then_translate(vec3(1.0, 2.0, 3.0));
+ assert_eq!(t1, t2);
+ assert_eq!(t1, t3);
+
+ assert_eq!(t1.transform_point3d(point3(1.0, 1.0, 1.0)), Some(point3(2.0, 3.0, 4.0)));
+ assert_eq!(t1.transform_point2d(point2(1.0, 1.0)), Some(point2(2.0, 3.0)));
+
+ assert_eq!(t1.then(&t1), Mf32::translation(2.0, 4.0, 6.0));
+
+ assert!(!t1.is_2d());
+ assert_eq!(Mf32::translation(1.0, 2.0, 3.0).to_2d(), Transform2D::translation(1.0, 2.0));
+ }
+
+ #[test]
+ pub fn test_rotation() {
+ let r1 = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2));
+ let r2 = Mf32::identity().pre_rotate(0.0, 0.0, 1.0, rad(FRAC_PI_2));
+ let r3 = Mf32::identity().then_rotate(0.0, 0.0, 1.0, rad(FRAC_PI_2));
+ assert_eq!(r1, r2);
+ assert_eq!(r1, r3);
+
+ assert!(r1.transform_point3d(point3(1.0, 2.0, 3.0)).unwrap().approx_eq(&point3(-2.0, 1.0, 3.0)));
+ assert!(r1.transform_point2d(point2(1.0, 2.0)).unwrap().approx_eq(&point2(-2.0, 1.0)));
+
+ assert!(r1.then(&r1).approx_eq(&Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2*2.0))));
+
+ assert!(r1.is_2d());
+ assert!(r1.to_2d().approx_eq(&Transform2D::rotation(rad(FRAC_PI_2))));
+ }
+
+ #[test]
+ pub fn test_scale() {
+ let s1 = Mf32::scale(2.0, 3.0, 4.0);
+ let s2 = Mf32::identity().pre_scale(2.0, 3.0, 4.0);
+ let s3 = Mf32::identity().then_scale(2.0, 3.0, 4.0);
+ assert_eq!(s1, s2);
+ assert_eq!(s1, s3);
+
+ assert!(s1.transform_point3d(point3(2.0, 2.0, 2.0)).unwrap().approx_eq(&point3(4.0, 6.0, 8.0)));
+ assert!(s1.transform_point2d(point2(2.0, 2.0)).unwrap().approx_eq(&point2(4.0, 6.0)));
+
+ assert_eq!(s1.then(&s1), Mf32::scale(4.0, 9.0, 16.0));
+
+ assert!(!s1.is_2d());
+ assert_eq!(Mf32::scale(2.0, 3.0, 0.0).to_2d(), Transform2D::scale(2.0, 3.0));
+ }
+
+
+ #[test]
+ pub fn test_pre_then_scale() {
+ let m = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2)).then_translate(vec3(6.0, 7.0, 8.0));
+ let s = Mf32::scale(2.0, 3.0, 4.0);
+ assert_eq!(m.then(&s), m.then_scale(2.0, 3.0, 4.0));
+ }
+
+
+ #[test]
+ pub fn test_ortho() {
+ let (left, right, bottom, top) = (0.0f32, 1.0f32, 0.1f32, 1.0f32);
+ let (near, far) = (-1.0f32, 1.0f32);
+ let result = Mf32::ortho(left, right, bottom, top, near, far);
+ let expected = Mf32::new(
+ 2.0, 0.0, 0.0, 0.0,
+ 0.0, 2.22222222, 0.0, 0.0,
+ 0.0, 0.0, -1.0, 0.0,
+ -1.0, -1.22222222, -0.0, 1.0
+ );
+ assert!(result.approx_eq(&expected));
+ }
+
+ #[test]
+ pub fn test_is_2d() {
+ assert!(Mf32::identity().is_2d());
+ assert!(Mf32::rotation(0.0, 0.0, 1.0, rad(0.7854)).is_2d());
+ assert!(!Mf32::rotation(0.0, 1.0, 0.0, rad(0.7854)).is_2d());
+ }
+
+ #[test]
+ pub fn test_new_2d() {
+ let m1 = Mf32::new_2d(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
+ let m2 = Mf32::new(
+ 1.0, 2.0, 0.0, 0.0,
+ 3.0, 4.0, 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ 5.0, 6.0, 0.0, 1.0
+ );
+ assert_eq!(m1, m2);
+ }
+
+ #[test]
+ pub fn test_inverse_simple() {
+ let m1 = Mf32::identity();
+ let m2 = m1.inverse().unwrap();
+ assert!(m1.approx_eq(&m2));
+ }
+
+ #[test]
+ pub fn test_inverse_scale() {
+ let m1 = Mf32::scale(1.5, 0.3, 2.1);
+ let m2 = m1.inverse().unwrap();
+ assert!(m1.then(&m2).approx_eq(&Mf32::identity()));
+ assert!(m2.then(&m1).approx_eq(&Mf32::identity()));
+ }
+
+ #[test]
+ pub fn test_inverse_translate() {
+ let m1 = Mf32::translation(-132.0, 0.3, 493.0);
+ let m2 = m1.inverse().unwrap();
+ assert!(m1.then(&m2).approx_eq(&Mf32::identity()));
+ assert!(m2.then(&m1).approx_eq(&Mf32::identity()));
+ }
+
+ #[test]
+ pub fn test_inverse_rotate() {
+ let m1 = Mf32::rotation(0.0, 1.0, 0.0, rad(1.57));
+ let m2 = m1.inverse().unwrap();
+ assert!(m1.then(&m2).approx_eq(&Mf32::identity()));
+ assert!(m2.then(&m1).approx_eq(&Mf32::identity()));
+ }
+
+ #[test]
+ pub fn test_inverse_transform_point_2d() {
+ let m1 = Mf32::translation(100.0, 200.0, 0.0);
+ let m2 = m1.inverse().unwrap();
+ assert!(m1.then(&m2).approx_eq(&Mf32::identity()));
+ assert!(m2.then(&m1).approx_eq(&Mf32::identity()));
+
+ let p1 = point2(1000.0, 2000.0);
+ let p2 = m1.transform_point2d(p1);
+ assert_eq!(p2, Some(point2(1100.0, 2200.0)));
+
+ let p3 = m2.transform_point2d(p2.unwrap());
+ assert_eq!(p3, Some(p1));
+ }
+
+ #[test]
+ fn test_inverse_none() {
+ assert!(Mf32::scale(2.0, 0.0, 2.0).inverse().is_none());
+ assert!(Mf32::scale(2.0, 2.0, 2.0).inverse().is_some());
+ }
+
+ #[test]
+ pub fn test_pre_post() {
+ let m1 = default::Transform3D::identity().then_scale(1.0, 2.0, 3.0).then_translate(vec3(1.0, 2.0, 3.0));
+ let m2 = default::Transform3D::identity().pre_translate(vec3(1.0, 2.0, 3.0)).pre_scale(1.0, 2.0, 3.0);
+ assert!(m1.approx_eq(&m2));
+
+ let r = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2));
+ let t = Mf32::translation(2.0, 3.0, 0.0);
+
+ let a = point3(1.0, 1.0, 1.0);
+
+ assert!(r.then(&t).transform_point3d(a).unwrap().approx_eq(&point3(1.0, 4.0, 1.0)));
+ assert!(t.then(&r).transform_point3d(a).unwrap().approx_eq(&point3(-4.0, 3.0, 1.0)));
+ assert!(t.then(&r).transform_point3d(a).unwrap().approx_eq(&r.transform_point3d(t.transform_point3d(a).unwrap()).unwrap()));
+ }
+
+ #[test]
+ fn test_size_of() {
+ use core::mem::size_of;
+ assert_eq!(size_of::<default::Transform3D<f32>>(), 16*size_of::<f32>());
+ assert_eq!(size_of::<default::Transform3D<f64>>(), 16*size_of::<f64>());
+ }
+
+ #[test]
+ pub fn test_transform_associativity() {
+ let m1 = Mf32::new(3.0, 2.0, 1.5, 1.0,
+ 0.0, 4.5, -1.0, -4.0,
+ 0.0, 3.5, 2.5, 40.0,
+ 0.0, 3.0, 0.0, 1.0);
+ let m2 = Mf32::new(1.0, -1.0, 3.0, 0.0,
+ -1.0, 0.5, 0.0, 2.0,
+ 1.5, -2.0, 6.0, 0.0,
+ -2.5, 6.0, 1.0, 1.0);
+
+ let p = point3(1.0, 3.0, 5.0);
+ let p1 = m1.then(&m2).transform_point3d(p).unwrap();
+ let p2 = m2.transform_point3d(m1.transform_point3d(p).unwrap()).unwrap();
+ assert!(p1.approx_eq(&p2));
+ }
+
+ #[test]
+ pub fn test_is_identity() {
+ let m1 = default::Transform3D::identity();
+ assert!(m1.is_identity());
+ let m2 = m1.then_translate(vec3(0.1, 0.0, 0.0));
+ assert!(!m2.is_identity());
+ }
+
+ #[test]
+ pub fn test_transform_vector() {
+ // Translation does not apply to vectors.
+ let m = Mf32::translation(1.0, 2.0, 3.0);
+ let v1 = vec3(10.0, -10.0, 3.0);
+ assert_eq!(v1, m.transform_vector3d(v1));
+ // While it does apply to points.
+ assert_ne!(Some(v1.to_point()), m.transform_point3d(v1.to_point()));
+
+ // same thing with 2d vectors/points
+ let v2 = vec2(10.0, -5.0);
+ assert_eq!(v2, m.transform_vector2d(v2));
+ assert_ne!(Some(v2.to_point()), m.transform_point2d(v2.to_point()));
+ }
+
+ #[test]
+ pub fn test_is_backface_visible() {
+ // backface is not visible for rotate-x 0 degree.
+ let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(0.0));
+ assert!(!r1.is_backface_visible());
+ // backface is not visible for rotate-x 45 degree.
+ let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(PI * 0.25));
+ assert!(!r1.is_backface_visible());
+ // backface is visible for rotate-x 180 degree.
+ let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(PI));
+ assert!(r1.is_backface_visible());
+ // backface is visible for rotate-x 225 degree.
+ let r1 = Mf32::rotation(1.0, 0.0, 0.0, rad(PI * 1.25));
+ assert!(r1.is_backface_visible());
+ // backface is not visible for non-inverseable matrix
+ let r1 = Mf32::scale(2.0, 0.0, 2.0);
+ assert!(!r1.is_backface_visible());
+ }
+
+ #[test]
+ pub fn test_homogeneous() {
+ let m = Mf32::new(
+ 1.0, 2.0, 0.5, 5.0,
+ 3.0, 4.0, 0.25, 6.0,
+ 0.5, -1.0, 1.0, -1.0,
+ -1.0, 1.0, -1.0, 2.0,
+ );
+ assert_eq!(
+ m.transform_point2d_homogeneous(point2(1.0, 2.0)),
+ HomogeneousVector::new(6.0, 11.0, 0.0, 19.0),
+ );
+ assert_eq!(
+ m.transform_point3d_homogeneous(point3(1.0, 2.0, 4.0)),
+ HomogeneousVector::new(8.0, 7.0, 4.0, 15.0),
+ );
+ }
+
+ #[test]
+ pub fn test_perspective_division() {
+ let p = point2(1.0, 2.0);
+ let mut m = Mf32::identity();
+ assert!(m.transform_point2d(p).is_some());
+ m.m44 = 0.0;
+ assert_eq!(None, m.transform_point2d(p));
+ m.m44 = 1.0;
+ m.m24 = -1.0;
+ assert_eq!(None, m.transform_point2d(p));
+ }
+
+ #[cfg(feature = "mint")]
+ #[test]
+ pub fn test_mint() {
+ let m1 = Mf32::rotation(0.0, 0.0, 1.0, rad(FRAC_PI_2));
+ let mm: mint::RowMatrix4<_> = m1.into();
+ let m2 = Mf32::from(mm);
+
+ assert_eq!(m1, m2);
+ }
+}
diff --git a/third_party/rust/euclid/src/translation.rs b/third_party/rust/euclid/src/translation.rs
new file mode 100644
index 0000000000..45126e8d44
--- /dev/null
+++ b/third_party/rust/euclid/src/translation.rs
@@ -0,0 +1,867 @@
+// Copyright 2018 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::num::*;
+use crate::UnknownUnit;
+use crate::{point2, point3, vec2, vec3, Box2D, Box3D, Rect, Size2D};
+use crate::{Point2D, Point3D, Transform2D, Transform3D, Vector2D, Vector3D};
+use core::cmp::{Eq, PartialEq};
+use core::fmt;
+use core::hash::Hash;
+use core::marker::PhantomData;
+use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A 2d transformation from a space to another that can only express translations.
+///
+/// The main benefit of this type over a Vector2D is the ability to cast
+/// between a source and a destination spaces.
+///
+/// Example:
+///
+/// ```
+/// use euclid::{Translation2D, Point2D, point2};
+/// struct ParentSpace;
+/// struct ChildSpace;
+/// type ScrollOffset = Translation2D<i32, ParentSpace, ChildSpace>;
+/// type ParentPoint = Point2D<i32, ParentSpace>;
+/// type ChildPoint = Point2D<i32, ChildSpace>;
+///
+/// let scrolling = ScrollOffset::new(0, 100);
+/// let p1: ParentPoint = point2(0, 0);
+/// let p2: ChildPoint = scrolling.transform_point(p1);
+/// ```
+///
+#[repr(C)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(
+ feature = "serde",
+ serde(bound(
+ serialize = "T: serde::Serialize",
+ deserialize = "T: serde::Deserialize<'de>"
+ ))
+)]
+pub struct Translation2D<T, Src, Dst> {
+ pub x: T,
+ pub y: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<(Src, Dst)>,
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Translation2D<T, Src, Dst>
+where
+ T: arbitrary::Arbitrary<'a>,
+{
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>
+ {
+ let (x, y) = arbitrary::Arbitrary::arbitrary(u)?;
+ Ok(Translation2D {
+ x,
+ y,
+ _unit: PhantomData,
+ })
+ }
+}
+
+impl<T: Copy, Src, Dst> Copy for Translation2D<T, Src, Dst> {}
+
+impl<T: Clone, Src, Dst> Clone for Translation2D<T, Src, Dst> {
+ fn clone(&self) -> Self {
+ Translation2D {
+ x: self.x.clone(),
+ y: self.y.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+impl<T, Src, Dst> Eq for Translation2D<T, Src, Dst> where T: Eq {}
+
+impl<T, Src, Dst> PartialEq for Translation2D<T, Src, Dst>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.x == other.x && self.y == other.y
+ }
+}
+
+impl<T, Src, Dst> Hash for Translation2D<T, Src, Dst>
+where
+ T: Hash,
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.x.hash(h);
+ self.y.hash(h);
+ }
+}
+
+impl<T, Src, Dst> Translation2D<T, Src, Dst> {
+ #[inline]
+ pub const fn new(x: T, y: T) -> Self {
+ Translation2D {
+ x,
+ y,
+ _unit: PhantomData,
+ }
+ }
+
+ #[inline]
+ pub fn splat(v: T) -> Self
+ where
+ T: Clone,
+ {
+ Translation2D {
+ x: v.clone(),
+ y: v,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Creates no-op translation (both `x` and `y` is `zero()`).
+ #[inline]
+ pub fn identity() -> Self
+ where
+ T: Zero,
+ {
+ Self::new(T::zero(), T::zero())
+ }
+
+ /// Check if translation does nothing (both x and y is `zero()`).
+ ///
+ /// ```rust
+ /// use euclid::default::Translation2D;
+ ///
+ /// assert_eq!(Translation2D::<f32>::identity().is_identity(), true);
+ /// assert_eq!(Translation2D::new(0, 0).is_identity(), true);
+ /// assert_eq!(Translation2D::new(1, 0).is_identity(), false);
+ /// assert_eq!(Translation2D::new(0, 1).is_identity(), false);
+ /// ```
+ #[inline]
+ pub fn is_identity(&self) -> bool
+ where
+ T: Zero + PartialEq,
+ {
+ let _0 = T::zero();
+ self.x == _0 && self.y == _0
+ }
+
+ /// No-op, just cast the unit.
+ #[inline]
+ pub fn transform_size(&self, s: Size2D<T, Src>) -> Size2D<T, Dst> {
+ Size2D::new(s.width, s.height)
+ }
+}
+
+impl<T: Copy, Src, Dst> Translation2D<T, Src, Dst> {
+ /// Cast into a 2D vector.
+ #[inline]
+ pub fn to_vector(&self) -> Vector2D<T, Src> {
+ vec2(self.x, self.y)
+ }
+
+ /// Cast into an array with x and y.
+ #[inline]
+ pub fn to_array(&self) -> [T; 2] {
+ [self.x, self.y]
+ }
+
+ /// Cast into a tuple with x and y.
+ #[inline]
+ pub fn to_tuple(&self) -> (T, T) {
+ (self.x, self.y)
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(&self) -> Translation2D<T, UnknownUnit, UnknownUnit> {
+ Translation2D {
+ x: self.x,
+ y: self.y,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(t: &Translation2D<T, UnknownUnit, UnknownUnit>) -> Self {
+ Translation2D {
+ x: t.x,
+ y: t.y,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Returns the matrix representation of this translation.
+ #[inline]
+ pub fn to_transform(&self) -> Transform2D<T, Src, Dst>
+ where
+ T: Zero + One,
+ {
+ (*self).into()
+ }
+
+ /// Translate a point and cast its unit.
+ #[inline]
+ pub fn transform_point(&self, p: Point2D<T, Src>) -> Point2D<T::Output, Dst>
+ where
+ T: Add,
+ {
+ point2(p.x + self.x, p.y + self.y)
+ }
+
+ /// Translate a rectangle and cast its unit.
+ #[inline]
+ pub fn transform_rect(&self, r: &Rect<T, Src>) -> Rect<T::Output, Dst>
+ where
+ T: Add<Output = T>,
+ {
+ Rect {
+ origin: self.transform_point(r.origin),
+ size: self.transform_size(r.size),
+ }
+ }
+
+ /// Translate a 2D box and cast its unit.
+ #[inline]
+ pub fn transform_box(&self, r: &Box2D<T, Src>) -> Box2D<T::Output, Dst>
+ where
+ T: Add,
+ {
+ Box2D {
+ min: self.transform_point(r.min),
+ max: self.transform_point(r.max),
+ }
+ }
+
+ /// Return the inverse transformation.
+ #[inline]
+ pub fn inverse(&self) -> Translation2D<T::Output, Dst, Src>
+ where
+ T: Neg,
+ {
+ Translation2D::new(-self.x, -self.y)
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, Src, Dst> Zeroable for Translation2D<T, Src, Dst> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Translation2D<T, Src, Dst> {}
+
+impl<T: Add, Src, Dst1, Dst2> Add<Translation2D<T, Dst1, Dst2>> for Translation2D<T, Src, Dst1> {
+ type Output = Translation2D<T::Output, Src, Dst2>;
+
+ fn add(self, other: Translation2D<T, Dst1, Dst2>) -> Self::Output {
+ Translation2D::new(self.x + other.x, self.y + other.y)
+ }
+}
+
+impl<T: AddAssign, Src, Dst> AddAssign<Translation2D<T, Dst, Dst>> for Translation2D<T, Src, Dst> {
+ fn add_assign(&mut self, other: Translation2D<T, Dst, Dst>) {
+ self.x += other.x;
+ self.y += other.y;
+ }
+}
+
+impl<T: Sub, Src, Dst1, Dst2> Sub<Translation2D<T, Dst1, Dst2>> for Translation2D<T, Src, Dst2> {
+ type Output = Translation2D<T::Output, Src, Dst1>;
+
+ fn sub(self, other: Translation2D<T, Dst1, Dst2>) -> Self::Output {
+ Translation2D::new(self.x - other.x, self.y - other.y)
+ }
+}
+
+impl<T: SubAssign, Src, Dst> SubAssign<Translation2D<T, Dst, Dst>> for Translation2D<T, Src, Dst> {
+ fn sub_assign(&mut self, other: Translation2D<T, Dst, Dst>) {
+ self.x -= other.x;
+ self.y -= other.y;
+ }
+}
+
+impl<T, Src, Dst> From<Vector2D<T, Src>> for Translation2D<T, Src, Dst> {
+ fn from(v: Vector2D<T, Src>) -> Self {
+ Translation2D::new(v.x, v.y)
+ }
+}
+
+impl<T, Src, Dst> Into<Vector2D<T, Src>> for Translation2D<T, Src, Dst> {
+ fn into(self) -> Vector2D<T, Src> {
+ vec2(self.x, self.y)
+ }
+}
+
+impl<T, Src, Dst> Into<Transform2D<T, Src, Dst>> for Translation2D<T, Src, Dst>
+where
+ T: Zero + One,
+{
+ fn into(self) -> Transform2D<T, Src, Dst> {
+ Transform2D::translation(self.x, self.y)
+ }
+}
+
+impl<T, Src, Dst> Default for Translation2D<T, Src, Dst>
+where
+ T: Zero,
+{
+ fn default() -> Self {
+ Self::identity()
+ }
+}
+
+impl<T: fmt::Debug, Src, Dst> fmt::Debug for Translation2D<T, Src, Dst> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Translation({:?},{:?})", self.x, self.y)
+ }
+}
+
+/// A 3d transformation from a space to another that can only express translations.
+///
+/// The main benefit of this type over a Vector3D is the ability to cast
+/// between a source and a destination spaces.
+#[repr(C)]
+pub struct Translation3D<T, Src, Dst> {
+ pub x: T,
+ pub y: T,
+ pub z: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<(Src, Dst)>,
+}
+
+impl<T: Copy, Src, Dst> Copy for Translation3D<T, Src, Dst> {}
+
+impl<T: Clone, Src, Dst> Clone for Translation3D<T, Src, Dst> {
+ fn clone(&self) -> Self {
+ Translation3D {
+ x: self.x.clone(),
+ y: self.y.clone(),
+ z: self.z.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de, T, Src, Dst> serde::Deserialize<'de> for Translation3D<T, Src, Dst>
+where
+ T: serde::Deserialize<'de>,
+{
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let (x, y, z) = serde::Deserialize::deserialize(deserializer)?;
+ Ok(Translation3D {
+ x,
+ y,
+ z,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<T, Src, Dst> serde::Serialize for Translation3D<T, Src, Dst>
+where
+ T: serde::Serialize,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ (&self.x, &self.y, &self.z).serialize(serializer)
+ }
+}
+
+impl<T, Src, Dst> Eq for Translation3D<T, Src, Dst> where T: Eq {}
+
+impl<T, Src, Dst> PartialEq for Translation3D<T, Src, Dst>
+where
+ T: PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.x == other.x && self.y == other.y && self.z == other.z
+ }
+}
+
+impl<T, Src, Dst> Hash for Translation3D<T, Src, Dst>
+where
+ T: Hash,
+{
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.x.hash(h);
+ self.y.hash(h);
+ self.z.hash(h);
+ }
+}
+
+impl<T, Src, Dst> Translation3D<T, Src, Dst> {
+ #[inline]
+ pub const fn new(x: T, y: T, z: T) -> Self {
+ Translation3D {
+ x,
+ y,
+ z,
+ _unit: PhantomData,
+ }
+ }
+
+ #[inline]
+ pub fn splat(v: T) -> Self
+ where
+ T: Clone,
+ {
+ Translation3D {
+ x: v.clone(),
+ y: v.clone(),
+ z: v,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Creates no-op translation (`x`, `y` and `z` is `zero()`).
+ #[inline]
+ pub fn identity() -> Self
+ where
+ T: Zero,
+ {
+ Translation3D::new(T::zero(), T::zero(), T::zero())
+ }
+
+ /// Check if translation does nothing (`x`, `y` and `z` is `zero()`).
+ ///
+ /// ```rust
+ /// use euclid::default::Translation3D;
+ ///
+ /// assert_eq!(Translation3D::<f32>::identity().is_identity(), true);
+ /// assert_eq!(Translation3D::new(0, 0, 0).is_identity(), true);
+ /// assert_eq!(Translation3D::new(1, 0, 0).is_identity(), false);
+ /// assert_eq!(Translation3D::new(0, 1, 0).is_identity(), false);
+ /// assert_eq!(Translation3D::new(0, 0, 1).is_identity(), false);
+ /// ```
+ #[inline]
+ pub fn is_identity(&self) -> bool
+ where
+ T: Zero + PartialEq,
+ {
+ let _0 = T::zero();
+ self.x == _0 && self.y == _0 && self.z == _0
+ }
+
+ /// No-op, just cast the unit.
+ #[inline]
+ pub fn transform_size(self, s: Size2D<T, Src>) -> Size2D<T, Dst> {
+ Size2D::new(s.width, s.height)
+ }
+}
+
+impl<T: Copy, Src, Dst> Translation3D<T, Src, Dst> {
+ /// Cast into a 3D vector.
+ #[inline]
+ pub fn to_vector(&self) -> Vector3D<T, Src> {
+ vec3(self.x, self.y, self.z)
+ }
+
+ /// Cast into an array with x, y and z.
+ #[inline]
+ pub fn to_array(&self) -> [T; 3] {
+ [self.x, self.y, self.z]
+ }
+
+ /// Cast into a tuple with x, y and z.
+ #[inline]
+ pub fn to_tuple(&self) -> (T, T, T) {
+ (self.x, self.y, self.z)
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(&self) -> Translation3D<T, UnknownUnit, UnknownUnit> {
+ Translation3D {
+ x: self.x,
+ y: self.y,
+ z: self.z,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(t: &Translation3D<T, UnknownUnit, UnknownUnit>) -> Self {
+ Translation3D {
+ x: t.x,
+ y: t.y,
+ z: t.z,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Returns the matrix representation of this translation.
+ #[inline]
+ pub fn to_transform(&self) -> Transform3D<T, Src, Dst>
+ where
+ T: Zero + One,
+ {
+ (*self).into()
+ }
+
+ /// Translate a point and cast its unit.
+ #[inline]
+ pub fn transform_point3d(&self, p: &Point3D<T, Src>) -> Point3D<T::Output, Dst>
+ where
+ T: Add,
+ {
+ point3(p.x + self.x, p.y + self.y, p.z + self.z)
+ }
+
+ /// Translate a point and cast its unit.
+ #[inline]
+ pub fn transform_point2d(&self, p: &Point2D<T, Src>) -> Point2D<T::Output, Dst>
+ where
+ T: Add,
+ {
+ point2(p.x + self.x, p.y + self.y)
+ }
+
+ /// Translate a 2D box and cast its unit.
+ #[inline]
+ pub fn transform_box2d(&self, b: &Box2D<T, Src>) -> Box2D<T::Output, Dst>
+ where
+ T: Add,
+ {
+ Box2D {
+ min: self.transform_point2d(&b.min),
+ max: self.transform_point2d(&b.max),
+ }
+ }
+
+ /// Translate a 3D box and cast its unit.
+ #[inline]
+ pub fn transform_box3d(&self, b: &Box3D<T, Src>) -> Box3D<T::Output, Dst>
+ where
+ T: Add,
+ {
+ Box3D {
+ min: self.transform_point3d(&b.min),
+ max: self.transform_point3d(&b.max),
+ }
+ }
+
+ /// Translate a rectangle and cast its unit.
+ #[inline]
+ pub fn transform_rect(&self, r: &Rect<T, Src>) -> Rect<T, Dst>
+ where
+ T: Add<Output = T>,
+ {
+ Rect {
+ origin: self.transform_point2d(&r.origin),
+ size: self.transform_size(r.size),
+ }
+ }
+
+ /// Return the inverse transformation.
+ #[inline]
+ pub fn inverse(&self) -> Translation3D<T::Output, Dst, Src>
+ where
+ T: Neg,
+ {
+ Translation3D::new(-self.x, -self.y, -self.z)
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, Src, Dst> Zeroable for Translation3D<T, Src, Dst> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Translation3D<T, Src, Dst> {}
+
+impl<T: Add, Src, Dst1, Dst2> Add<Translation3D<T, Dst1, Dst2>> for Translation3D<T, Src, Dst1> {
+ type Output = Translation3D<T::Output, Src, Dst2>;
+
+ fn add(self, other: Translation3D<T, Dst1, Dst2>) -> Self::Output {
+ Translation3D::new(self.x + other.x, self.y + other.y, self.z + other.z)
+ }
+}
+
+impl<T: AddAssign, Src, Dst> AddAssign<Translation3D<T, Dst, Dst>> for Translation3D<T, Src, Dst> {
+ fn add_assign(&mut self, other: Translation3D<T, Dst, Dst>) {
+ self.x += other.x;
+ self.y += other.y;
+ self.z += other.z;
+ }
+}
+
+impl<T: Sub, Src, Dst1, Dst2> Sub<Translation3D<T, Dst1, Dst2>> for Translation3D<T, Src, Dst2> {
+ type Output = Translation3D<T::Output, Src, Dst1>;
+
+ fn sub(self, other: Translation3D<T, Dst1, Dst2>) -> Self::Output {
+ Translation3D::new(self.x - other.x, self.y - other.y, self.z - other.z)
+ }
+}
+
+impl<T: SubAssign, Src, Dst> SubAssign<Translation3D<T, Dst, Dst>> for Translation3D<T, Src, Dst> {
+ fn sub_assign(&mut self, other: Translation3D<T, Dst, Dst>) {
+ self.x -= other.x;
+ self.y -= other.y;
+ self.z -= other.z;
+ }
+}
+
+impl<T, Src, Dst> From<Vector3D<T, Src>> for Translation3D<T, Src, Dst> {
+ fn from(v: Vector3D<T, Src>) -> Self {
+ Translation3D::new(v.x, v.y, v.z)
+ }
+}
+
+impl<T, Src, Dst> Into<Vector3D<T, Src>> for Translation3D<T, Src, Dst> {
+ fn into(self) -> Vector3D<T, Src> {
+ vec3(self.x, self.y, self.z)
+ }
+}
+
+impl<T, Src, Dst> Into<Transform3D<T, Src, Dst>> for Translation3D<T, Src, Dst>
+where
+ T: Zero + One,
+{
+ fn into(self) -> Transform3D<T, Src, Dst> {
+ Transform3D::translation(self.x, self.y, self.z)
+ }
+}
+
+impl<T, Src, Dst> Default for Translation3D<T, Src, Dst>
+where
+ T: Zero,
+{
+ fn default() -> Self {
+ Self::identity()
+ }
+}
+
+impl<T: fmt::Debug, Src, Dst> fmt::Debug for Translation3D<T, Src, Dst> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Translation({:?},{:?},{:?})", self.x, self.y, self.z)
+ }
+}
+
+#[cfg(test)]
+mod _2d {
+ #[test]
+ fn simple() {
+ use crate::{rect, Rect, Translation2D};
+
+ struct A;
+ struct B;
+
+ type Translation = Translation2D<i32, A, B>;
+ type SrcRect = Rect<i32, A>;
+ type DstRect = Rect<i32, B>;
+
+ let tx = Translation::new(10, -10);
+ let r1: SrcRect = rect(10, 20, 30, 40);
+ let r2: DstRect = tx.transform_rect(&r1);
+ assert_eq!(r2, rect(20, 10, 30, 40));
+
+ let inv_tx = tx.inverse();
+ assert_eq!(inv_tx.transform_rect(&r2), r1);
+
+ assert!((tx + inv_tx).is_identity());
+ }
+
+ /// Operation tests
+ mod ops {
+ use crate::default::Translation2D;
+
+ #[test]
+ fn test_add() {
+ let t1 = Translation2D::new(1.0, 2.0);
+ let t2 = Translation2D::new(3.0, 4.0);
+ assert_eq!(t1 + t2, Translation2D::new(4.0, 6.0));
+
+ let t1 = Translation2D::new(1.0, 2.0);
+ let t2 = Translation2D::new(0.0, 0.0);
+ assert_eq!(t1 + t2, Translation2D::new(1.0, 2.0));
+
+ let t1 = Translation2D::new(1.0, 2.0);
+ let t2 = Translation2D::new(-3.0, -4.0);
+ assert_eq!(t1 + t2, Translation2D::new(-2.0, -2.0));
+
+ let t1 = Translation2D::new(0.0, 0.0);
+ let t2 = Translation2D::new(0.0, 0.0);
+ assert_eq!(t1 + t2, Translation2D::new(0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_add_assign() {
+ let mut t = Translation2D::new(1.0, 2.0);
+ t += Translation2D::new(3.0, 4.0);
+ assert_eq!(t, Translation2D::new(4.0, 6.0));
+
+ let mut t = Translation2D::new(1.0, 2.0);
+ t += Translation2D::new(0.0, 0.0);
+ assert_eq!(t, Translation2D::new(1.0, 2.0));
+
+ let mut t = Translation2D::new(1.0, 2.0);
+ t += Translation2D::new(-3.0, -4.0);
+ assert_eq!(t, Translation2D::new(-2.0, -2.0));
+
+ let mut t = Translation2D::new(0.0, 0.0);
+ t += Translation2D::new(0.0, 0.0);
+ assert_eq!(t, Translation2D::new(0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_sub() {
+ let t1 = Translation2D::new(1.0, 2.0);
+ let t2 = Translation2D::new(3.0, 4.0);
+ assert_eq!(t1 - t2, Translation2D::new(-2.0, -2.0));
+
+ let t1 = Translation2D::new(1.0, 2.0);
+ let t2 = Translation2D::new(0.0, 0.0);
+ assert_eq!(t1 - t2, Translation2D::new(1.0, 2.0));
+
+ let t1 = Translation2D::new(1.0, 2.0);
+ let t2 = Translation2D::new(-3.0, -4.0);
+ assert_eq!(t1 - t2, Translation2D::new(4.0, 6.0));
+
+ let t1 = Translation2D::new(0.0, 0.0);
+ let t2 = Translation2D::new(0.0, 0.0);
+ assert_eq!(t1 - t2, Translation2D::new(0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_sub_assign() {
+ let mut t = Translation2D::new(1.0, 2.0);
+ t -= Translation2D::new(3.0, 4.0);
+ assert_eq!(t, Translation2D::new(-2.0, -2.0));
+
+ let mut t = Translation2D::new(1.0, 2.0);
+ t -= Translation2D::new(0.0, 0.0);
+ assert_eq!(t, Translation2D::new(1.0, 2.0));
+
+ let mut t = Translation2D::new(1.0, 2.0);
+ t -= Translation2D::new(-3.0, -4.0);
+ assert_eq!(t, Translation2D::new(4.0, 6.0));
+
+ let mut t = Translation2D::new(0.0, 0.0);
+ t -= Translation2D::new(0.0, 0.0);
+ assert_eq!(t, Translation2D::new(0.0, 0.0));
+ }
+ }
+}
+
+#[cfg(test)]
+mod _3d {
+ #[test]
+ fn simple() {
+ use crate::{point3, Point3D, Translation3D};
+
+ struct A;
+ struct B;
+
+ type Translation = Translation3D<i32, A, B>;
+ type SrcPoint = Point3D<i32, A>;
+ type DstPoint = Point3D<i32, B>;
+
+ let tx = Translation::new(10, -10, 100);
+ let p1: SrcPoint = point3(10, 20, 30);
+ let p2: DstPoint = tx.transform_point3d(&p1);
+ assert_eq!(p2, point3(20, 10, 130));
+
+ let inv_tx = tx.inverse();
+ assert_eq!(inv_tx.transform_point3d(&p2), p1);
+
+ assert!((tx + inv_tx).is_identity());
+ }
+
+ /// Operation tests
+ mod ops {
+ use crate::default::Translation3D;
+
+ #[test]
+ pub fn test_add() {
+ let t1 = Translation3D::new(1.0, 2.0, 3.0);
+ let t2 = Translation3D::new(4.0, 5.0, 6.0);
+ assert_eq!(t1 + t2, Translation3D::new(5.0, 7.0, 9.0));
+
+ let t1 = Translation3D::new(1.0, 2.0, 3.0);
+ let t2 = Translation3D::new(0.0, 0.0, 0.0);
+ assert_eq!(t1 + t2, Translation3D::new(1.0, 2.0, 3.0));
+
+ let t1 = Translation3D::new(1.0, 2.0, 3.0);
+ let t2 = Translation3D::new(-4.0, -5.0, -6.0);
+ assert_eq!(t1 + t2, Translation3D::new(-3.0, -3.0, -3.0));
+
+ let t1 = Translation3D::new(0.0, 0.0, 0.0);
+ let t2 = Translation3D::new(0.0, 0.0, 0.0);
+ assert_eq!(t1 + t2, Translation3D::new(0.0, 0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_add_assign() {
+ let mut t = Translation3D::new(1.0, 2.0, 3.0);
+ t += Translation3D::new(4.0, 5.0, 6.0);
+ assert_eq!(t, Translation3D::new(5.0, 7.0, 9.0));
+
+ let mut t = Translation3D::new(1.0, 2.0, 3.0);
+ t += Translation3D::new(0.0, 0.0, 0.0);
+ assert_eq!(t, Translation3D::new(1.0, 2.0, 3.0));
+
+ let mut t = Translation3D::new(1.0, 2.0, 3.0);
+ t += Translation3D::new(-4.0, -5.0, -6.0);
+ assert_eq!(t, Translation3D::new(-3.0, -3.0, -3.0));
+
+ let mut t = Translation3D::new(0.0, 0.0, 0.0);
+ t += Translation3D::new(0.0, 0.0, 0.0);
+ assert_eq!(t, Translation3D::new(0.0, 0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_sub() {
+ let t1 = Translation3D::new(1.0, 2.0, 3.0);
+ let t2 = Translation3D::new(4.0, 5.0, 6.0);
+ assert_eq!(t1 - t2, Translation3D::new(-3.0, -3.0, -3.0));
+
+ let t1 = Translation3D::new(1.0, 2.0, 3.0);
+ let t2 = Translation3D::new(0.0, 0.0, 0.0);
+ assert_eq!(t1 - t2, Translation3D::new(1.0, 2.0, 3.0));
+
+ let t1 = Translation3D::new(1.0, 2.0, 3.0);
+ let t2 = Translation3D::new(-4.0, -5.0, -6.0);
+ assert_eq!(t1 - t2, Translation3D::new(5.0, 7.0, 9.0));
+
+ let t1 = Translation3D::new(0.0, 0.0, 0.0);
+ let t2 = Translation3D::new(0.0, 0.0, 0.0);
+ assert_eq!(t1 - t2, Translation3D::new(0.0, 0.0, 0.0));
+ }
+
+ #[test]
+ pub fn test_sub_assign() {
+ let mut t = Translation3D::new(1.0, 2.0, 3.0);
+ t -= Translation3D::new(4.0, 5.0, 6.0);
+ assert_eq!(t, Translation3D::new(-3.0, -3.0, -3.0));
+
+ let mut t = Translation3D::new(1.0, 2.0, 3.0);
+ t -= Translation3D::new(0.0, 0.0, 0.0);
+ assert_eq!(t, Translation3D::new(1.0, 2.0, 3.0));
+
+ let mut t = Translation3D::new(1.0, 2.0, 3.0);
+ t -= Translation3D::new(-4.0, -5.0, -6.0);
+ assert_eq!(t, Translation3D::new(5.0, 7.0, 9.0));
+
+ let mut t = Translation3D::new(0.0, 0.0, 0.0);
+ t -= Translation3D::new(0.0, 0.0, 0.0);
+ assert_eq!(t, Translation3D::new(0.0, 0.0, 0.0));
+ }
+ }
+}
diff --git a/third_party/rust/euclid/src/trig.rs b/third_party/rust/euclid/src/trig.rs
new file mode 100644
index 0000000000..907ffb0baa
--- /dev/null
+++ b/third_party/rust/euclid/src/trig.rs
@@ -0,0 +1,80 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+/// Trait for basic trigonometry functions, so they can be used on generic numeric types
+pub trait Trig {
+ fn sin(self) -> Self;
+ fn cos(self) -> Self;
+ fn tan(self) -> Self;
+ fn fast_atan2(y: Self, x: Self) -> Self;
+ fn degrees_to_radians(deg: Self) -> Self;
+ fn radians_to_degrees(rad: Self) -> Self;
+}
+
+macro_rules! trig {
+ ($ty:ident) => {
+ impl Trig for $ty {
+ #[inline]
+ fn sin(self) -> $ty {
+ num_traits::Float::sin(self)
+ }
+ #[inline]
+ fn cos(self) -> $ty {
+ num_traits::Float::cos(self)
+ }
+ #[inline]
+ fn tan(self) -> $ty {
+ num_traits::Float::tan(self)
+ }
+
+ /// A slightly faster approximation of `atan2`.
+ ///
+ /// Note that it does not deal with the case where both x and y are 0.
+ #[inline]
+ fn fast_atan2(y: $ty, x: $ty) -> $ty {
+ // This macro is used with f32 and f64 and clippy warns about the extra
+ // precision with f32.
+ #![cfg_attr(feature = "cargo-clippy", allow(excessive_precision))]
+
+ // See https://math.stackexchange.com/questions/1098487/atan2-faster-approximation#1105038
+ use core::$ty::consts;
+ let x_abs = num_traits::Float::abs(x);
+ let y_abs = num_traits::Float::abs(y);
+ let a = x_abs.min(y_abs) / x_abs.max(y_abs);
+ let s = a * a;
+ let mut result =
+ ((-0.046_496_474_9 * s + 0.159_314_22) * s - 0.327_622_764) * s * a + a;
+ if y_abs > x_abs {
+ result = consts::FRAC_PI_2 - result;
+ }
+ if x < 0.0 {
+ result = consts::PI - result
+ }
+ if y < 0.0 {
+ result = -result
+ }
+
+ result
+ }
+
+ #[inline]
+ fn degrees_to_radians(deg: Self) -> Self {
+ deg.to_radians()
+ }
+
+ #[inline]
+ fn radians_to_degrees(rad: Self) -> Self {
+ rad.to_degrees()
+ }
+ }
+ };
+}
+
+trig!(f32);
+trig!(f64);
diff --git a/third_party/rust/euclid/src/vector.rs b/third_party/rust/euclid/src/vector.rs
new file mode 100644
index 0000000000..1a23bbed31
--- /dev/null
+++ b/third_party/rust/euclid/src/vector.rs
@@ -0,0 +1,2596 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::UnknownUnit;
+use crate::approxeq::ApproxEq;
+use crate::approxord::{max, min};
+use crate::length::Length;
+use crate::num::*;
+use crate::point::{point2, point3, Point2D, Point3D};
+use crate::scale::Scale;
+use crate::size::{size2, size3, Size2D, Size3D};
+use crate::transform2d::Transform2D;
+use crate::transform3d::Transform3D;
+use crate::trig::Trig;
+use crate::Angle;
+use core::cmp::{Eq, PartialEq};
+use core::fmt;
+use core::hash::Hash;
+use core::iter::Sum;
+use core::marker::PhantomData;
+use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+#[cfg(feature = "mint")]
+use mint;
+use num_traits::real::Real;
+use num_traits::{Float, NumCast, Signed};
+#[cfg(feature = "serde")]
+use serde;
+
+#[cfg(feature = "bytemuck")]
+use bytemuck::{Zeroable, Pod};
+
+/// A 2d Vector tagged with a unit.
+#[repr(C)]
+pub struct Vector2D<T, U> {
+ /// The `x` (traditionally, horizontal) coordinate.
+ pub x: T,
+ /// The `y` (traditionally, vertical) coordinate.
+ pub y: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<U>,
+}
+
+mint_vec!(Vector2D[x, y] = Vector2);
+
+impl<T: Copy, U> Copy for Vector2D<T, U> {}
+
+impl<T: Clone, U> Clone for Vector2D<T, U> {
+ fn clone(&self) -> Self {
+ Vector2D {
+ x: self.x.clone(),
+ y: self.y.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de, T, U> serde::Deserialize<'de> for Vector2D<T, U>
+where
+ T: serde::Deserialize<'de>,
+{
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let (x, y) = serde::Deserialize::deserialize(deserializer)?;
+ Ok(Vector2D {
+ x,
+ y,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<T, U> serde::Serialize for Vector2D<T, U>
+where
+ T: serde::Serialize,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ (&self.x, &self.y).serialize(serializer)
+ }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a, T, U> arbitrary::Arbitrary<'a> for Vector2D<T, U>
+where
+ T: arbitrary::Arbitrary<'a>,
+{
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self>
+ {
+ let (x, y) = arbitrary::Arbitrary::arbitrary(u)?;
+ Ok(Vector2D {
+ x,
+ y,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for Vector2D<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for Vector2D<T, U> {}
+
+impl<T: Eq, U> Eq for Vector2D<T, U> {}
+
+impl<T: PartialEq, U> PartialEq for Vector2D<T, U> {
+ fn eq(&self, other: &Self) -> bool {
+ self.x == other.x && self.y == other.y
+ }
+}
+
+impl<T: Hash, U> Hash for Vector2D<T, U> {
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.x.hash(h);
+ self.y.hash(h);
+ }
+}
+
+impl<T: Zero, U> Zero for Vector2D<T, U> {
+ /// Constructor, setting all components to zero.
+ #[inline]
+ fn zero() -> Self {
+ Vector2D::new(Zero::zero(), Zero::zero())
+ }
+}
+
+impl<T: fmt::Debug, U> fmt::Debug for Vector2D<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_tuple("").field(&self.x).field(&self.y).finish()
+ }
+}
+
+impl<T: Default, U> Default for Vector2D<T, U> {
+ fn default() -> Self {
+ Vector2D::new(Default::default(), Default::default())
+ }
+}
+
+impl<T, U> Vector2D<T, U> {
+ /// Constructor, setting all components to zero.
+ #[inline]
+ pub fn zero() -> Self
+ where
+ T: Zero,
+ {
+ Vector2D::new(Zero::zero(), Zero::zero())
+ }
+
+ /// Constructor, setting all components to one.
+ #[inline]
+ pub fn one() -> Self
+ where
+ T: One,
+ {
+ Vector2D::new(One::one(), One::one())
+ }
+
+ /// Constructor taking scalar values directly.
+ #[inline]
+ pub const fn new(x: T, y: T) -> Self {
+ Vector2D {
+ x,
+ y,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Constructor setting all components to the same value.
+ #[inline]
+ pub fn splat(v: T) -> Self
+ where
+ T: Clone,
+ {
+ Vector2D {
+ x: v.clone(),
+ y: v,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Constructor taking angle and length
+ pub fn from_angle_and_length(angle: Angle<T>, length: T) -> Self
+ where
+ T: Trig + Mul<Output = T> + Copy,
+ {
+ vec2(length * angle.radians.cos(), length * angle.radians.sin())
+ }
+
+ /// Constructor taking properly Lengths instead of scalar values.
+ #[inline]
+ pub fn from_lengths(x: Length<T, U>, y: Length<T, U>) -> Self {
+ vec2(x.0, y.0)
+ }
+
+ /// Tag a unit-less value with units.
+ #[inline]
+ pub fn from_untyped(p: Vector2D<T, UnknownUnit>) -> Self {
+ vec2(p.x, p.y)
+ }
+
+ /// Computes the vector with absolute values of each component.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use std::{i32, f32};
+ /// # use euclid::vec2;
+ /// enum U {}
+ ///
+ /// assert_eq!(vec2::<_, U>(-1, 2).abs(), vec2(1, 2));
+ ///
+ /// let vec = vec2::<_, U>(f32::NAN, -f32::MAX).abs();
+ /// assert!(vec.x.is_nan());
+ /// assert_eq!(vec.y, f32::MAX);
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// The behavior for each component follows the scalar type's implementation of
+ /// `num_traits::Signed::abs`.
+ pub fn abs(self) -> Self
+ where
+ T: Signed,
+ {
+ vec2(self.x.abs(), self.y.abs())
+ }
+
+ /// Dot product.
+ #[inline]
+ pub fn dot(self, other: Self) -> T
+ where
+ T: Add<Output = T> + Mul<Output = T>,
+ {
+ self.x * other.x + self.y * other.y
+ }
+
+ /// Returns the norm of the cross product [self.x, self.y, 0] x [other.x, other.y, 0].
+ #[inline]
+ pub fn cross(self, other: Self) -> T
+ where
+ T: Sub<Output = T> + Mul<Output = T>,
+ {
+ self.x * other.y - self.y * other.x
+ }
+
+ /// Returns the component-wise multiplication of the two vectors.
+ #[inline]
+ pub fn component_mul(self, other: Self) -> Self
+ where
+ T: Mul<Output = T>,
+ {
+ vec2(self.x * other.x, self.y * other.y)
+ }
+
+ /// Returns the component-wise division of the two vectors.
+ #[inline]
+ pub fn component_div(self, other: Self) -> Self
+ where
+ T: Div<Output = T>,
+ {
+ vec2(self.x / other.x, self.y / other.y)
+ }
+}
+
+impl<T: Copy, U> Vector2D<T, U> {
+ /// Create a 3d vector from this one, using the specified z value.
+ #[inline]
+ pub fn extend(self, z: T) -> Vector3D<T, U> {
+ vec3(self.x, self.y, z)
+ }
+
+ /// Cast this vector into a point.
+ ///
+ /// Equivalent to adding this vector to the origin.
+ #[inline]
+ pub fn to_point(self) -> Point2D<T, U> {
+ Point2D {
+ x: self.x,
+ y: self.y,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Swap x and y.
+ #[inline]
+ pub fn yx(self) -> Self {
+ vec2(self.y, self.x)
+ }
+
+ /// Cast this vector into a size.
+ #[inline]
+ pub fn to_size(self) -> Size2D<T, U> {
+ size2(self.x, self.y)
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(self) -> Vector2D<T, UnknownUnit> {
+ vec2(self.x, self.y)
+ }
+
+ /// Cast the unit.
+ #[inline]
+ pub fn cast_unit<V>(self) -> Vector2D<T, V> {
+ vec2(self.x, self.y)
+ }
+
+ /// Cast into an array with x and y.
+ #[inline]
+ pub fn to_array(self) -> [T; 2] {
+ [self.x, self.y]
+ }
+
+ /// Cast into a tuple with x and y.
+ #[inline]
+ pub fn to_tuple(self) -> (T, T) {
+ (self.x, self.y)
+ }
+
+ /// Convert into a 3d vector with `z` coordinate equals to `T::zero()`.
+ #[inline]
+ pub fn to_3d(self) -> Vector3D<T, U>
+ where
+ T: Zero,
+ {
+ vec3(self.x, self.y, Zero::zero())
+ }
+
+ /// Rounds each component to the nearest integer value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::vec2;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(vec2::<_, Mm>(-0.1, -0.8).round(), vec2::<_, Mm>(0.0, -1.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn round(self) -> Self
+ where
+ T: Round,
+ {
+ vec2(self.x.round(), self.y.round())
+ }
+
+ /// Rounds each component to the smallest integer equal or greater than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::vec2;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(vec2::<_, Mm>(-0.1, -0.8).ceil(), vec2::<_, Mm>(0.0, 0.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn ceil(self) -> Self
+ where
+ T: Ceil,
+ {
+ vec2(self.x.ceil(), self.y.ceil())
+ }
+
+ /// Rounds each component to the biggest integer equal or lower than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::vec2;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(vec2::<_, Mm>(-0.1, -0.8).floor(), vec2::<_, Mm>(-1.0, -1.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn floor(self) -> Self
+ where
+ T: Floor,
+ {
+ vec2(self.x.floor(), self.y.floor())
+ }
+
+ /// Returns the signed angle between this vector and the x axis.
+ /// Positive values counted counterclockwise, where 0 is `+x` axis, `PI/2`
+ /// is `+y` axis.
+ ///
+ /// The returned angle is between -PI and PI.
+ pub fn angle_from_x_axis(self) -> Angle<T>
+ where
+ T: Trig,
+ {
+ Angle::radians(Trig::fast_atan2(self.y, self.x))
+ }
+
+ /// Creates translation by this vector in vector units.
+ #[inline]
+ pub fn to_transform(self) -> Transform2D<T, U, U>
+ where
+ T: Zero + One,
+ {
+ Transform2D::translation(self.x, self.y)
+ }
+}
+
+impl<T, U> Vector2D<T, U>
+where
+ T: Copy + Mul<T, Output = T> + Add<T, Output = T>,
+{
+ /// Returns the vector's length squared.
+ #[inline]
+ pub fn square_length(self) -> T {
+ self.x * self.x + self.y * self.y
+ }
+
+ /// Returns this vector projected onto another one.
+ ///
+ /// Projecting onto a nil vector will cause a division by zero.
+ #[inline]
+ pub fn project_onto_vector(self, onto: Self) -> Self
+ where
+ T: Sub<T, Output = T> + Div<T, Output = T>,
+ {
+ onto * (self.dot(onto) / onto.square_length())
+ }
+
+ /// Returns the signed angle between this vector and another vector.
+ ///
+ /// The returned angle is between -PI and PI.
+ pub fn angle_to(self, other: Self) -> Angle<T>
+ where
+ T: Sub<Output = T> + Trig,
+ {
+ Angle::radians(Trig::fast_atan2(self.cross(other), self.dot(other)))
+ }
+}
+
+impl<T: Float, U> Vector2D<T, U> {
+ /// Return the normalized vector even if the length is larger than the max value of Float.
+ #[inline]
+ #[must_use]
+ pub fn robust_normalize(self) -> Self {
+ let length = self.length();
+ if length.is_infinite() {
+ let scaled = self / T::max_value();
+ scaled / scaled.length()
+ } else {
+ self / length
+ }
+ }
+
+ /// Returns true if all members are finite.
+ #[inline]
+ pub fn is_finite(self) -> bool {
+ self.x.is_finite() && self.y.is_finite()
+ }
+}
+
+impl<T: Real, U> Vector2D<T, U> {
+ /// Returns the vector length.
+ #[inline]
+ pub fn length(self) -> T {
+ self.square_length().sqrt()
+ }
+
+ /// Returns the vector with length of one unit.
+ #[inline]
+ #[must_use]
+ pub fn normalize(self) -> Self {
+ self / self.length()
+ }
+
+ /// Returns the vector with length of one unit.
+ ///
+ /// Unlike [`Vector2D::normalize`](#method.normalize), this returns None in the case that the
+ /// length of the vector is zero.
+ #[inline]
+ #[must_use]
+ pub fn try_normalize(self) -> Option<Self> {
+ let len = self.length();
+ if len == T::zero() {
+ None
+ } else {
+ Some(self / len)
+ }
+ }
+
+ /// Return this vector scaled to fit the provided length.
+ #[inline]
+ pub fn with_length(self, length: T) -> Self {
+ self.normalize() * length
+ }
+
+ /// Return this vector capped to a maximum length.
+ #[inline]
+ pub fn with_max_length(self, max_length: T) -> Self {
+ let square_length = self.square_length();
+ if square_length > max_length * max_length {
+ return self * (max_length / square_length.sqrt());
+ }
+
+ self
+ }
+
+ /// Return this vector with a minimum length applied.
+ #[inline]
+ pub fn with_min_length(self, min_length: T) -> Self {
+ let square_length = self.square_length();
+ if square_length < min_length * min_length {
+ return self * (min_length / square_length.sqrt());
+ }
+
+ self
+ }
+
+ /// Return this vector with minimum and maximum lengths applied.
+ #[inline]
+ pub fn clamp_length(self, min: T, max: T) -> Self {
+ debug_assert!(min <= max);
+ self.with_min_length(min).with_max_length(max)
+ }
+}
+
+impl<T, U> Vector2D<T, U>
+where
+ T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
+{
+ /// Linearly interpolate each component between this vector and another vector.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::vec2;
+ /// use euclid::default::Vector2D;
+ ///
+ /// let from: Vector2D<_> = vec2(0.0, 10.0);
+ /// let to: Vector2D<_> = vec2(8.0, -4.0);
+ ///
+ /// assert_eq!(from.lerp(to, -1.0), vec2(-8.0, 24.0));
+ /// assert_eq!(from.lerp(to, 0.0), vec2( 0.0, 10.0));
+ /// assert_eq!(from.lerp(to, 0.5), vec2( 4.0, 3.0));
+ /// assert_eq!(from.lerp(to, 1.0), vec2( 8.0, -4.0));
+ /// assert_eq!(from.lerp(to, 2.0), vec2(16.0, -18.0));
+ /// ```
+ #[inline]
+ pub fn lerp(self, other: Self, t: T) -> Self {
+ let one_t = T::one() - t;
+ self * one_t + other * t
+ }
+
+ /// Returns a reflection vector using an incident ray and a surface normal.
+ #[inline]
+ pub fn reflect(self, normal: Self) -> Self {
+ let two = T::one() + T::one();
+ self - normal * two * self.dot(normal)
+ }
+}
+
+impl<T: PartialOrd, U> Vector2D<T, U> {
+ /// Returns the vector each component of which are minimum of this vector and another.
+ #[inline]
+ pub fn min(self, other: Self) -> Self {
+ vec2(min(self.x, other.x), min(self.y, other.y))
+ }
+
+ /// Returns the vector each component of which are maximum of this vector and another.
+ #[inline]
+ pub fn max(self, other: Self) -> Self {
+ vec2(max(self.x, other.x), max(self.y, other.y))
+ }
+
+ /// Returns the vector each component of which is clamped by corresponding
+ /// components of `start` and `end`.
+ ///
+ /// Shortcut for `self.max(start).min(end)`.
+ #[inline]
+ pub fn clamp(self, start: Self, end: Self) -> Self
+ where
+ T: Copy,
+ {
+ self.max(start).min(end)
+ }
+
+ /// Returns vector with results of "greater than" operation on each component.
+ #[inline]
+ pub fn greater_than(self, other: Self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.x > other.x,
+ y: self.y > other.y,
+ }
+ }
+
+ /// Returns vector with results of "lower than" operation on each component.
+ #[inline]
+ pub fn lower_than(self, other: Self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.x < other.x,
+ y: self.y < other.y,
+ }
+ }
+}
+
+impl<T: PartialEq, U> Vector2D<T, U> {
+ /// Returns vector with results of "equal" operation on each component.
+ #[inline]
+ pub fn equal(self, other: Self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.x == other.x,
+ y: self.y == other.y,
+ }
+ }
+
+ /// Returns vector with results of "not equal" operation on each component.
+ #[inline]
+ pub fn not_equal(self, other: Self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.x != other.x,
+ y: self.y != other.y,
+ }
+ }
+}
+
+impl<T: NumCast + Copy, U> Vector2D<T, U> {
+ /// Cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating vector to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ #[inline]
+ pub fn cast<NewT: NumCast>(self) -> Vector2D<NewT, U> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating vector to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ pub fn try_cast<NewT: NumCast>(self) -> Option<Vector2D<NewT, U>> {
+ match (NumCast::from(self.x), NumCast::from(self.y)) {
+ (Some(x), Some(y)) => Some(Vector2D::new(x, y)),
+ _ => None,
+ }
+ }
+
+ // Convenience functions for common casts.
+
+ /// Cast into an `f32` vector.
+ #[inline]
+ pub fn to_f32(self) -> Vector2D<f32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `f64` vector.
+ #[inline]
+ pub fn to_f64(self) -> Vector2D<f64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `usize` vector, truncating decimals if any.
+ ///
+ /// When casting from floating vector vectors, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_usize(self) -> Vector2D<usize, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u32` vector, truncating decimals if any.
+ ///
+ /// When casting from floating vector vectors, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_u32(self) -> Vector2D<u32, U> {
+ self.cast()
+ }
+
+ /// Cast into an i32 vector, truncating decimals if any.
+ ///
+ /// When casting from floating vector vectors, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i32(self) -> Vector2D<i32, U> {
+ self.cast()
+ }
+
+ /// Cast into an i64 vector, truncating decimals if any.
+ ///
+ /// When casting from floating vector vectors, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i64(self) -> Vector2D<i64, U> {
+ self.cast()
+ }
+}
+
+impl<T: Neg, U> Neg for Vector2D<T, U> {
+ type Output = Vector2D<T::Output, U>;
+
+ #[inline]
+ fn neg(self) -> Self::Output {
+ vec2(-self.x, -self.y)
+ }
+}
+
+impl<T: Add, U> Add for Vector2D<T, U> {
+ type Output = Vector2D<T::Output, U>;
+
+ #[inline]
+ fn add(self, other: Self) -> Self::Output {
+ Vector2D::new(self.x + other.x, self.y + other.y)
+ }
+}
+
+impl<T: Add + Copy, U> Add<&Self> for Vector2D<T, U> {
+ type Output = Vector2D<T::Output, U>;
+
+ #[inline]
+ fn add(self, other: &Self) -> Self::Output {
+ Vector2D::new(self.x + other.x, self.y + other.y)
+ }
+}
+
+impl<T: Add<Output = T> + Zero, U> Sum for Vector2D<T, U> {
+ fn sum<I: Iterator<Item=Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+impl<'a, T: 'a + Add<Output = T> + Copy + Zero, U: 'a> Sum<&'a Self> for Vector2D<T, U> {
+ fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+impl<T: Copy + Add<T, Output = T>, U> AddAssign for Vector2D<T, U> {
+ #[inline]
+ fn add_assign(&mut self, other: Self) {
+ *self = *self + other
+ }
+}
+
+impl<T: Sub, U> Sub for Vector2D<T, U> {
+ type Output = Vector2D<T::Output, U>;
+
+ #[inline]
+ fn sub(self, other: Self) -> Self::Output {
+ vec2(self.x - other.x, self.y - other.y)
+ }
+}
+
+impl<T: Copy + Sub<T, Output = T>, U> SubAssign<Vector2D<T, U>> for Vector2D<T, U> {
+ #[inline]
+ fn sub_assign(&mut self, other: Self) {
+ *self = *self - other
+ }
+}
+
+impl<T: Copy + Mul, U> Mul<T> for Vector2D<T, U> {
+ type Output = Vector2D<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ vec2(self.x * scale, self.y * scale)
+ }
+}
+
+impl<T: Copy + Mul<T, Output = T>, U> MulAssign<T> for Vector2D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: T) {
+ *self = *self * scale
+ }
+}
+
+impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Vector2D<T, U1> {
+ type Output = Vector2D<T::Output, U2>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ vec2(self.x * scale.0, self.y * scale.0)
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Vector2D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: Scale<T, U, U>) {
+ self.x *= scale.0;
+ self.y *= scale.0;
+ }
+}
+
+impl<T: Copy + Div, U> Div<T> for Vector2D<T, U> {
+ type Output = Vector2D<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ vec2(self.x / scale, self.y / scale)
+ }
+}
+
+impl<T: Copy + Div<T, Output = T>, U> DivAssign<T> for Vector2D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: T) {
+ *self = *self / scale
+ }
+}
+
+impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Vector2D<T, U2> {
+ type Output = Vector2D<T::Output, U1>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ vec2(self.x / scale.0, self.y / scale.0)
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Vector2D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: Scale<T, U, U>) {
+ self.x /= scale.0;
+ self.y /= scale.0;
+ }
+}
+
+impl<T: Round, U> Round for Vector2D<T, U> {
+ /// See [`Vector2D::round()`](#method.round)
+ #[inline]
+ fn round(self) -> Self {
+ self.round()
+ }
+}
+
+impl<T: Ceil, U> Ceil for Vector2D<T, U> {
+ /// See [`Vector2D::ceil()`](#method.ceil)
+ #[inline]
+ fn ceil(self) -> Self {
+ self.ceil()
+ }
+}
+
+impl<T: Floor, U> Floor for Vector2D<T, U> {
+ /// See [`Vector2D::floor()`](#method.floor)
+ #[inline]
+ fn floor(self) -> Self {
+ self.floor()
+ }
+}
+
+impl<T: ApproxEq<T>, U> ApproxEq<Vector2D<T, U>> for Vector2D<T, U> {
+ #[inline]
+ fn approx_epsilon() -> Self {
+ vec2(T::approx_epsilon(), T::approx_epsilon())
+ }
+
+ #[inline]
+ fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool {
+ self.x.approx_eq_eps(&other.x, &eps.x) && self.y.approx_eq_eps(&other.y, &eps.y)
+ }
+}
+
+impl<T, U> Into<[T; 2]> for Vector2D<T, U> {
+ fn into(self) -> [T; 2] {
+ [self.x, self.y]
+ }
+}
+
+impl<T, U> From<[T; 2]> for Vector2D<T, U> {
+ fn from([x, y]: [T; 2]) -> Self {
+ vec2(x, y)
+ }
+}
+
+impl<T, U> Into<(T, T)> for Vector2D<T, U> {
+ fn into(self) -> (T, T) {
+ (self.x, self.y)
+ }
+}
+
+impl<T, U> From<(T, T)> for Vector2D<T, U> {
+ fn from(tuple: (T, T)) -> Self {
+ vec2(tuple.0, tuple.1)
+ }
+}
+
+impl<T, U> From<Size2D<T, U>> for Vector2D<T, U> {
+ fn from(size: Size2D<T, U>) -> Self {
+ vec2(size.width, size.height)
+ }
+}
+
+/// A 3d Vector tagged with a unit.
+#[repr(C)]
+pub struct Vector3D<T, U> {
+ /// The `x` (traditionally, horizontal) coordinate.
+ pub x: T,
+ /// The `y` (traditionally, vertical) coordinate.
+ pub y: T,
+ /// The `z` (traditionally, depth) coordinate.
+ pub z: T,
+ #[doc(hidden)]
+ pub _unit: PhantomData<U>,
+}
+
+mint_vec!(Vector3D[x, y, z] = Vector3);
+
+impl<T: Copy, U> Copy for Vector3D<T, U> {}
+
+impl<T: Clone, U> Clone for Vector3D<T, U> {
+ fn clone(&self) -> Self {
+ Vector3D {
+ x: self.x.clone(),
+ y: self.y.clone(),
+ z: self.z.clone(),
+ _unit: PhantomData,
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<'de, T, U> serde::Deserialize<'de> for Vector3D<T, U>
+where
+ T: serde::Deserialize<'de>,
+{
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let (x, y, z) = serde::Deserialize::deserialize(deserializer)?;
+ Ok(Vector3D {
+ x,
+ y,
+ z,
+ _unit: PhantomData,
+ })
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<T, U> serde::Serialize for Vector3D<T, U>
+where
+ T: serde::Serialize,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ (&self.x, &self.y, &self.z).serialize(serializer)
+ }
+}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Zeroable, U> Zeroable for Vector3D<T, U> {}
+
+#[cfg(feature = "bytemuck")]
+unsafe impl<T: Pod, U: 'static> Pod for Vector3D<T, U> {}
+
+impl<T: Eq, U> Eq for Vector3D<T, U> {}
+
+impl<T: PartialEq, U> PartialEq for Vector3D<T, U> {
+ fn eq(&self, other: &Self) -> bool {
+ self.x == other.x && self.y == other.y && self.z == other.z
+ }
+}
+
+impl<T: Hash, U> Hash for Vector3D<T, U> {
+ fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
+ self.x.hash(h);
+ self.y.hash(h);
+ self.z.hash(h);
+ }
+}
+
+impl<T: Zero, U> Zero for Vector3D<T, U> {
+ /// Constructor, setting all components to zero.
+ #[inline]
+ fn zero() -> Self {
+ vec3(Zero::zero(), Zero::zero(), Zero::zero())
+ }
+}
+
+impl<T: fmt::Debug, U> fmt::Debug for Vector3D<T, U> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_tuple("")
+ .field(&self.x)
+ .field(&self.y)
+ .field(&self.z)
+ .finish()
+ }
+}
+
+impl<T: Default, U> Default for Vector3D<T, U> {
+ fn default() -> Self {
+ Vector3D::new(Default::default(), Default::default(), Default::default())
+ }
+}
+
+impl<T, U> Vector3D<T, U> {
+ /// Constructor, setting all components to zero.
+ #[inline]
+ pub fn zero() -> Self
+ where
+ T: Zero,
+ {
+ vec3(Zero::zero(), Zero::zero(), Zero::zero())
+ }
+
+ /// Constructor, setting all components to one.
+ #[inline]
+ pub fn one() -> Self
+ where
+ T: One,
+ {
+ vec3(One::one(), One::one(), One::one())
+ }
+
+ /// Constructor taking scalar values directly.
+ #[inline]
+ pub const fn new(x: T, y: T, z: T) -> Self {
+ Vector3D {
+ x,
+ y,
+ z,
+ _unit: PhantomData,
+ }
+ }
+ /// Constructor setting all components to the same value.
+ #[inline]
+ pub fn splat(v: T) -> Self
+ where
+ T: Clone,
+ {
+ Vector3D {
+ x: v.clone(),
+ y: v.clone(),
+ z: v,
+ _unit: PhantomData,
+ }
+ }
+
+ /// Constructor taking properly Lengths instead of scalar values.
+ #[inline]
+ pub fn from_lengths(x: Length<T, U>, y: Length<T, U>, z: Length<T, U>) -> Vector3D<T, U> {
+ vec3(x.0, y.0, z.0)
+ }
+
+ /// Tag a unitless value with units.
+ #[inline]
+ pub fn from_untyped(p: Vector3D<T, UnknownUnit>) -> Self {
+ vec3(p.x, p.y, p.z)
+ }
+
+ /// Computes the vector with absolute values of each component.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use std::{i32, f32};
+ /// # use euclid::vec3;
+ /// enum U {}
+ ///
+ /// assert_eq!(vec3::<_, U>(-1, 0, 2).abs(), vec3(1, 0, 2));
+ ///
+ /// let vec = vec3::<_, U>(f32::NAN, 0.0, -f32::MAX).abs();
+ /// assert!(vec.x.is_nan());
+ /// assert_eq!(vec.y, 0.0);
+ /// assert_eq!(vec.z, f32::MAX);
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// The behavior for each component follows the scalar type's implementation of
+ /// `num_traits::Signed::abs`.
+ pub fn abs(self) -> Self
+ where
+ T: Signed,
+ {
+ vec3(self.x.abs(), self.y.abs(), self.z.abs())
+ }
+
+ /// Dot product.
+ #[inline]
+ pub fn dot(self, other: Self) -> T
+ where
+ T: Add<Output = T> + Mul<Output = T>,
+ {
+ self.x * other.x + self.y * other.y + self.z * other.z
+ }
+}
+
+impl<T: Copy, U> Vector3D<T, U> {
+ /// Cross product.
+ #[inline]
+ pub fn cross(self, other: Self) -> Self
+ where
+ T: Sub<Output = T> + Mul<Output = T>,
+ {
+ vec3(
+ self.y * other.z - self.z * other.y,
+ self.z * other.x - self.x * other.z,
+ self.x * other.y - self.y * other.x,
+ )
+ }
+
+ /// Returns the component-wise multiplication of the two vectors.
+ #[inline]
+ pub fn component_mul(self, other: Self) -> Self
+ where
+ T: Mul<Output = T>,
+ {
+ vec3(self.x * other.x, self.y * other.y, self.z * other.z)
+ }
+
+ /// Returns the component-wise division of the two vectors.
+ #[inline]
+ pub fn component_div(self, other: Self) -> Self
+ where
+ T: Div<Output = T>,
+ {
+ vec3(self.x / other.x, self.y / other.y, self.z / other.z)
+ }
+
+ /// Cast this vector into a point.
+ ///
+ /// Equivalent to adding this vector to the origin.
+ #[inline]
+ pub fn to_point(self) -> Point3D<T, U> {
+ point3(self.x, self.y, self.z)
+ }
+
+ /// Returns a 2d vector using this vector's x and y coordinates
+ #[inline]
+ pub fn xy(self) -> Vector2D<T, U> {
+ vec2(self.x, self.y)
+ }
+
+ /// Returns a 2d vector using this vector's x and z coordinates
+ #[inline]
+ pub fn xz(self) -> Vector2D<T, U> {
+ vec2(self.x, self.z)
+ }
+
+ /// Returns a 2d vector using this vector's x and z coordinates
+ #[inline]
+ pub fn yz(self) -> Vector2D<T, U> {
+ vec2(self.y, self.z)
+ }
+
+ /// Cast into an array with x, y and z.
+ #[inline]
+ pub fn to_array(self) -> [T; 3] {
+ [self.x, self.y, self.z]
+ }
+
+ /// Cast into an array with x, y, z and 0.
+ #[inline]
+ pub fn to_array_4d(self) -> [T; 4]
+ where
+ T: Zero,
+ {
+ [self.x, self.y, self.z, Zero::zero()]
+ }
+
+ /// Cast into a tuple with x, y and z.
+ #[inline]
+ pub fn to_tuple(self) -> (T, T, T) {
+ (self.x, self.y, self.z)
+ }
+
+ /// Cast into a tuple with x, y, z and 0.
+ #[inline]
+ pub fn to_tuple_4d(self) -> (T, T, T, T)
+ where
+ T: Zero,
+ {
+ (self.x, self.y, self.z, Zero::zero())
+ }
+
+ /// Drop the units, preserving only the numeric value.
+ #[inline]
+ pub fn to_untyped(self) -> Vector3D<T, UnknownUnit> {
+ vec3(self.x, self.y, self.z)
+ }
+
+ /// Cast the unit.
+ #[inline]
+ pub fn cast_unit<V>(self) -> Vector3D<T, V> {
+ vec3(self.x, self.y, self.z)
+ }
+
+ /// Convert into a 2d vector.
+ #[inline]
+ pub fn to_2d(self) -> Vector2D<T, U> {
+ self.xy()
+ }
+
+ /// Rounds each component to the nearest integer value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::vec3;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(vec3::<_, Mm>(-0.1, -0.8, 0.4).round(), vec3::<_, Mm>(0.0, -1.0, 0.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn round(self) -> Self
+ where
+ T: Round,
+ {
+ vec3(self.x.round(), self.y.round(), self.z.round())
+ }
+
+ /// Rounds each component to the smallest integer equal or greater than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::vec3;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(vec3::<_, Mm>(-0.1, -0.8, 0.4).ceil(), vec3::<_, Mm>(0.0, 0.0, 1.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn ceil(self) -> Self
+ where
+ T: Ceil,
+ {
+ vec3(self.x.ceil(), self.y.ceil(), self.z.ceil())
+ }
+
+ /// Rounds each component to the biggest integer equal or lower than the original value.
+ ///
+ /// This behavior is preserved for negative values (unlike the basic cast).
+ ///
+ /// ```rust
+ /// # use euclid::vec3;
+ /// enum Mm {}
+ ///
+ /// assert_eq!(vec3::<_, Mm>(-0.1, -0.8, 0.4).floor(), vec3::<_, Mm>(-1.0, -1.0, 0.0))
+ /// ```
+ #[inline]
+ #[must_use]
+ pub fn floor(self) -> Self
+ where
+ T: Floor,
+ {
+ vec3(self.x.floor(), self.y.floor(), self.z.floor())
+ }
+
+ /// Creates translation by this vector in vector units
+ #[inline]
+ pub fn to_transform(self) -> Transform3D<T, U, U>
+ where
+ T: Zero + One,
+ {
+ Transform3D::translation(self.x, self.y, self.z)
+ }
+}
+
+impl<T, U> Vector3D<T, U>
+where
+ T: Copy + Mul<T, Output = T> + Add<T, Output = T>,
+{
+ /// Returns the vector's length squared.
+ #[inline]
+ pub fn square_length(self) -> T {
+ self.x * self.x + self.y * self.y + self.z * self.z
+ }
+
+ /// Returns this vector projected onto another one.
+ ///
+ /// Projecting onto a nil vector will cause a division by zero.
+ #[inline]
+ pub fn project_onto_vector(self, onto: Self) -> Self
+ where
+ T: Sub<T, Output = T> + Div<T, Output = T>,
+ {
+ onto * (self.dot(onto) / onto.square_length())
+ }
+}
+
+impl<T: Float, U> Vector3D<T, U> {
+ /// Return the normalized vector even if the length is larger than the max value of Float.
+ #[inline]
+ #[must_use]
+ pub fn robust_normalize(self) -> Self {
+ let length = self.length();
+ if length.is_infinite() {
+ let scaled = self / T::max_value();
+ scaled / scaled.length()
+ } else {
+ self / length
+ }
+ }
+
+ /// Returns true if all members are finite.
+ #[inline]
+ pub fn is_finite(self) -> bool {
+ self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
+ }
+}
+
+impl<T: Real, U> Vector3D<T, U> {
+ /// Returns the positive angle between this vector and another vector.
+ ///
+ /// The returned angle is between 0 and PI.
+ pub fn angle_to(self, other: Self) -> Angle<T>
+ where
+ T: Trig,
+ {
+ Angle::radians(Trig::fast_atan2(
+ self.cross(other).length(),
+ self.dot(other),
+ ))
+ }
+
+ /// Returns the vector length.
+ #[inline]
+ pub fn length(self) -> T {
+ self.square_length().sqrt()
+ }
+
+ /// Returns the vector with length of one unit
+ #[inline]
+ #[must_use]
+ pub fn normalize(self) -> Self {
+ self / self.length()
+ }
+
+ /// Returns the vector with length of one unit.
+ ///
+ /// Unlike [`Vector2D::normalize`](#method.normalize), this returns None in the case that the
+ /// length of the vector is zero.
+ #[inline]
+ #[must_use]
+ pub fn try_normalize(self) -> Option<Self> {
+ let len = self.length();
+ if len == T::zero() {
+ None
+ } else {
+ Some(self / len)
+ }
+ }
+
+ /// Return this vector capped to a maximum length.
+ #[inline]
+ pub fn with_max_length(self, max_length: T) -> Self {
+ let square_length = self.square_length();
+ if square_length > max_length * max_length {
+ return self * (max_length / square_length.sqrt());
+ }
+
+ self
+ }
+
+ /// Return this vector with a minimum length applied.
+ #[inline]
+ pub fn with_min_length(self, min_length: T) -> Self {
+ let square_length = self.square_length();
+ if square_length < min_length * min_length {
+ return self * (min_length / square_length.sqrt());
+ }
+
+ self
+ }
+
+ /// Return this vector with minimum and maximum lengths applied.
+ #[inline]
+ pub fn clamp_length(self, min: T, max: T) -> Self {
+ debug_assert!(min <= max);
+ self.with_min_length(min).with_max_length(max)
+ }
+}
+
+impl<T, U> Vector3D<T, U>
+where
+ T: Copy + One + Add<Output = T> + Sub<Output = T> + Mul<Output = T>,
+{
+ /// Linearly interpolate each component between this vector and another vector.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use euclid::vec3;
+ /// use euclid::default::Vector3D;
+ ///
+ /// let from: Vector3D<_> = vec3(0.0, 10.0, -1.0);
+ /// let to: Vector3D<_> = vec3(8.0, -4.0, 0.0);
+ ///
+ /// assert_eq!(from.lerp(to, -1.0), vec3(-8.0, 24.0, -2.0));
+ /// assert_eq!(from.lerp(to, 0.0), vec3( 0.0, 10.0, -1.0));
+ /// assert_eq!(from.lerp(to, 0.5), vec3( 4.0, 3.0, -0.5));
+ /// assert_eq!(from.lerp(to, 1.0), vec3( 8.0, -4.0, 0.0));
+ /// assert_eq!(from.lerp(to, 2.0), vec3(16.0, -18.0, 1.0));
+ /// ```
+ #[inline]
+ pub fn lerp(self, other: Self, t: T) -> Self {
+ let one_t = T::one() - t;
+ self * one_t + other * t
+ }
+
+ /// Returns a reflection vector using an incident ray and a surface normal.
+ #[inline]
+ pub fn reflect(self, normal: Self) -> Self {
+ let two = T::one() + T::one();
+ self - normal * two * self.dot(normal)
+ }
+}
+
+impl<T: PartialOrd, U> Vector3D<T, U> {
+ /// Returns the vector each component of which are minimum of this vector and another.
+ #[inline]
+ pub fn min(self, other: Self) -> Self {
+ vec3(
+ min(self.x, other.x),
+ min(self.y, other.y),
+ min(self.z, other.z),
+ )
+ }
+
+ /// Returns the vector each component of which are maximum of this vector and another.
+ #[inline]
+ pub fn max(self, other: Self) -> Self {
+ vec3(
+ max(self.x, other.x),
+ max(self.y, other.y),
+ max(self.z, other.z),
+ )
+ }
+
+ /// Returns the vector each component of which is clamped by corresponding
+ /// components of `start` and `end`.
+ ///
+ /// Shortcut for `self.max(start).min(end)`.
+ #[inline]
+ pub fn clamp(self, start: Self, end: Self) -> Self
+ where
+ T: Copy,
+ {
+ self.max(start).min(end)
+ }
+
+ /// Returns vector with results of "greater than" operation on each component.
+ #[inline]
+ pub fn greater_than(self, other: Self) -> BoolVector3D {
+ BoolVector3D {
+ x: self.x > other.x,
+ y: self.y > other.y,
+ z: self.z > other.z,
+ }
+ }
+
+ /// Returns vector with results of "lower than" operation on each component.
+ #[inline]
+ pub fn lower_than(self, other: Self) -> BoolVector3D {
+ BoolVector3D {
+ x: self.x < other.x,
+ y: self.y < other.y,
+ z: self.z < other.z,
+ }
+ }
+}
+
+impl<T: PartialEq, U> Vector3D<T, U> {
+ /// Returns vector with results of "equal" operation on each component.
+ #[inline]
+ pub fn equal(self, other: Self) -> BoolVector3D {
+ BoolVector3D {
+ x: self.x == other.x,
+ y: self.y == other.y,
+ z: self.z == other.z,
+ }
+ }
+
+ /// Returns vector with results of "not equal" operation on each component.
+ #[inline]
+ pub fn not_equal(self, other: Self) -> BoolVector3D {
+ BoolVector3D {
+ x: self.x != other.x,
+ y: self.y != other.y,
+ z: self.z != other.z,
+ }
+ }
+}
+
+impl<T: NumCast + Copy, U> Vector3D<T, U> {
+ /// Cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating vector to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ #[inline]
+ pub fn cast<NewT: NumCast>(self) -> Vector3D<NewT, U> {
+ self.try_cast().unwrap()
+ }
+
+ /// Fallible cast from one numeric representation to another, preserving the units.
+ ///
+ /// When casting from floating vector to integer coordinates, the decimals are truncated
+ /// as one would expect from a simple cast, but this behavior does not always make sense
+ /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
+ pub fn try_cast<NewT: NumCast>(self) -> Option<Vector3D<NewT, U>> {
+ match (
+ NumCast::from(self.x),
+ NumCast::from(self.y),
+ NumCast::from(self.z),
+ ) {
+ (Some(x), Some(y), Some(z)) => Some(vec3(x, y, z)),
+ _ => None,
+ }
+ }
+
+ // Convenience functions for common casts.
+
+ /// Cast into an `f32` vector.
+ #[inline]
+ pub fn to_f32(self) -> Vector3D<f32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `f64` vector.
+ #[inline]
+ pub fn to_f64(self) -> Vector3D<f64, U> {
+ self.cast()
+ }
+
+ /// Cast into an `usize` vector, truncating decimals if any.
+ ///
+ /// When casting from floating vector vectors, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_usize(self) -> Vector3D<usize, U> {
+ self.cast()
+ }
+
+ /// Cast into an `u32` vector, truncating decimals if any.
+ ///
+ /// When casting from floating vector vectors, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_u32(self) -> Vector3D<u32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i32` vector, truncating decimals if any.
+ ///
+ /// When casting from floating vector vectors, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i32(self) -> Vector3D<i32, U> {
+ self.cast()
+ }
+
+ /// Cast into an `i64` vector, truncating decimals if any.
+ ///
+ /// When casting from floating vector vectors, it is worth considering whether
+ /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+ /// the desired conversion behavior.
+ #[inline]
+ pub fn to_i64(self) -> Vector3D<i64, U> {
+ self.cast()
+ }
+}
+
+impl<T: Neg, U> Neg for Vector3D<T, U> {
+ type Output = Vector3D<T::Output, U>;
+
+ #[inline]
+ fn neg(self) -> Self::Output {
+ vec3(-self.x, -self.y, -self.z)
+ }
+}
+
+impl<T: Add, U> Add for Vector3D<T, U> {
+ type Output = Vector3D<T::Output, U>;
+
+ #[inline]
+ fn add(self, other: Self) -> Self::Output {
+ vec3(self.x + other.x, self.y + other.y, self.z + other.z)
+ }
+}
+
+impl<'a, T: 'a + Add + Copy, U: 'a> Add<&Self> for Vector3D<T, U> {
+ type Output = Vector3D<T::Output, U>;
+
+ #[inline]
+ fn add(self, other: &Self) -> Self::Output {
+ vec3(self.x + other.x, self.y + other.y, self.z + other.z)
+ }
+}
+
+impl<T: Add<Output = T> + Zero, U> Sum for Vector3D<T, U> {
+ fn sum<I: Iterator<Item=Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+impl<'a, T: 'a + Add<Output = T> + Copy + Zero, U: 'a> Sum<&'a Self> for Vector3D<T, U> {
+ fn sum<I: Iterator<Item=&'a Self>>(iter: I) -> Self {
+ iter.fold(Self::zero(), Add::add)
+ }
+}
+
+impl<T: Copy + Add<T, Output = T>, U> AddAssign for Vector3D<T, U> {
+ #[inline]
+ fn add_assign(&mut self, other: Self) {
+ *self = *self + other
+ }
+}
+
+impl<T: Sub, U> Sub for Vector3D<T, U> {
+ type Output = Vector3D<T::Output, U>;
+
+ #[inline]
+ fn sub(self, other: Self) -> Self::Output {
+ vec3(self.x - other.x, self.y - other.y, self.z - other.z)
+ }
+}
+
+impl<T: Copy + Sub<T, Output = T>, U> SubAssign<Vector3D<T, U>> for Vector3D<T, U> {
+ #[inline]
+ fn sub_assign(&mut self, other: Self) {
+ *self = *self - other
+ }
+}
+
+impl<T: Copy + Mul, U> Mul<T> for Vector3D<T, U> {
+ type Output = Vector3D<T::Output, U>;
+
+ #[inline]
+ fn mul(self, scale: T) -> Self::Output {
+ vec3(
+ self.x * scale,
+ self.y * scale,
+ self.z * scale,
+ )
+ }
+}
+
+impl<T: Copy + Mul<T, Output = T>, U> MulAssign<T> for Vector3D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: T) {
+ *self = *self * scale
+ }
+}
+
+impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for Vector3D<T, U1> {
+ type Output = Vector3D<T::Output, U2>;
+
+ #[inline]
+ fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ vec3(
+ self.x * scale.0,
+ self.y * scale.0,
+ self.z * scale.0,
+ )
+ }
+}
+
+impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for Vector3D<T, U> {
+ #[inline]
+ fn mul_assign(&mut self, scale: Scale<T, U, U>) {
+ self.x *= scale.0;
+ self.y *= scale.0;
+ self.z *= scale.0;
+ }
+}
+
+impl<T: Copy + Div, U> Div<T> for Vector3D<T, U> {
+ type Output = Vector3D<T::Output, U>;
+
+ #[inline]
+ fn div(self, scale: T) -> Self::Output {
+ vec3(
+ self.x / scale,
+ self.y / scale,
+ self.z / scale,
+ )
+ }
+}
+
+impl<T: Copy + Div<T, Output = T>, U> DivAssign<T> for Vector3D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: T) {
+ *self = *self / scale
+ }
+}
+
+impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for Vector3D<T, U2> {
+ type Output = Vector3D<T::Output, U1>;
+
+ #[inline]
+ fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
+ vec3(
+ self.x / scale.0,
+ self.y / scale.0,
+ self.z / scale.0,
+ )
+ }
+}
+
+impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for Vector3D<T, U> {
+ #[inline]
+ fn div_assign(&mut self, scale: Scale<T, U, U>) {
+ self.x /= scale.0;
+ self.y /= scale.0;
+ self.z /= scale.0;
+ }
+}
+
+impl<T: Round, U> Round for Vector3D<T, U> {
+ /// See [`Vector3D::round()`](#method.round)
+ #[inline]
+ fn round(self) -> Self {
+ self.round()
+ }
+}
+
+impl<T: Ceil, U> Ceil for Vector3D<T, U> {
+ /// See [`Vector3D::ceil()`](#method.ceil)
+ #[inline]
+ fn ceil(self) -> Self {
+ self.ceil()
+ }
+}
+
+impl<T: Floor, U> Floor for Vector3D<T, U> {
+ /// See [`Vector3D::floor()`](#method.floor)
+ #[inline]
+ fn floor(self) -> Self {
+ self.floor()
+ }
+}
+
+impl<T: ApproxEq<T>, U> ApproxEq<Vector3D<T, U>> for Vector3D<T, U> {
+ #[inline]
+ fn approx_epsilon() -> Self {
+ vec3(
+ T::approx_epsilon(),
+ T::approx_epsilon(),
+ T::approx_epsilon(),
+ )
+ }
+
+ #[inline]
+ fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool {
+ self.x.approx_eq_eps(&other.x, &eps.x)
+ && self.y.approx_eq_eps(&other.y, &eps.y)
+ && self.z.approx_eq_eps(&other.z, &eps.z)
+ }
+}
+
+impl<T, U> Into<[T; 3]> for Vector3D<T, U> {
+ fn into(self) -> [T; 3] {
+ [self.x, self.y, self.z]
+ }
+}
+
+impl<T, U> From<[T; 3]> for Vector3D<T, U> {
+ fn from([x, y, z]: [T; 3]) -> Self {
+ vec3(x, y, z)
+ }
+}
+
+impl<T, U> Into<(T, T, T)> for Vector3D<T, U> {
+ fn into(self) -> (T, T, T) {
+ (self.x, self.y, self.z)
+ }
+}
+
+impl<T, U> From<(T, T, T)> for Vector3D<T, U> {
+ fn from(tuple: (T, T, T)) -> Self {
+ vec3(tuple.0, tuple.1, tuple.2)
+ }
+}
+
+/// A 2d vector of booleans, useful for component-wise logic operations.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub struct BoolVector2D {
+ pub x: bool,
+ pub y: bool,
+}
+
+/// A 3d vector of booleans, useful for component-wise logic operations.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub struct BoolVector3D {
+ pub x: bool,
+ pub y: bool,
+ pub z: bool,
+}
+
+impl BoolVector2D {
+ /// Returns `true` if all components are `true` and `false` otherwise.
+ #[inline]
+ pub fn all(self) -> bool {
+ self.x && self.y
+ }
+
+ /// Returns `true` if any component are `true` and `false` otherwise.
+ #[inline]
+ pub fn any(self) -> bool {
+ self.x || self.y
+ }
+
+ /// Returns `true` if all components are `false` and `false` otherwise. Negation of `any()`.
+ #[inline]
+ pub fn none(self) -> bool {
+ !self.any()
+ }
+
+ /// Returns new vector with by-component AND operation applied.
+ #[inline]
+ pub fn and(self, other: Self) -> Self {
+ BoolVector2D {
+ x: self.x && other.x,
+ y: self.y && other.y,
+ }
+ }
+
+ /// Returns new vector with by-component OR operation applied.
+ #[inline]
+ pub fn or(self, other: Self) -> Self {
+ BoolVector2D {
+ x: self.x || other.x,
+ y: self.y || other.y,
+ }
+ }
+
+ /// Returns new vector with results of negation operation on each component.
+ #[inline]
+ pub fn not(self) -> Self {
+ BoolVector2D {
+ x: !self.x,
+ y: !self.y,
+ }
+ }
+
+ /// Returns point, each component of which or from `a`, or from `b` depending on truly value
+ /// of corresponding vector component. `true` selects value from `a` and `false` from `b`.
+ #[inline]
+ pub fn select_point<T, U>(self, a: Point2D<T, U>, b: Point2D<T, U>) -> Point2D<T, U> {
+ point2(
+ if self.x { a.x } else { b.x },
+ if self.y { a.y } else { b.y },
+ )
+ }
+
+ /// Returns vector, each component of which or from `a`, or from `b` depending on truly value
+ /// of corresponding vector component. `true` selects value from `a` and `false` from `b`.
+ #[inline]
+ pub fn select_vector<T, U>(self, a: Vector2D<T, U>, b: Vector2D<T, U>) -> Vector2D<T, U> {
+ vec2(
+ if self.x { a.x } else { b.x },
+ if self.y { a.y } else { b.y },
+ )
+ }
+
+ /// Returns size, each component of which or from `a`, or from `b` depending on truly value
+ /// of corresponding vector component. `true` selects value from `a` and `false` from `b`.
+ #[inline]
+ pub fn select_size<T, U>(self, a: Size2D<T, U>, b: Size2D<T, U>) -> Size2D<T, U> {
+ size2(
+ if self.x { a.width } else { b.width },
+ if self.y { a.height } else { b.height },
+ )
+ }
+}
+
+impl BoolVector3D {
+ /// Returns `true` if all components are `true` and `false` otherwise.
+ #[inline]
+ pub fn all(self) -> bool {
+ self.x && self.y && self.z
+ }
+
+ /// Returns `true` if any component are `true` and `false` otherwise.
+ #[inline]
+ pub fn any(self) -> bool {
+ self.x || self.y || self.z
+ }
+
+ /// Returns `true` if all components are `false` and `false` otherwise. Negation of `any()`.
+ #[inline]
+ pub fn none(self) -> bool {
+ !self.any()
+ }
+
+ /// Returns new vector with by-component AND operation applied.
+ #[inline]
+ pub fn and(self, other: Self) -> Self {
+ BoolVector3D {
+ x: self.x && other.x,
+ y: self.y && other.y,
+ z: self.z && other.z,
+ }
+ }
+
+ /// Returns new vector with by-component OR operation applied.
+ #[inline]
+ pub fn or(self, other: Self) -> Self {
+ BoolVector3D {
+ x: self.x || other.x,
+ y: self.y || other.y,
+ z: self.z || other.z,
+ }
+ }
+
+ /// Returns new vector with results of negation operation on each component.
+ #[inline]
+ pub fn not(self) -> Self {
+ BoolVector3D {
+ x: !self.x,
+ y: !self.y,
+ z: !self.z,
+ }
+ }
+
+ /// Returns point, each component of which or from `a`, or from `b` depending on truly value
+ /// of corresponding vector component. `true` selects value from `a` and `false` from `b`.
+ #[inline]
+ pub fn select_point<T, U>(self, a: Point3D<T, U>, b: Point3D<T, U>) -> Point3D<T, U> {
+ point3(
+ if self.x { a.x } else { b.x },
+ if self.y { a.y } else { b.y },
+ if self.z { a.z } else { b.z },
+ )
+ }
+
+ /// Returns vector, each component of which or from `a`, or from `b` depending on truly value
+ /// of corresponding vector component. `true` selects value from `a` and `false` from `b`.
+ #[inline]
+ pub fn select_vector<T, U>(self, a: Vector3D<T, U>, b: Vector3D<T, U>) -> Vector3D<T, U> {
+ vec3(
+ if self.x { a.x } else { b.x },
+ if self.y { a.y } else { b.y },
+ if self.z { a.z } else { b.z },
+ )
+ }
+
+ /// Returns size, each component of which or from `a`, or from `b` depending on truly value
+ /// of corresponding vector component. `true` selects value from `a` and `false` from `b`.
+ #[inline]
+ #[must_use]
+ pub fn select_size<T, U>(self, a: Size3D<T, U>, b: Size3D<T, U>) -> Size3D<T, U> {
+ size3(
+ if self.x { a.width } else { b.width },
+ if self.y { a.height } else { b.height },
+ if self.z { a.depth } else { b.depth },
+ )
+ }
+
+ /// Returns a 2d vector using this vector's x and y coordinates.
+ #[inline]
+ pub fn xy(self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.x,
+ y: self.y,
+ }
+ }
+
+ /// Returns a 2d vector using this vector's x and z coordinates.
+ #[inline]
+ pub fn xz(self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.x,
+ y: self.z,
+ }
+ }
+
+ /// Returns a 2d vector using this vector's y and z coordinates.
+ #[inline]
+ pub fn yz(self) -> BoolVector2D {
+ BoolVector2D {
+ x: self.y,
+ y: self.z,
+ }
+ }
+}
+
+/// Convenience constructor.
+#[inline]
+pub const fn vec2<T, U>(x: T, y: T) -> Vector2D<T, U> {
+ Vector2D {
+ x,
+ y,
+ _unit: PhantomData,
+ }
+}
+
+/// Convenience constructor.
+#[inline]
+pub const fn vec3<T, U>(x: T, y: T, z: T) -> Vector3D<T, U> {
+ Vector3D {
+ x,
+ y,
+ z,
+ _unit: PhantomData,
+ }
+}
+
+/// Shorthand for `BoolVector2D { x, y }`.
+#[inline]
+pub const fn bvec2(x: bool, y: bool) -> BoolVector2D {
+ BoolVector2D { x, y }
+}
+
+/// Shorthand for `BoolVector3D { x, y, z }`.
+#[inline]
+pub const fn bvec3(x: bool, y: bool, z: bool) -> BoolVector3D {
+ BoolVector3D { x, y, z }
+}
+
+#[cfg(test)]
+mod vector2d {
+ use crate::scale::Scale;
+ use crate::{default, vec2};
+
+ #[cfg(feature = "mint")]
+ use mint;
+ type Vec2 = default::Vector2D<f32>;
+
+ #[test]
+ pub fn test_scalar_mul() {
+ let p1: Vec2 = vec2(3.0, 5.0);
+
+ let result = p1 * 5.0;
+
+ assert_eq!(result, Vec2::new(15.0, 25.0));
+ }
+
+ #[test]
+ pub fn test_dot() {
+ let p1: Vec2 = vec2(2.0, 7.0);
+ let p2: Vec2 = vec2(13.0, 11.0);
+ assert_eq!(p1.dot(p2), 103.0);
+ }
+
+ #[test]
+ pub fn test_cross() {
+ let p1: Vec2 = vec2(4.0, 7.0);
+ let p2: Vec2 = vec2(13.0, 8.0);
+ let r = p1.cross(p2);
+ assert_eq!(r, -59.0);
+ }
+
+ #[test]
+ pub fn test_normalize() {
+ use std::f32;
+
+ let p0: Vec2 = Vec2::zero();
+ let p1: Vec2 = vec2(4.0, 0.0);
+ let p2: Vec2 = vec2(3.0, -4.0);
+ assert!(p0.normalize().x.is_nan() && p0.normalize().y.is_nan());
+ assert_eq!(p1.normalize(), vec2(1.0, 0.0));
+ assert_eq!(p2.normalize(), vec2(0.6, -0.8));
+
+ let p3: Vec2 = vec2(::std::f32::MAX, ::std::f32::MAX);
+ assert_ne!(
+ p3.normalize(),
+ vec2(1.0 / 2.0f32.sqrt(), 1.0 / 2.0f32.sqrt())
+ );
+ assert_eq!(
+ p3.robust_normalize(),
+ vec2(1.0 / 2.0f32.sqrt(), 1.0 / 2.0f32.sqrt())
+ );
+
+ let p4: Vec2 = Vec2::zero();
+ assert!(p4.try_normalize().is_none());
+ let p5: Vec2 = Vec2::new(f32::MIN_POSITIVE, f32::MIN_POSITIVE);
+ assert!(p5.try_normalize().is_none());
+
+ let p6: Vec2 = vec2(4.0, 0.0);
+ let p7: Vec2 = vec2(3.0, -4.0);
+ assert_eq!(p6.try_normalize().unwrap(), vec2(1.0, 0.0));
+ assert_eq!(p7.try_normalize().unwrap(), vec2(0.6, -0.8));
+ }
+
+ #[test]
+ pub fn test_min() {
+ let p1: Vec2 = vec2(1.0, 3.0);
+ let p2: Vec2 = vec2(2.0, 2.0);
+
+ let result = p1.min(p2);
+
+ assert_eq!(result, vec2(1.0, 2.0));
+ }
+
+ #[test]
+ pub fn test_max() {
+ let p1: Vec2 = vec2(1.0, 3.0);
+ let p2: Vec2 = vec2(2.0, 2.0);
+
+ let result = p1.max(p2);
+
+ assert_eq!(result, vec2(2.0, 3.0));
+ }
+
+ #[test]
+ pub fn test_angle_from_x_axis() {
+ use crate::approxeq::ApproxEq;
+ use core::f32::consts::FRAC_PI_2;
+
+ let right: Vec2 = vec2(10.0, 0.0);
+ let down: Vec2 = vec2(0.0, 4.0);
+ let up: Vec2 = vec2(0.0, -1.0);
+
+ assert!(right.angle_from_x_axis().get().approx_eq(&0.0));
+ assert!(down.angle_from_x_axis().get().approx_eq(&FRAC_PI_2));
+ assert!(up.angle_from_x_axis().get().approx_eq(&-FRAC_PI_2));
+ }
+
+ #[test]
+ pub fn test_angle_to() {
+ use crate::approxeq::ApproxEq;
+ use core::f32::consts::FRAC_PI_2;
+
+ let right: Vec2 = vec2(10.0, 0.0);
+ let right2: Vec2 = vec2(1.0, 0.0);
+ let up: Vec2 = vec2(0.0, -1.0);
+ let up_left: Vec2 = vec2(-1.0, -1.0);
+
+ assert!(right.angle_to(right2).get().approx_eq(&0.0));
+ assert!(right.angle_to(up).get().approx_eq(&-FRAC_PI_2));
+ assert!(up.angle_to(right).get().approx_eq(&FRAC_PI_2));
+ assert!(up_left
+ .angle_to(up)
+ .get()
+ .approx_eq_eps(&(0.5 * FRAC_PI_2), &0.0005));
+ }
+
+ #[test]
+ pub fn test_with_max_length() {
+ use crate::approxeq::ApproxEq;
+
+ let v1: Vec2 = vec2(0.5, 0.5);
+ let v2: Vec2 = vec2(1.0, 0.0);
+ let v3: Vec2 = vec2(0.1, 0.2);
+ let v4: Vec2 = vec2(2.0, -2.0);
+ let v5: Vec2 = vec2(1.0, 2.0);
+ let v6: Vec2 = vec2(-1.0, 3.0);
+
+ assert_eq!(v1.with_max_length(1.0), v1);
+ assert_eq!(v2.with_max_length(1.0), v2);
+ assert_eq!(v3.with_max_length(1.0), v3);
+ assert_eq!(v4.with_max_length(10.0), v4);
+ assert_eq!(v5.with_max_length(10.0), v5);
+ assert_eq!(v6.with_max_length(10.0), v6);
+
+ let v4_clamped = v4.with_max_length(1.0);
+ assert!(v4_clamped.length().approx_eq(&1.0));
+ assert!(v4_clamped.normalize().approx_eq(&v4.normalize()));
+
+ let v5_clamped = v5.with_max_length(1.5);
+ assert!(v5_clamped.length().approx_eq(&1.5));
+ assert!(v5_clamped.normalize().approx_eq(&v5.normalize()));
+
+ let v6_clamped = v6.with_max_length(2.5);
+ assert!(v6_clamped.length().approx_eq(&2.5));
+ assert!(v6_clamped.normalize().approx_eq(&v6.normalize()));
+ }
+
+ #[test]
+ pub fn test_project_onto_vector() {
+ use crate::approxeq::ApproxEq;
+
+ let v1: Vec2 = vec2(1.0, 2.0);
+ let x: Vec2 = vec2(1.0, 0.0);
+ let y: Vec2 = vec2(0.0, 1.0);
+
+ assert!(v1.project_onto_vector(x).approx_eq(&vec2(1.0, 0.0)));
+ assert!(v1.project_onto_vector(y).approx_eq(&vec2(0.0, 2.0)));
+ assert!(v1.project_onto_vector(-x).approx_eq(&vec2(1.0, 0.0)));
+ assert!(v1.project_onto_vector(x * 10.0).approx_eq(&vec2(1.0, 0.0)));
+ assert!(v1.project_onto_vector(v1 * 2.0).approx_eq(&v1));
+ assert!(v1.project_onto_vector(-v1).approx_eq(&v1));
+ }
+
+ #[cfg(feature = "mint")]
+ #[test]
+ pub fn test_mint() {
+ let v1 = Vec2::new(1.0, 3.0);
+ let vm: mint::Vector2<_> = v1.into();
+ let v2 = Vec2::from(vm);
+
+ assert_eq!(v1, v2);
+ }
+
+ pub enum Mm {}
+ pub enum Cm {}
+
+ pub type Vector2DMm<T> = super::Vector2D<T, Mm>;
+ pub type Vector2DCm<T> = super::Vector2D<T, Cm>;
+
+ #[test]
+ pub fn test_add() {
+ let p1 = Vector2DMm::new(1.0, 2.0);
+ let p2 = Vector2DMm::new(3.0, 4.0);
+
+ assert_eq!(p1 + p2, vec2(4.0, 6.0));
+ assert_eq!(p1 + &p2, vec2(4.0, 6.0));
+ }
+
+ #[test]
+ pub fn test_sum() {
+ let vecs = [
+ Vector2DMm::new(1.0, 2.0),
+ Vector2DMm::new(3.0, 4.0),
+ Vector2DMm::new(5.0, 6.0)
+ ];
+ let sum = Vector2DMm::new(9.0, 12.0);
+ assert_eq!(vecs.iter().sum::<Vector2DMm<_>>(), sum);
+ }
+
+ #[test]
+ pub fn test_add_assign() {
+ let mut p1 = Vector2DMm::new(1.0, 2.0);
+ p1 += vec2(3.0, 4.0);
+
+ assert_eq!(p1, vec2(4.0, 6.0));
+ }
+
+ #[test]
+ pub fn test_tpyed_scalar_mul() {
+ let p1 = Vector2DMm::new(1.0, 2.0);
+ let cm_per_mm = Scale::<f32, Mm, Cm>::new(0.1);
+
+ let result: Vector2DCm<f32> = p1 * cm_per_mm;
+
+ assert_eq!(result, vec2(0.1, 0.2));
+ }
+
+ #[test]
+ pub fn test_swizzling() {
+ let p: default::Vector2D<i32> = vec2(1, 2);
+ assert_eq!(p.yx(), vec2(2, 1));
+ }
+
+ #[test]
+ pub fn test_reflect() {
+ use crate::approxeq::ApproxEq;
+ let a: Vec2 = vec2(1.0, 3.0);
+ let n1: Vec2 = vec2(0.0, -1.0);
+ let n2: Vec2 = vec2(1.0, -1.0).normalize();
+
+ assert!(a.reflect(n1).approx_eq(&vec2(1.0, -3.0)));
+ assert!(a.reflect(n2).approx_eq(&vec2(3.0, 1.0)));
+ }
+}
+
+#[cfg(test)]
+mod vector3d {
+ use crate::scale::Scale;
+ use crate::{default, vec2, vec3};
+ #[cfg(feature = "mint")]
+ use mint;
+
+ type Vec3 = default::Vector3D<f32>;
+
+ #[test]
+ pub fn test_add() {
+ let p1 = Vec3::new(1.0, 2.0, 3.0);
+ let p2 = Vec3::new(4.0, 5.0, 6.0);
+
+ assert_eq!(p1 + p2, vec3(5.0, 7.0, 9.0));
+ assert_eq!(p1 + &p2, vec3(5.0, 7.0, 9.0));
+ }
+
+ #[test]
+ pub fn test_sum() {
+ let vecs = [
+ Vec3::new(1.0, 2.0, 3.0),
+ Vec3::new(4.0, 5.0, 6.0),
+ Vec3::new(7.0, 8.0, 9.0)
+ ];
+ let sum = Vec3::new(12.0, 15.0, 18.0);
+ assert_eq!(vecs.iter().sum::<Vec3>(), sum);
+ }
+
+ #[test]
+ pub fn test_dot() {
+ let p1: Vec3 = vec3(7.0, 21.0, 32.0);
+ let p2: Vec3 = vec3(43.0, 5.0, 16.0);
+ assert_eq!(p1.dot(p2), 918.0);
+ }
+
+ #[test]
+ pub fn test_cross() {
+ let p1: Vec3 = vec3(4.0, 7.0, 9.0);
+ let p2: Vec3 = vec3(13.0, 8.0, 3.0);
+ let p3 = p1.cross(p2);
+ assert_eq!(p3, vec3(-51.0, 105.0, -59.0));
+ }
+
+ #[test]
+ pub fn test_normalize() {
+ use std::f32;
+
+ let p0: Vec3 = Vec3::zero();
+ let p1: Vec3 = vec3(0.0, -6.0, 0.0);
+ let p2: Vec3 = vec3(1.0, 2.0, -2.0);
+ assert!(
+ p0.normalize().x.is_nan() && p0.normalize().y.is_nan() && p0.normalize().z.is_nan()
+ );
+ assert_eq!(p1.normalize(), vec3(0.0, -1.0, 0.0));
+ assert_eq!(p2.normalize(), vec3(1.0 / 3.0, 2.0 / 3.0, -2.0 / 3.0));
+
+ let p3: Vec3 = vec3(::std::f32::MAX, ::std::f32::MAX, 0.0);
+ assert_ne!(
+ p3.normalize(),
+ vec3(1.0 / 2.0f32.sqrt(), 1.0 / 2.0f32.sqrt(), 0.0)
+ );
+ assert_eq!(
+ p3.robust_normalize(),
+ vec3(1.0 / 2.0f32.sqrt(), 1.0 / 2.0f32.sqrt(), 0.0)
+ );
+
+ let p4: Vec3 = Vec3::zero();
+ assert!(p4.try_normalize().is_none());
+ let p5: Vec3 = Vec3::new(f32::MIN_POSITIVE, f32::MIN_POSITIVE, f32::MIN_POSITIVE);
+ assert!(p5.try_normalize().is_none());
+
+ let p6: Vec3 = vec3(4.0, 0.0, 3.0);
+ let p7: Vec3 = vec3(3.0, -4.0, 0.0);
+ assert_eq!(p6.try_normalize().unwrap(), vec3(0.8, 0.0, 0.6));
+ assert_eq!(p7.try_normalize().unwrap(), vec3(0.6, -0.8, 0.0));
+ }
+
+ #[test]
+ pub fn test_min() {
+ let p1: Vec3 = vec3(1.0, 3.0, 5.0);
+ let p2: Vec3 = vec3(2.0, 2.0, -1.0);
+
+ let result = p1.min(p2);
+
+ assert_eq!(result, vec3(1.0, 2.0, -1.0));
+ }
+
+ #[test]
+ pub fn test_max() {
+ let p1: Vec3 = vec3(1.0, 3.0, 5.0);
+ let p2: Vec3 = vec3(2.0, 2.0, -1.0);
+
+ let result = p1.max(p2);
+
+ assert_eq!(result, vec3(2.0, 3.0, 5.0));
+ }
+
+ #[test]
+ pub fn test_clamp() {
+ let p1: Vec3 = vec3(1.0, -1.0, 5.0);
+ let p2: Vec3 = vec3(2.0, 5.0, 10.0);
+ let p3: Vec3 = vec3(-1.0, 2.0, 20.0);
+
+ let result = p3.clamp(p1, p2);
+
+ assert_eq!(result, vec3(1.0, 2.0, 10.0));
+ }
+
+ #[test]
+ pub fn test_typed_scalar_mul() {
+ enum Mm {}
+ enum Cm {}
+
+ let p1 = super::Vector3D::<f32, Mm>::new(1.0, 2.0, 3.0);
+ let cm_per_mm = Scale::<f32, Mm, Cm>::new(0.1);
+
+ let result: super::Vector3D<f32, Cm> = p1 * cm_per_mm;
+
+ assert_eq!(result, vec3(0.1, 0.2, 0.3));
+ }
+
+ #[test]
+ pub fn test_swizzling() {
+ let p: Vec3 = vec3(1.0, 2.0, 3.0);
+ assert_eq!(p.xy(), vec2(1.0, 2.0));
+ assert_eq!(p.xz(), vec2(1.0, 3.0));
+ assert_eq!(p.yz(), vec2(2.0, 3.0));
+ }
+
+ #[cfg(feature = "mint")]
+ #[test]
+ pub fn test_mint() {
+ let v1 = Vec3::new(1.0, 3.0, 5.0);
+ let vm: mint::Vector3<_> = v1.into();
+ let v2 = Vec3::from(vm);
+
+ assert_eq!(v1, v2);
+ }
+
+ #[test]
+ pub fn test_reflect() {
+ use crate::approxeq::ApproxEq;
+ let a: Vec3 = vec3(1.0, 3.0, 2.0);
+ let n1: Vec3 = vec3(0.0, -1.0, 0.0);
+ let n2: Vec3 = vec3(0.0, 1.0, 1.0).normalize();
+
+ assert!(a.reflect(n1).approx_eq(&vec3(1.0, -3.0, 2.0)));
+ assert!(a.reflect(n2).approx_eq(&vec3(1.0, -2.0, -3.0)));
+ }
+
+ #[test]
+ pub fn test_angle_to() {
+ use crate::approxeq::ApproxEq;
+ use core::f32::consts::FRAC_PI_2;
+
+ let right: Vec3 = vec3(10.0, 0.0, 0.0);
+ let right2: Vec3 = vec3(1.0, 0.0, 0.0);
+ let up: Vec3 = vec3(0.0, -1.0, 0.0);
+ let up_left: Vec3 = vec3(-1.0, -1.0, 0.0);
+
+ assert!(right.angle_to(right2).get().approx_eq(&0.0));
+ assert!(right.angle_to(up).get().approx_eq(&FRAC_PI_2));
+ assert!(up.angle_to(right).get().approx_eq(&FRAC_PI_2));
+ assert!(up_left
+ .angle_to(up)
+ .get()
+ .approx_eq_eps(&(0.5 * FRAC_PI_2), &0.0005));
+ }
+
+ #[test]
+ pub fn test_with_max_length() {
+ use crate::approxeq::ApproxEq;
+
+ let v1: Vec3 = vec3(0.5, 0.5, 0.0);
+ let v2: Vec3 = vec3(1.0, 0.0, 0.0);
+ let v3: Vec3 = vec3(0.1, 0.2, 0.3);
+ let v4: Vec3 = vec3(2.0, -2.0, 2.0);
+ let v5: Vec3 = vec3(1.0, 2.0, -3.0);
+ let v6: Vec3 = vec3(-1.0, 3.0, 2.0);
+
+ assert_eq!(v1.with_max_length(1.0), v1);
+ assert_eq!(v2.with_max_length(1.0), v2);
+ assert_eq!(v3.with_max_length(1.0), v3);
+ assert_eq!(v4.with_max_length(10.0), v4);
+ assert_eq!(v5.with_max_length(10.0), v5);
+ assert_eq!(v6.with_max_length(10.0), v6);
+
+ let v4_clamped = v4.with_max_length(1.0);
+ assert!(v4_clamped.length().approx_eq(&1.0));
+ assert!(v4_clamped.normalize().approx_eq(&v4.normalize()));
+
+ let v5_clamped = v5.with_max_length(1.5);
+ assert!(v5_clamped.length().approx_eq(&1.5));
+ assert!(v5_clamped.normalize().approx_eq(&v5.normalize()));
+
+ let v6_clamped = v6.with_max_length(2.5);
+ assert!(v6_clamped.length().approx_eq(&2.5));
+ assert!(v6_clamped.normalize().approx_eq(&v6.normalize()));
+ }
+
+ #[test]
+ pub fn test_project_onto_vector() {
+ use crate::approxeq::ApproxEq;
+
+ let v1: Vec3 = vec3(1.0, 2.0, 3.0);
+ let x: Vec3 = vec3(1.0, 0.0, 0.0);
+ let y: Vec3 = vec3(0.0, 1.0, 0.0);
+ let z: Vec3 = vec3(0.0, 0.0, 1.0);
+
+ assert!(v1.project_onto_vector(x).approx_eq(&vec3(1.0, 0.0, 0.0)));
+ assert!(v1.project_onto_vector(y).approx_eq(&vec3(0.0, 2.0, 0.0)));
+ assert!(v1.project_onto_vector(z).approx_eq(&vec3(0.0, 0.0, 3.0)));
+ assert!(v1.project_onto_vector(-x).approx_eq(&vec3(1.0, 0.0, 0.0)));
+ assert!(v1
+ .project_onto_vector(x * 10.0)
+ .approx_eq(&vec3(1.0, 0.0, 0.0)));
+ assert!(v1.project_onto_vector(v1 * 2.0).approx_eq(&v1));
+ assert!(v1.project_onto_vector(-v1).approx_eq(&v1));
+ }
+}
+
+#[cfg(test)]
+mod bool_vector {
+ use super::*;
+ use crate::default;
+ type Vec2 = default::Vector2D<f32>;
+ type Vec3 = default::Vector3D<f32>;
+
+ #[test]
+ fn test_bvec2() {
+ assert_eq!(
+ Vec2::new(1.0, 2.0).greater_than(Vec2::new(2.0, 1.0)),
+ bvec2(false, true),
+ );
+
+ assert_eq!(
+ Vec2::new(1.0, 2.0).lower_than(Vec2::new(2.0, 1.0)),
+ bvec2(true, false),
+ );
+
+ assert_eq!(
+ Vec2::new(1.0, 2.0).equal(Vec2::new(1.0, 3.0)),
+ bvec2(true, false),
+ );
+
+ assert_eq!(
+ Vec2::new(1.0, 2.0).not_equal(Vec2::new(1.0, 3.0)),
+ bvec2(false, true),
+ );
+
+ assert!(bvec2(true, true).any());
+ assert!(bvec2(false, true).any());
+ assert!(bvec2(true, false).any());
+ assert!(!bvec2(false, false).any());
+ assert!(bvec2(false, false).none());
+ assert!(bvec2(true, true).all());
+ assert!(!bvec2(false, true).all());
+ assert!(!bvec2(true, false).all());
+ assert!(!bvec2(false, false).all());
+
+ assert_eq!(bvec2(true, false).not(), bvec2(false, true));
+ assert_eq!(
+ bvec2(true, false).and(bvec2(true, true)),
+ bvec2(true, false)
+ );
+ assert_eq!(bvec2(true, false).or(bvec2(true, true)), bvec2(true, true));
+
+ assert_eq!(
+ bvec2(true, false).select_vector(Vec2::new(1.0, 2.0), Vec2::new(3.0, 4.0)),
+ Vec2::new(1.0, 4.0),
+ );
+ }
+
+ #[test]
+ fn test_bvec3() {
+ assert_eq!(
+ Vec3::new(1.0, 2.0, 3.0).greater_than(Vec3::new(3.0, 2.0, 1.0)),
+ bvec3(false, false, true),
+ );
+
+ assert_eq!(
+ Vec3::new(1.0, 2.0, 3.0).lower_than(Vec3::new(3.0, 2.0, 1.0)),
+ bvec3(true, false, false),
+ );
+
+ assert_eq!(
+ Vec3::new(1.0, 2.0, 3.0).equal(Vec3::new(3.0, 2.0, 1.0)),
+ bvec3(false, true, false),
+ );
+
+ assert_eq!(
+ Vec3::new(1.0, 2.0, 3.0).not_equal(Vec3::new(3.0, 2.0, 1.0)),
+ bvec3(true, false, true),
+ );
+
+ assert!(bvec3(true, true, false).any());
+ assert!(bvec3(false, true, false).any());
+ assert!(bvec3(true, false, false).any());
+ assert!(!bvec3(false, false, false).any());
+ assert!(bvec3(false, false, false).none());
+ assert!(bvec3(true, true, true).all());
+ assert!(!bvec3(false, true, false).all());
+ assert!(!bvec3(true, false, false).all());
+ assert!(!bvec3(false, false, false).all());
+
+ assert_eq!(bvec3(true, false, true).not(), bvec3(false, true, false));
+ assert_eq!(
+ bvec3(true, false, true).and(bvec3(true, true, false)),
+ bvec3(true, false, false)
+ );
+ assert_eq!(
+ bvec3(true, false, false).or(bvec3(true, true, false)),
+ bvec3(true, true, false)
+ );
+
+ assert_eq!(
+ bvec3(true, false, true)
+ .select_vector(Vec3::new(1.0, 2.0, 3.0), Vec3::new(4.0, 5.0, 6.0)),
+ Vec3::new(1.0, 5.0, 3.0),
+ );
+ }
+}