-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add inverse method to AffineTransform #1092
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is clearly useful. I had one take-it-or-leave it thing, but LGTM.
geo/src/algorithm/affine_ops.rs
Outdated
let inv_det = T::one() / determinant; | ||
Some(Self::new( | ||
e * inv_det, | ||
T::from(-b * inv_det).unwrap(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These T::from
are kind of unfortunate.
To get rid of them, we could constrain these newly added Neg
's to instead be Neg<Output=T>
?
Or do we actually want to support Neg w/ other Outputs? I don't think it really even occurred to me before that you could specify the output of Neg. I'm guessing it's for when you want to do something like:
// unsigned
impl Neg for MyUnsignedNumber {
// output of negation can be negative, so output is signed
type Output= MySignedNumber;
...
}
maybe that's why? I have no idea.
Anyway, if you'd rather keep it, it's NBD. I wouldn't be super surprised if it's all optimized away anyway. 😅
geo/src/algorithm/affine_ops.rs
Outdated
@@ -111,16 +115,16 @@ impl<T: CoordNum, M: MapCoordsInPlace<T> + MapCoords<T, T, Output = Self>> Affin | |||
/// ], max_relative = 1.0); | |||
/// ``` | |||
#[derive(Copy, Clone, PartialEq, Eq)] | |||
pub struct AffineTransform<T: CoordNum = f64>([[T; 3]; 3]); | |||
pub struct AffineTransform<T: CoordNum + Neg = f64>([[T; 3]; 3]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we actually need to add these Neg
bounds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In some form, because they're needed by the arithmetic that produces the determinant (according to the compiler).
But I haven't tried @michaelkirk's suggestion (see his previous comment) yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it's only needed for inverse
, right? It still seems to build if I split out inverse
into its own impl
block and drop the new Neg
bounds from everywhere else:
impl<T: CoordNum + Neg> AffineTransform<T> {
/// Return the inverse of a given transform. Composing a transform with its inverse yields
/// the [identity matrix](Self::identity)
pub fn inverse(&self) -> Option<Self>
where
<T as Neg>::Output: Mul<T>,
<<T as Neg>::Output as Mul<T>>::Output: ToPrimitive,
{
let a = self.0[0][0];
let b = self.0[0][1];
let xoff = self.0[0][2];
let d = self.0[1][0];
let e = self.0[1][1];
let yoff = self.0[1][2];
let determinant = a * e - b * d;
if determinant == T::zero() {
return None; // The matrix is not invertible
}
let inv_det = T::one() / determinant;
Some(Self::new(
e * inv_det,
T::from(-b * inv_det).unwrap(),
(b * yoff - e * xoff) * inv_det,
T::from(-d * inv_det).unwrap(),
a * inv_det,
(d * xoff - a * yoff) * inv_det,
))
}
}
PS: should probably be #[must_use]
too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both of these are good points!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though I'd recommend doing:
impl<T: CoordNum + Neg<Output=T>> AffineTransform<T> {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it's only needed for
inverse
, right? It still seems to build if I split outinverse
into its ownimpl
block and drop the newNeg
bounds from everywhere else:impl<T: CoordNum + Neg> AffineTransform<T> { /// Return the inverse of a given transform. Composing a transform with its inverse yields /// the [identity matrix](Self::identity) pub fn inverse(&self) -> Option<Self> where <T as Neg>::Output: Mul<T>, <<T as Neg>::Output as Mul<T>>::Output: ToPrimitive, { let a = self.0[0][0]; let b = self.0[0][1]; let xoff = self.0[0][2]; let d = self.0[1][0]; let e = self.0[1][1]; let yoff = self.0[1][2]; let determinant = a * e - b * d; if determinant == T::zero() { return None; // The matrix is not invertible } let inv_det = T::one() / determinant; Some(Self::new( e * inv_det, T::from(-b * inv_det).unwrap(), (b * yoff - e * xoff) * inv_det, T::from(-d * inv_det).unwrap(), a * inv_det, (d * xoff - a * yoff) * inv_det, )) } }PS: should probably be
#[must_use]
too.
Ahh, it didn't occur to me to split it into its own impl. This is much better I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also return None
if the cast fails..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh and breaking out this impl means it's no longer a breaking change, nice!
CHANGES.md
if knowledge of this change could be valuable to users.This is technically breaking due to the addition of an additional trait bound (Neg
). In practice, it's unlikely to affect library consumers.