Add egui::math::Rot2 rotation helper
This commit is contained in:
parent
bb469bf52f
commit
36c15c4e41
7 changed files with 197 additions and 34 deletions
|
@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
* Mouse-over explanation to duplicate ID warning.
|
* Mouse-over explanation to duplicate ID warning.
|
||||||
* You can now easily constrain Egui to a portion of the screen using `RawInput::screen_rect`.
|
* You can now easily constrain Egui to a portion of the screen using `RawInput::screen_rect`.
|
||||||
* You can now control the minimum and maixumum number of decimals to show in a `Slider` or `DragValue`.
|
* You can now control the minimum and maixumum number of decimals to show in a `Slider` or `DragValue`.
|
||||||
|
* Add `egui::math::Rot2`: rotation helper.
|
||||||
|
|
||||||
### Changed 🔧
|
### Changed 🔧
|
||||||
|
|
||||||
|
|
|
@ -109,11 +109,9 @@ pub fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) {
|
||||||
// Draw a pointy triangle arrow:
|
// Draw a pointy triangle arrow:
|
||||||
let rect = Rect::from_center_size(rect.center(), vec2(rect.width(), rect.height()) * 0.75);
|
let rect = Rect::from_center_size(rect.center(), vec2(rect.width(), rect.height()) * 0.75);
|
||||||
let mut points = vec![rect.left_top(), rect.right_top(), rect.center_bottom()];
|
let mut points = vec![rect.left_top(), rect.right_top(), rect.center_bottom()];
|
||||||
let rotation = Vec2::angled(remap(openness, 0.0..=1.0, -TAU / 4.0..=0.0));
|
let rotation = Rot2::from_angle(remap(openness, 0.0..=1.0, -TAU / 4.0..=0.0));
|
||||||
for p in &mut points {
|
for p in &mut points {
|
||||||
let v = *p - rect.center();
|
*p = rect.center() + rotation * (*p - rect.center());
|
||||||
let v = rotation.rotate_other(v);
|
|
||||||
*p = rect.center() + v;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.painter().add(PaintCmd::closed_line(points, stroke));
|
ui.painter().add(PaintCmd::closed_line(points, stroke));
|
||||||
|
|
|
@ -141,8 +141,8 @@ impl FractalClock {
|
||||||
];
|
];
|
||||||
|
|
||||||
let hand_rotors = [
|
let hand_rotors = [
|
||||||
hands[0].length * Vec2::angled(hand_rotations[0]),
|
hands[0].length * Rot2::from_angle(hand_rotations[0]),
|
||||||
hands[1].length * Vec2::angled(hand_rotations[1]),
|
hands[1].length * Rot2::from_angle(hand_rotations[1]),
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -179,9 +179,9 @@ impl FractalClock {
|
||||||
|
|
||||||
let luminance_u8 = (255.0 * luminance).round() as u8;
|
let luminance_u8 = (255.0 * luminance).round() as u8;
|
||||||
|
|
||||||
for rotor in &hand_rotors {
|
for &rotor in &hand_rotors {
|
||||||
for a in &nodes {
|
for a in &nodes {
|
||||||
let new_dir = rotor.rotate_other(a.dir);
|
let new_dir = rotor * a.dir;
|
||||||
let b = Node {
|
let b = Node {
|
||||||
pos: a.pos + new_dir,
|
pos: a.pos + new_dir,
|
||||||
dir: new_dir,
|
dir: new_dir,
|
||||||
|
|
|
@ -6,10 +6,11 @@ use std::ops::{Add, Div, Mul, RangeInclusive, Sub};
|
||||||
|
|
||||||
mod pos2;
|
mod pos2;
|
||||||
mod rect;
|
mod rect;
|
||||||
|
mod rot2;
|
||||||
pub mod smart_aim;
|
pub mod smart_aim;
|
||||||
mod vec2;
|
mod vec2;
|
||||||
|
|
||||||
pub use {pos2::*, rect::*, vec2::*};
|
pub use {pos2::*, rect::*, rot2::*, vec2::*};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
183
egui/src/math/rot2.rs
Normal file
183
egui/src/math/rot2.rs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
use super::Vec2;
|
||||||
|
|
||||||
|
// {s,c} represents the rotation matrix:
|
||||||
|
//
|
||||||
|
// | c -s |
|
||||||
|
// | s c |
|
||||||
|
//
|
||||||
|
// `vec2(c,s)` represents where the X axis will end up after rotation.
|
||||||
|
//
|
||||||
|
/// Represents a rotation in the 2D plane.
|
||||||
|
/// A rotation of 90° rotates the X axis to the Y axis.
|
||||||
|
/// Normally a `Rot2` is normalized (unit-length).
|
||||||
|
/// If not, it will also scale vectors.
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub struct Rot2 {
|
||||||
|
/// angle.sin()
|
||||||
|
s: f32,
|
||||||
|
/// angle.cos()
|
||||||
|
c: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Identity rotation
|
||||||
|
impl Default for Rot2 {
|
||||||
|
/// Identity rotation
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { s: 0.0, c: 1.0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rot2 {
|
||||||
|
pub fn identity() -> Self {
|
||||||
|
Self { s: 0.0, c: 1.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `TAU / 4.0` rotation means rotating the X axis to the Y axis.
|
||||||
|
pub fn from_angle(angle: f32) -> Self {
|
||||||
|
let (s, c) = angle.sin_cos();
|
||||||
|
Self { s, c }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn angle(self) -> f32 {
|
||||||
|
self.s.atan2(self.c)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The factor by which vectors will be scaled.
|
||||||
|
pub fn length(self) -> f32 {
|
||||||
|
self.c.hypot(self.s)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length_squared(self) -> f32 {
|
||||||
|
self.c.powi(2) + self.s.powi(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_finite(self) -> bool {
|
||||||
|
self.c.is_finite() && self.s.is_finite()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn inverse(self) -> Rot2 {
|
||||||
|
Self {
|
||||||
|
s: -self.s,
|
||||||
|
c: self.c,
|
||||||
|
} / self.length_squared()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn normalized(self) -> Self {
|
||||||
|
let l = self.length();
|
||||||
|
let ret = Self {
|
||||||
|
c: self.c / l,
|
||||||
|
s: self.s / l,
|
||||||
|
};
|
||||||
|
debug_assert!(ret.is_finite());
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Rot2 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Rot2 {{ angle: {:.1}°, length: {} }}",
|
||||||
|
self.angle().to_degrees(),
|
||||||
|
self.length()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<Rot2> for Rot2 {
|
||||||
|
type Output = Rot2;
|
||||||
|
fn mul(self, r: Rot2) -> Rot2 {
|
||||||
|
/*
|
||||||
|
|lc -ls| * |rc -rs|
|
||||||
|
|ls lc| |rs rc|
|
||||||
|
*/
|
||||||
|
Rot2 {
|
||||||
|
c: self.c * r.c - self.s * r.s,
|
||||||
|
s: self.s * r.c + self.c * r.s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<Vec2> for Rot2 {
|
||||||
|
type Output = Vec2;
|
||||||
|
fn mul(self, v: Vec2) -> Vec2 {
|
||||||
|
Vec2 {
|
||||||
|
x: self.c * v.x - self.s * v.y,
|
||||||
|
y: self.s * v.x + self.c * v.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<Rot2> for f32 {
|
||||||
|
type Output = Rot2;
|
||||||
|
fn mul(self, r: Rot2) -> Rot2 {
|
||||||
|
Rot2 {
|
||||||
|
c: self * r.c,
|
||||||
|
s: self * r.s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<f32> for Rot2 {
|
||||||
|
type Output = Rot2;
|
||||||
|
fn mul(self, r: f32) -> Rot2 {
|
||||||
|
Rot2 {
|
||||||
|
c: self.c * r,
|
||||||
|
s: self.s * r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Div<f32> for Rot2 {
|
||||||
|
type Output = Rot2;
|
||||||
|
fn div(self, r: f32) -> Rot2 {
|
||||||
|
Rot2 {
|
||||||
|
c: self.c / r,
|
||||||
|
s: self.s / r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Rot2;
|
||||||
|
use crate::vec2;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rotation2() {
|
||||||
|
{
|
||||||
|
let angle = std::f32::consts::TAU / 6.0;
|
||||||
|
let rot = Rot2::from_angle(angle);
|
||||||
|
assert!((rot.angle() - angle).abs() < 1e-5);
|
||||||
|
assert!((rot * rot.inverse()).angle().abs() < 1e-5);
|
||||||
|
assert!((rot.inverse() * rot).angle().abs() < 1e-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let angle = std::f32::consts::TAU / 4.0;
|
||||||
|
let rot = Rot2::from_angle(angle);
|
||||||
|
assert!(((rot * vec2(1.0, 0.0)) - vec2(0.0, 1.0)).length() < 1e-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test rotation and scaling
|
||||||
|
let angle = std::f32::consts::TAU / 4.0;
|
||||||
|
let rot = 3.0 * Rot2::from_angle(angle);
|
||||||
|
let rotated = rot * vec2(1.0, 0.0);
|
||||||
|
let expected = vec2(0.0, 3.0);
|
||||||
|
assert!(
|
||||||
|
(rotated - expected).length() < 1e-5,
|
||||||
|
"Expected {:?} to equal {:?}. rot: {:?}",
|
||||||
|
rotated,
|
||||||
|
expected,
|
||||||
|
rot,
|
||||||
|
);
|
||||||
|
|
||||||
|
let undone = rot.inverse() * rot;
|
||||||
|
assert!(undone.angle().abs() < 1e-5);
|
||||||
|
assert!((undone.length() - 1.0).abs() < 1e-5,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,6 +60,8 @@ impl Vec2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rotates the vector by 90°, i.e positive X to positive Y
|
||||||
|
/// (clockwise in Egui coordinates).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn rot90(self) -> Self {
|
pub fn rot90(self) -> Self {
|
||||||
vec2(self.y, -self.x)
|
vec2(self.y, -self.x)
|
||||||
|
@ -85,16 +87,6 @@ impl Vec2 {
|
||||||
vec2(angle.cos(), angle.sin())
|
vec2(angle.cos(), angle.sin())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use this vector as a rotor, rotating something else.
|
|
||||||
/// Example: `Vec2::angled(angle).rotate_other(some_vec)`
|
|
||||||
#[must_use]
|
|
||||||
pub fn rotate_other(self, v: Vec2) -> Self {
|
|
||||||
Self {
|
|
||||||
x: v.x * self.x + v.y * -self.y,
|
|
||||||
y: v.x * self.y + v.y * self.x,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn floor(self) -> Self {
|
pub fn floor(self) -> Self {
|
||||||
vec2(self.x.floor(), self.y.floor())
|
vec2(self.x.floor(), self.y.floor())
|
||||||
|
|
|
@ -159,22 +159,10 @@ impl Painter {
|
||||||
let tip_length = full_length / 3.0;
|
let tip_length = full_length / 3.0;
|
||||||
let dir = dir.normalized();
|
let dir = dir.normalized();
|
||||||
let tip = origin + dir * full_length;
|
let tip = origin + dir * full_length;
|
||||||
let angle = TAU / 10.0;
|
let rot = Rot2::from_angle(TAU / 10.0);
|
||||||
self.line_segment([origin, tip], stroke);
|
self.line_segment([origin, tip], stroke);
|
||||||
self.line_segment(
|
self.line_segment([tip, tip - tip_length * (rot * dir)], stroke);
|
||||||
[
|
self.line_segment([tip, tip - tip_length * (rot.inverse() * dir)], stroke);
|
||||||
tip,
|
|
||||||
tip - tip_length * Vec2::angled(angle).rotate_other(dir),
|
|
||||||
],
|
|
||||||
stroke,
|
|
||||||
);
|
|
||||||
self.line_segment(
|
|
||||||
[
|
|
||||||
tip,
|
|
||||||
tip - tip_length * Vec2::angled(-angle).rotate_other(dir),
|
|
||||||
],
|
|
||||||
stroke,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue