Skip to content

Commit 9f376df

Browse files
authored
Add inverse_mul and inverse_transform_point for isometries (#14311)
# Objective The isometry types added in #14269 support transforming other isometries and points, as well as computing the inverse of an isometry using `inverse`. However, transformations like `iso1.inverse() * iso2` and `iso.inverse() * point` can be optimized for single-shot cases using custom methods that avoid an extra rotation operation. ## Solution Add `inverse_mul` and `inverse_transform_point` for `Isometry2d` and `Isometry3d`. Note that these methods are only faster when the isometry can't be reused for multiple transformations. ## Testing All of the methods have a test, similarly to the existing transformation operations.
1 parent 22b65b7 commit 9f376df

File tree

1 file changed

+73
-1
lines changed

1 file changed

+73
-1
lines changed

crates/bevy_math/src/isometry.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,32 @@ impl Isometry2d {
9090
}
9191
}
9292

93+
/// Compute `iso1.inverse() * iso2` in a more efficient way for one-shot cases.
94+
///
95+
/// If the same isometry is used multiple times, it is more efficient to instead compute
96+
/// the inverse once and use that for each transformation.
97+
#[inline]
98+
pub fn inverse_mul(&self, rhs: Self) -> Self {
99+
let inv_rot = self.rotation.inverse();
100+
let delta_translation = rhs.translation - self.translation;
101+
Self::new(inv_rot * delta_translation, inv_rot * rhs.rotation)
102+
}
103+
93104
/// Transform a point by rotating and translating it using this isometry.
94105
#[inline]
95106
pub fn transform_point(&self, point: Vec2) -> Vec2 {
96107
self.rotation * point + self.translation
97108
}
109+
110+
/// Transform a point by rotating and translating it using the inverse of this isometry.
111+
///
112+
/// This is more efficient than `iso.inverse().transform_point(point)` for one-shot cases.
113+
/// If the same isometry is used multiple times, it is more efficient to instead compute
114+
/// the inverse once and use that for each transformation.
115+
#[inline]
116+
pub fn inverse_transform_point(&self, point: Vec2) -> Vec2 {
117+
self.rotation.inverse() * (point - self.translation)
118+
}
98119
}
99120

100121
impl From<Isometry2d> for Affine2 {
@@ -257,11 +278,32 @@ impl Isometry3d {
257278
}
258279
}
259280

281+
/// Compute `iso1.inverse() * iso2` in a more efficient way for one-shot cases.
282+
///
283+
/// If the same isometry is used multiple times, it is more efficient to instead compute
284+
/// the inverse once and use that for each transformation.
285+
#[inline]
286+
pub fn inverse_mul(&self, rhs: Self) -> Self {
287+
let inv_rot = self.rotation.inverse();
288+
let delta_translation = rhs.translation - self.translation;
289+
Self::new(inv_rot * delta_translation, inv_rot * rhs.rotation)
290+
}
291+
260292
/// Transform a point by rotating and translating it using this isometry.
261293
#[inline]
262294
pub fn transform_point(&self, point: impl Into<Vec3A>) -> Vec3A {
263295
self.rotation * point.into() + self.translation
264296
}
297+
298+
/// Transform a point by rotating and translating it using the inverse of this isometry.
299+
///
300+
/// This is more efficient than `iso.inverse().transform_point(point)` for one-shot cases.
301+
/// If the same isometry is used multiple times, it is more efficient to instead compute
302+
/// the inverse once and use that for each transformation.
303+
#[inline]
304+
pub fn inverse_transform_point(&self, point: impl Into<Vec3A>) -> Vec3A {
305+
self.rotation.inverse() * (point.into() - self.translation)
306+
}
265307
}
266308

267309
impl From<Isometry3d> for Affine3 {
@@ -374,7 +416,7 @@ impl UlpsEq for Isometry3d {
374416
#[cfg(test)]
375417
mod tests {
376418
use super::*;
377-
use crate::{vec2, vec3};
419+
use crate::{vec2, vec3, vec3a};
378420
use approx::assert_abs_diff_eq;
379421
use std::f32::consts::{FRAC_PI_2, FRAC_PI_3};
380422

@@ -386,6 +428,14 @@ mod tests {
386428
assert_abs_diff_eq!(iso1 * iso2, expected);
387429
}
388430

431+
#[test]
432+
fn inverse_mul_2d() {
433+
let iso1 = Isometry2d::new(vec2(1.0, 0.0), Rot2::FRAC_PI_2);
434+
let iso2 = Isometry2d::new(vec2(0.0, 0.0), Rot2::PI);
435+
let expected = Isometry2d::new(vec2(0.0, 1.0), Rot2::FRAC_PI_2);
436+
assert_abs_diff_eq!(iso1.inverse_mul(iso2), expected);
437+
}
438+
389439
#[test]
390440
fn mul_3d() {
391441
let iso1 = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_x(FRAC_PI_2));
@@ -394,6 +444,14 @@ mod tests {
394444
assert_abs_diff_eq!(iso1 * iso2, expected);
395445
}
396446

447+
#[test]
448+
fn inverse_mul_3d() {
449+
let iso1 = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_x(FRAC_PI_2));
450+
let iso2 = Isometry3d::new(vec3(1.0, 0.0, 1.0), Quat::from_rotation_x(FRAC_PI_2));
451+
let expected = Isometry3d::new(vec3(0.0, 1.0, 0.0), Quat::IDENTITY);
452+
assert_abs_diff_eq!(iso1.inverse_mul(iso2), expected);
453+
}
454+
397455
#[test]
398456
fn identity_2d() {
399457
let iso = Isometry2d::new(vec2(-1.0, -0.5), Rot2::degrees(75.0));
@@ -431,10 +489,24 @@ mod tests {
431489
assert_abs_diff_eq!(vec2(-0.5, 0.5), iso * point);
432490
}
433491

492+
#[test]
493+
fn inverse_transform_2d() {
494+
let iso = Isometry2d::new(vec2(0.5, -0.5), Rot2::FRAC_PI_2);
495+
let point = vec2(-0.5, 0.5);
496+
assert_abs_diff_eq!(vec2(1.0, 1.0), iso.inverse_transform_point(point));
497+
}
498+
434499
#[test]
435500
fn transform_3d() {
436501
let iso = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_y(FRAC_PI_2));
437502
let point = vec3(1.0, 1.0, 1.0);
438503
assert_abs_diff_eq!(vec3(2.0, 1.0, -1.0), iso * point);
439504
}
505+
506+
#[test]
507+
fn inverse_transform_3d() {
508+
let iso = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_y(FRAC_PI_2));
509+
let point = vec3(2.0, 1.0, -1.0);
510+
assert_abs_diff_eq!(vec3a(1.0, 1.0, 1.0), iso.inverse_transform_point(point));
511+
}
440512
}

0 commit comments

Comments
 (0)