use std::f64::consts::PI;
#[cfg(feature = "egui")]
use egui::{Pos2, Vec2};
use gerber_types::{AxisSelect, ImageMirroring};
use nalgebra::{Matrix3, Point2, Vector2, Vector3};
use crate::geometry::mirroring::Mirroring;
#[derive(Debug, Copy, Clone)]
pub struct GerberTransform {
pub rotation: f32,
pub mirroring: Mirroring,
pub origin: Vector2<f64>,
pub offset: Vector2<f64>,
pub scale: f64,
}
impl Default for GerberTransform {
fn default() -> Self {
Self {
rotation: 0.0,
mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0),
offset: Vector2::new(0.0, 0.0),
scale: 1.0,
}
}
}
impl GerberTransform {
#[inline]
pub fn apply_to_position(&self, pos: Point2<f64>) -> Point2<f64> {
let pos_adjusted = (pos.x - self.origin.x, pos.y - self.origin.y);
let (mirrored_x, mirrored_y) = self.mirroring * pos_adjusted;
let rotation = self.rotation as f64;
let cos_angle = rotation.cos();
let sin_angle = rotation.sin();
let rotated_x = mirrored_x * cos_angle - mirrored_y * sin_angle;
let rotated_y = mirrored_x * sin_angle + mirrored_y * cos_angle;
Point2::new(
rotated_x * self.scale + self.offset.x + self.origin.x,
rotated_y * self.scale + self.offset.y + self.origin.y,
)
}
#[cfg(feature = "egui")]
#[inline]
pub fn apply_to_pos2(&self, pos: Pos2) -> Vec2 {
let pos_adjusted = (pos.x as f64 - self.origin.x, pos.y as f64 - self.origin.y);
let (mirrored_x, mirrored_y) = self.mirroring * pos_adjusted;
let (sin_theta, cos_theta) = (-self.rotation as f64).sin_cos();
let rotated_x = mirrored_x * cos_theta - mirrored_y * sin_theta;
let rotated_y = mirrored_x * sin_theta + mirrored_y * cos_theta;
Vec2::new(
(rotated_x * self.scale + self.origin.x + self.offset.x) as f32,
(rotated_y * self.scale + self.origin.y + self.offset.y) as f32,
)
}
pub fn flip_y(mut self) -> Self {
self.offset.y = -self.offset.y;
self.origin.y = -self.origin.y;
self
}
}
impl GerberTransform {
pub fn to_matrix(&self) -> Matrix3<f64> {
let translate_neg_origin = Matrix3::new(1.0, 0.0, -self.origin.x, 0.0, 1.0, -self.origin.y, 0.0, 0.0, 1.0);
let rad = self.rotation as f64;
let cos_rad = rad.cos();
let sin_rad = rad.sin();
let rotation_matrix = Matrix3::new(cos_rad, -sin_rad, 0.0, sin_rad, cos_rad, 0.0, 0.0, 0.0, 1.0);
let [mirror_x, mirror_y] = self.mirroring.as_f64();
let mirroring_matrix = Matrix3::new(mirror_x, 0.0, 0.0, 0.0, mirror_y, 0.0, 0.0, 0.0, 1.0);
let scaling_matrix = Matrix3::new(self.scale, 0.0, 0.0, 0.0, self.scale, 0.0, 0.0, 0.0, 1.0);
let translate_origin = Matrix3::new(1.0, 0.0, self.origin.x, 0.0, 1.0, self.origin.y, 0.0, 0.0, 1.0);
let translate_offset = Matrix3::new(1.0, 0.0, self.offset.x, 0.0, 1.0, self.offset.y, 0.0, 0.0, 1.0);
translate_offset * translate_origin * scaling_matrix * rotation_matrix * mirroring_matrix * translate_neg_origin
}
pub fn combine(&self, other: &GerberTransform) -> Self {
let matrix1 = self.to_matrix();
let matrix2 = other.to_matrix();
let combined_matrix = matrix2 * matrix1;
Self::from_matrix(&combined_matrix)
}
pub fn from_matrix(matrix: &Matrix3<f64>) -> Self {
let offset = Vector2::new(matrix[(0, 2)], matrix[(1, 2)]);
let a = matrix[(0, 0)];
let b = matrix[(0, 1)];
let c = matrix[(1, 0)];
let d = matrix[(1, 1)];
let det = a * d - b * c;
let mirroring_x = det < 0.0;
let mirroring_y = false;
let scale_x = (a * a + c * c).sqrt();
let scale_y = (b * b + d * d).sqrt();
let scale = (scale_x + scale_y) / 2.0;
let rotation_radians = if !mirroring_x { c.atan2(a) } else { (-c).atan2(-a) } as f32;
Self {
rotation: rotation_radians,
mirroring: Mirroring {
x: mirroring_x,
y: mirroring_y,
},
origin: Vector2::new(0.0, 0.0),
offset,
scale,
}
}
pub fn apply_to_position_matrix(&self, position: Point2<f64>) -> Point2<f64> {
let point_vec = Vector3::new(position.x, position.y, 1.0);
let matrix = self.to_matrix();
let transformed = matrix * point_vec;
Point2::new(transformed[0], transformed[1])
}
#[cfg(feature = "egui")]
pub fn apply_to_pos2_matrix(&self, pos: Pos2) -> Vec2 {
let mut screen_transform = self.clone();
screen_transform.rotation = -self.rotation;
let matrix = screen_transform.to_matrix();
let point_vec = nalgebra::Vector3::new(pos.x as f64, pos.y as f64, 1.0);
let transformed = matrix * point_vec;
Vec2::new(transformed[0] as f32, transformed[1] as f32)
}
}
pub trait Matrix3Point2Ext {
fn transform_point2(&self, point: Point2<f64>) -> Point2<f64>;
}
impl Matrix3Point2Ext for Matrix3<f64> {
#[inline]
fn transform_point2(&self, point: Point2<f64>) -> Point2<f64> {
let point_vec = Vector3::new(point.x, point.y, 1.0);
let transformed = self * point_vec;
Point2::new(transformed[0], transformed[1])
}
}
#[cfg(feature = "egui")]
pub trait Matrix3Pos2Ext {
fn transform_pos2(&self, pos: Pos2) -> Vec2;
}
#[cfg(feature = "egui")]
impl Matrix3Pos2Ext for Matrix3<f64> {
#[inline]
fn transform_pos2(&self, pos: Pos2) -> Vec2 {
let point_vec = Vector3::new(pos.x as f64, -(pos.y as f64), 1.0);
let transformed = self * point_vec;
Vec2::new(transformed[0] as f32, -transformed[1] as f32)
}
}
pub trait Matrix3ScalingExt {
fn get_scaling_factors(&self) -> Vector2<f64>;
}
impl Matrix3ScalingExt for Matrix3<f64> {
fn get_scaling_factors(&self) -> Vector2<f64> {
let scale_x = (self[(0, 0)].powi(2) + self[(1, 0)].powi(2)).sqrt();
let scale_y = (self[(0, 1)].powi(2) + self[(1, 1)].powi(2)).sqrt();
Vector2::new(scale_x, scale_y)
}
}
#[cfg(test)]
mod transform_tests {
use std::f32::consts::PI;
use nalgebra::{Point2, Vector2};
use crate::geometry::mirroring::Mirroring;
use crate::geometry::*;
#[test]
fn test_identity_transform_matrix() {
let identity = GerberTransform {
rotation: 0.0,
mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0),
offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let matrix = identity.to_matrix();
assert!((matrix[(0, 0)] - 1.0).abs() < 1e-6);
assert!((matrix[(0, 1)] - 0.0).abs() < 1e-6);
assert!((matrix[(0, 2)] - 0.0).abs() < 1e-6);
assert!((matrix[(1, 0)] - 0.0).abs() < 1e-6);
assert!((matrix[(1, 1)] - 1.0).abs() < 1e-6);
assert!((matrix[(1, 2)] - 0.0).abs() < 1e-6);
assert!((matrix[(2, 0)] - 0.0).abs() < 1e-6);
assert!((matrix[(2, 1)] - 0.0).abs() < 1e-6);
assert!((matrix[(2, 2)] - 1.0).abs() < 1e-6);
let reconstructed = GerberTransform::from_matrix(&matrix);
assert!((reconstructed.rotation - 0.0).abs() < 1e-6);
assert_eq!(reconstructed.mirroring.x, false);
assert_eq!(reconstructed.mirroring.y, false);
assert!((reconstructed.origin.x - 0.0).abs() < 1e-6);
assert!((reconstructed.origin.y - 0.0).abs() < 1e-6);
assert!((reconstructed.offset.x - 0.0).abs() < 1e-6);
assert!((reconstructed.offset.y - 0.0).abs() < 1e-6);
assert!((reconstructed.scale - 1.0).abs() < 1e-6);
}
#[test]
fn test_rotation_transform_matrix() {
let rotation_90 = GerberTransform {
rotation: PI / 2.0, mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0),
offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let matrix = rotation_90.to_matrix();
assert!((matrix[(0, 0)] - 0.0).abs() < 1e-6);
assert!((matrix[(0, 1)] - -1.0).abs() < 1e-6);
assert!((matrix[(0, 2)] - 0.0).abs() < 1e-6);
assert!((matrix[(1, 0)] - 1.0).abs() < 1e-6);
assert!((matrix[(1, 1)] - 0.0).abs() < 1e-6);
assert!((matrix[(1, 2)] - 0.0).abs() < 1e-6);
let reconstructed = GerberTransform::from_matrix(&matrix);
assert!((reconstructed.rotation - PI / 2.0).abs() < 1e-6);
assert_eq!(reconstructed.mirroring.x, false);
assert!((reconstructed.scale - 1.0).abs() < 1e-6);
}
#[test]
fn test_offset_transform_matrix() {
let offset_transform = GerberTransform {
rotation: 0.0,
mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0),
offset: Vector2::new(10.0, 20.0),
scale: 1.0,
};
let matrix = offset_transform.to_matrix();
assert!((matrix[(0, 0)] - 1.0).abs() < 1e-6);
assert!((matrix[(0, 1)] - 0.0).abs() < 1e-6);
assert!((matrix[(0, 2)] - 10.0).abs() < 1e-6);
assert!((matrix[(1, 0)] - 0.0).abs() < 1e-6);
assert!((matrix[(1, 1)] - 1.0).abs() < 1e-6);
assert!((matrix[(1, 2)] - 20.0).abs() < 1e-6);
let reconstructed = GerberTransform::from_matrix(&matrix);
assert!((reconstructed.rotation - 0.0).abs() < 1e-6);
assert_eq!(reconstructed.mirroring.x, false);
assert!((reconstructed.offset.x - 10.0).abs() < 1e-6);
assert!((reconstructed.offset.y - 20.0).abs() < 1e-6);
assert!((reconstructed.scale - 1.0).abs() < 1e-6);
}
#[test]
fn test_combined_transforms_example() {
let box1_position = Point2::new(-5.0, 0.0);
let box2_position = Point2::new(5.0, 0.0);
let transform1 = GerberTransform {
rotation: PI / 4.0, mirroring: Mirroring::default(),
origin: Vector2::new(-5.0, 0.0), offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let transform2 = GerberTransform {
rotation: PI / 4.0, mirroring: Mirroring::default(),
origin: Vector2::new(5.0, 0.0), offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let transform_both = GerberTransform {
rotation: PI / 2.0, mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0), offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let box1_transform1 = transform1.apply_to_position_matrix(box1_position);
println!("box1_transform1: {:?}", [box1_transform1.x, box1_transform1.y]);
let box2_transform2 = transform2.apply_to_position_matrix(box2_position);
println!("box2_transform2: {:?}", [box2_transform2.x, box2_transform2.y]);
let box1_final_reference = transform_both.apply_to_position_matrix(box1_transform1);
println!("box1_final_reference: {:?}", [
box1_final_reference.x,
box1_final_reference.y
]);
let box2_final_reference = transform_both.apply_to_position_matrix(box2_transform2);
println!("box2_final_reference: {:?}", [
box2_final_reference.x,
box2_final_reference.y
]);
let combined1_matrix = transform1.combine(&transform_both);
let combined2_matrix = transform2.combine(&transform_both);
let box1_final_matrix = combined1_matrix.apply_to_position_matrix(box1_position);
println!("box1_final_matrix: {:?}", [box1_final_matrix.x, box1_final_matrix.y]);
let box2_final_matrix = combined2_matrix.apply_to_position_matrix(box2_position);
println!("box2_final_matrix: {:?}", [box2_final_matrix.x, box2_final_matrix.y]);
println!(
"combined1_matrix: rotation={}, offset={:?}, origin={:?}, scale={}",
combined1_matrix.rotation,
[combined1_matrix.offset.x, combined1_matrix.offset.y],
[combined1_matrix.origin.x, combined1_matrix.origin.y],
combined1_matrix.scale
);
println!(
"combined2_matrix: rotation={}, offset={:?}, origin={:?}, scale={}",
combined2_matrix.rotation,
[combined2_matrix.offset.x, combined2_matrix.offset.y],
[combined2_matrix.origin.x, combined2_matrix.origin.y],
combined2_matrix.scale
);
assert!((box1_final_matrix.x - box1_final_reference.x).abs() < 1e-6);
assert!((box1_final_matrix.y - box1_final_reference.y).abs() < 1e-6);
assert!((box2_final_matrix.x - box2_final_reference.x).abs() < 1e-6);
assert!((box2_final_matrix.y - box2_final_reference.y).abs() < 1e-6);
}
#[test]
fn test_two_boxes_with_rotation() {
let box1_position = Point2::new(-5.0, -5.0);
let box2_position = Point2::new(-5.0, 5.0);
let transform1 = GerberTransform {
rotation: PI / 4.0, mirroring: Mirroring::default(),
origin: Vector2::new(-5.0, -5.0), offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let transform2 = GerberTransform {
rotation: PI / 4.0, mirroring: Mirroring::default(),
origin: Vector2::new(-5.0, 5.0), offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let transform_both = GerberTransform {
rotation: PI / 2.0, mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0), offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let box1_after_local = transform1.apply_to_position_matrix(box1_position);
println!(
"Box1 after local rotation: ({:.2}, {:.2})",
box1_after_local.x, box1_after_local.y
);
let box2_after_local = transform2.apply_to_position_matrix(box2_position);
println!(
"Box2 after local rotation: ({:.2}, {:.2})",
box2_after_local.x, box2_after_local.y
);
let box1_final_reference = transform_both.apply_to_position_matrix(box1_after_local);
println!(
"Box1 final position (sequential): ({:.2}, {:.2})",
box1_final_reference.x, box1_final_reference.y
);
let box2_final_reference = transform_both.apply_to_position_matrix(box2_after_local);
println!(
"Box2 final position (sequential): ({:.2}, {:.2})",
box2_final_reference.x, box2_final_reference.y
);
let combined1 = transform1.combine(&transform_both);
let combined2 = transform2.combine(&transform_both);
let box1_final_combined = combined1.apply_to_position_matrix(box1_position);
println!(
"Box1 final position (combined): ({:.2}, {:.2})",
box1_final_combined.x, box1_final_combined.y
);
let box2_final_combined = combined2.apply_to_position_matrix(box2_position);
println!(
"Box2 final position (combined): ({:.2}, {:.2})",
box2_final_combined.x, box2_final_combined.y
);
println!(
"Combined transform for Box1: rotation={:.2} degrees, offset=({:.2}, {:.2}), origin=({:.2}, {:.2}), scale={:.2}",
combined1.rotation * 180.0 / PI as f32,
combined1.offset.x,
combined1.offset.y,
combined1.origin.x,
combined1.origin.y,
combined1.scale
);
println!(
"Combined transform for Box2: rotation={:.2} degrees, offset=({:.2}, {:.2}), origin=({:.2}, {:.2}), scale={:.2}",
combined2.rotation * 180.0 / PI as f32,
combined2.offset.x,
combined2.offset.y,
combined2.origin.x,
combined2.origin.y,
combined2.scale
);
assert!((box1_final_reference.x - 5.0).abs() < 1e-6);
assert!((box1_final_reference.y + 5.0).abs() < 1e-6);
assert!((box2_final_reference.x + 5.0).abs() < 1e-6); assert!((box2_final_reference.y + 5.0).abs() < 1e-6);
assert!((box1_final_combined.x - box1_final_reference.x).abs() < 1e-6);
assert!((box1_final_combined.y - box1_final_reference.y).abs() < 1e-6);
assert!((box2_final_combined.x - box2_final_reference.x).abs() < 1e-6);
assert!((box2_final_combined.y - box2_final_reference.y).abs() < 1e-6);
let test_points1 = [
Point2::new(-6.0, -5.0), Point2::new(-5.0, -6.0), Point2::new(-4.0, -5.0), Point2::new(-5.0, -4.0), ];
let test_points2 = [
Point2::new(-6.0, 5.0), Point2::new(-5.0, 6.0), Point2::new(-4.0, 5.0), Point2::new(-5.0, 4.0), ];
println!("\nBox1 test points:");
for (i, point) in test_points1.iter().enumerate() {
let after_local = transform1.apply_to_position_matrix(*point);
let final_pos = transform_both.apply_to_position_matrix(after_local);
let combined_pos = combined1.apply_to_position_matrix(*point);
println!(
"Point {}: Original=({:.2}, {:.2}), Final=({:.2}, {:.2}), Combined=({:.2}, {:.2})",
i + 1,
point.x,
point.y,
final_pos.x,
final_pos.y,
combined_pos.x,
combined_pos.y
);
assert!((final_pos.x - combined_pos.x).abs() < 1e-6);
assert!((final_pos.y - combined_pos.y).abs() < 1e-6);
}
println!("\nBox2 test points:");
for (i, point) in test_points2.iter().enumerate() {
let after_local = transform2.apply_to_position_matrix(*point);
let final_pos = transform_both.apply_to_position_matrix(after_local);
let combined_pos = combined2.apply_to_position_matrix(*point);
println!(
"Point {}: Original=({:.2}, {:.2}), Final=({:.2}, {:.2}), Combined=({:.2}, {:.2})",
i + 1,
point.x,
point.y,
final_pos.x,
final_pos.y,
combined_pos.x,
combined_pos.y
);
assert!((final_pos.x - combined_pos.x).abs() < 1e-6);
assert!((final_pos.y - combined_pos.y).abs() < 1e-6);
}
assert!((box1_final_reference.x - 5.0).abs() < 1e-6);
assert!((box1_final_reference.y + 5.0).abs() < 1e-6);
assert!((box2_final_reference.x + 5.0).abs() < 1e-6); assert!((box2_final_reference.y + 5.0).abs() < 1e-6); }
#[test]
fn test_combined_transforms_complex() {
let transform1 = GerberTransform {
rotation: PI / 6.0, mirroring: Mirroring::default(),
origin: Vector2::new(3.0, 4.0),
offset: Vector2::new(1.0, 2.0),
scale: 1.5,
};
let transform2 = GerberTransform {
rotation: PI / 3.0, mirroring: Mirroring::default(),
origin: Vector2::new(-2.0, 5.0),
offset: Vector2::new(3.0, -1.0),
scale: 0.8,
};
let test_points = vec![
Point2::new(0.0, 0.0),
Point2::new(10.0, 0.0),
Point2::new(0.0, 10.0),
Point2::new(-5.0, -5.0),
];
for point in test_points {
let intermediate = transform1.apply_to_position_matrix(point);
let final_reference = transform2.apply_to_position_matrix(intermediate);
let combined = transform1.combine(&transform2);
let final_combined = combined.apply_to_position_matrix(point);
assert!((final_combined.x - final_reference.x).abs() < 1e-6);
assert!((final_combined.y - final_reference.y).abs() < 1e-6);
}
}
#[test]
fn test_mirroring_transforms() {
let mirror_x = GerberTransform {
rotation: 0.0,
mirroring: Mirroring {
x: true,
y: false,
},
origin: Vector2::new(0.0, 0.0),
offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let rotate_45 = GerberTransform {
rotation: PI / 4.0, mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0),
offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let point = Point2::new(3.0, 4.0);
let intermediate = mirror_x.apply_to_position_matrix(point);
let final_reference = rotate_45.apply_to_position_matrix(intermediate);
let combined = mirror_x.combine(&rotate_45);
let final_combined = combined.apply_to_position_matrix(point);
assert!((final_combined.x - final_reference.x).abs() < 1e-6);
assert!((final_combined.y - final_reference.y).abs() < 1e-6);
assert_eq!(combined.mirroring.x, true);
}
#[test]
fn test_scaling_transforms() {
let scale_2x = GerberTransform {
rotation: 0.0,
mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0),
offset: Vector2::new(0.0, 0.0),
scale: 2.0,
};
let offset_10_20 = GerberTransform {
rotation: 0.0,
mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0),
offset: Vector2::new(10.0, 20.0),
scale: 1.0,
};
let point = Point2::new(3.0, 4.0);
let intermediate = scale_2x.apply_to_position_matrix(point);
let final_reference = offset_10_20.apply_to_position_matrix(intermediate);
let combined = scale_2x.combine(&offset_10_20);
let final_combined = combined.apply_to_position_matrix(point);
assert!((final_combined.x - final_reference.x).abs() < 1e-6);
assert!((final_combined.y - final_reference.y).abs() < 1e-6);
assert!((combined.scale - 2.0).abs() < 1e-6);
}
#[test]
fn test_multiple_combined_transforms() {
let transform1 = GerberTransform {
rotation: PI / 4.0, mirroring: Mirroring::default(),
origin: Vector2::new(-5.0, 0.0),
offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let transform2 = GerberTransform {
rotation: PI / 2.0, mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0),
offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let transform3 = GerberTransform {
rotation: 0.0,
mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0),
offset: Vector2::new(10.0, 10.0),
scale: 2.0,
};
let point = Point2::new(-5.0, 0.0);
let step1 = transform1.apply_to_position_matrix(point);
let step2 = transform2.apply_to_position_matrix(step1);
let final_reference = transform3.apply_to_position_matrix(step2);
let combined_1_2 = transform1.combine(&transform2);
let combined_all = combined_1_2.combine(&transform3);
let final_combined = combined_all.apply_to_position_matrix(point);
assert!((final_combined.x - final_reference.x).abs() < 1e-6);
assert!((final_combined.y - final_reference.y).abs() < 1e-6);
}
#[test]
fn test_rotation_around_different_centers() {
let box1_position = Point2::new(-5.0, 0.0);
let box2_position = Point2::new(5.0, 0.0);
let box1_local_rot = GerberTransform {
rotation: PI / 4.0, mirroring: Mirroring::default(),
origin: Vector2::new(-5.0, 0.0), offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let box2_local_rot = GerberTransform {
rotation: PI / 4.0, mirroring: Mirroring::default(),
origin: Vector2::new(5.0, 0.0), offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let global_rot = GerberTransform {
rotation: PI / 2.0, mirroring: Mirroring::default(),
origin: Vector2::new(0.0, 0.0), offset: Vector2::new(0.0, 0.0),
scale: 1.0,
};
let box1_after_local = box1_local_rot.apply_to_position_matrix(box1_position);
let box1_after_global = global_rot.apply_to_position_matrix(box1_after_local);
let box2_after_local = box2_local_rot.apply_to_position_matrix(box2_position);
let box2_after_global = global_rot.apply_to_position_matrix(box2_after_local);
let box1_combined = box1_local_rot.combine(&global_rot);
let box2_combined = box2_local_rot.combine(&global_rot);
let box1_after_combined = box1_combined.apply_to_position_matrix(box1_position);
let box2_after_combined = box2_combined.apply_to_position_matrix(box2_position);
println!("Sequential for box1: {:?}", [box1_after_global.x, box1_after_global.y]);
println!("Combined for box1: {:?}", [
box1_after_combined.x,
box1_after_combined.y
]);
println!("Sequential for box2: {:?}", [box2_after_global.x, box2_after_global.y]);
println!("Combined for box2: {:?}", [
box2_after_combined.x,
box2_after_combined.y
]);
println!(
"box1_combined: rotation={}, offset={:?}, origin={:?}, scale={}",
box1_combined.rotation,
[box1_combined.offset.x, box1_combined.offset.y],
[box1_combined.origin.x, box1_combined.origin.y],
box1_combined.scale
);
println!(
"box2_combined: rotation={}, offset={:?}, origin={:?}, scale={}",
box2_combined.rotation,
[box2_combined.offset.x, box2_combined.offset.y],
[box2_combined.origin.x, box2_combined.origin.y],
box2_combined.scale
);
assert!((box1_after_combined.x - box1_after_global.x).abs() < 1e-6);
assert!((box1_after_combined.y - box1_after_global.y).abs() < 1e-6);
assert!((box2_after_combined.x - box2_after_global.x).abs() < 1e-6);
assert!((box2_after_combined.y - box2_after_global.y).abs() < 1e-6);
assert!((box1_after_global.x).abs() < 1e-6);
assert!((box1_after_global.y - -5.0).abs() < 1e-6);
assert!((box2_after_global.x).abs() < 1e-6);
assert!((box2_after_global.y - 5.0).abs() < 1e-6);
}
}
pub trait Matrix3TransformExt {
fn is_axis_aligned(&self) -> bool;
fn extract_rotation_angle(&self) -> f64;
fn is_90_or_270_rotation(&self) -> bool;
fn is_0_or_180_rotation(&self) -> bool;
fn get_axis_aligned_angle(&self) -> Option<i32>;
}
impl Matrix3TransformExt for Matrix3<f64> {
fn is_axis_aligned(&self) -> bool {
let a = self[(0, 0)];
let b = self[(0, 1)];
let c = self[(1, 0)];
let d = self[(1, 1)];
let case1 =
(b.abs() < f64::EPSILON && c.abs() < f64::EPSILON) && (a.abs() > f64::EPSILON || d.abs() > f64::EPSILON);
let case2 =
(a.abs() < f64::EPSILON && d.abs() < f64::EPSILON) && (b.abs() > f64::EPSILON || c.abs() > f64::EPSILON);
case1 || case2
}
fn extract_rotation_angle(&self) -> f64 {
let a = self[(0, 0)];
let b = self[(0, 1)];
let c = self[(1, 0)];
let d = self[(1, 1)];
let det = (a * d - b * c).sqrt();
let a_norm = if det.abs() > f64::EPSILON { a / det } else { a };
let b_norm = if det.abs() > f64::EPSILON { b / det } else { b };
let angle = b_norm.atan2(a_norm);
(angle + 2.0 * PI) % (2.0 * PI)
}
fn is_90_or_270_rotation(&self) -> bool {
let a = self[(0, 0)];
let b = self[(0, 1)];
let c = self[(1, 0)];
let d = self[(1, 1)];
let diagonals_are_zero = a.abs() < f64::EPSILON && d.abs() < f64::EPSILON;
let off_diagonals_opposite_sign = (b * c) < 0.0 && b.abs() > f64::EPSILON && c.abs() > f64::EPSILON;
let relative_diff = if c.abs() > f64::EPSILON {
(b.abs() / c.abs() - 1.0).abs()
} else {
f64::INFINITY
};
let magnitudes_equal = relative_diff < 1e-6;
diagonals_are_zero && off_diagonals_opposite_sign && magnitudes_equal
}
fn is_0_or_180_rotation(&self) -> bool {
let a = self[(0, 0)];
let b = self[(0, 1)];
let c = self[(1, 0)];
let d = self[(1, 1)];
let off_diagonals_are_zero = b.abs() < f64::EPSILON && c.abs() < f64::EPSILON;
let diagonals_are_nonzero = a.abs() > f64::EPSILON && d.abs() > f64::EPSILON;
let relative_diff = if d.abs() > f64::EPSILON {
(a.abs() / d.abs() - 1.0).abs()
} else {
f64::INFINITY
};
let magnitudes_equal = relative_diff < 1e-6;
off_diagonals_are_zero && diagonals_are_nonzero && magnitudes_equal
}
fn get_axis_aligned_angle(&self) -> Option<i32> {
if self.is_0_or_180_rotation() {
let a = self[(0, 0)];
let d = self[(1, 1)];
if a * d > 0.0 { Some(0) } else { Some(180) }
} else if self.is_90_or_270_rotation() {
let b = self[(0, 1)];
let c = self[(1, 0)];
if b < 0.0 && c > 0.0 { Some(90) } else { Some(270) }
} else {
None
}
}
}
#[derive(Clone, Debug)]
pub struct GerberImageTransform {
pub mirroring: ImageMirroring,
pub offset: Vector2<f64>,
pub scale: Vector2<f64>,
pub rotation: f64,
pub axis_select: AxisSelect,
}
impl Default for GerberImageTransform {
fn default() -> Self {
Self {
mirroring: ImageMirroring::default(),
offset: Vector2::new(0.0, 0.0),
scale: Vector2::new(1.0, 1.0),
rotation: 0.0,
axis_select: AxisSelect::default(),
}
}
}
impl GerberImageTransform {
#[rustfmt::skip]
pub fn to_matrix(&self) -> Matrix3<f64> {
let [mirror_x, mirror_y] = match self.mirroring {
ImageMirroring::None => [1.0, 1.0],
ImageMirroring::A => [-1.0, 1.0],
ImageMirroring::B => [1.0, -1.0],
ImageMirroring::AB => [-1.0, -1.0],
};
let mirroring_matrix = Matrix3::new(
mirror_x, 0.0, 0.0,
0.0, mirror_y, 0.0,
0.0, 0.0, 1.0
);
let scaling_matrix = Matrix3::new(
self.scale.x, 0.0, 0.0,
0.0, self.scale.y, 0.0,
0.0, 0.0, 1.0
);
let rad = self.rotation;
let cos_rad = rad.cos();
let sin_rad = rad.sin();
let rotation_matrix = Matrix3::new(
cos_rad, -sin_rad, 0.0,
sin_rad, cos_rad, 0.0,
0.0, 0.0, 1.0
);
let translate_offset = Matrix3::new(
1.0, 0.0, self.offset.x,
0.0, 1.0, self.offset.y,
0.0, 0.0, 1.0
);
let axis_assignment_matrix = {
match self.axis_select {
AxisSelect::AXBY => {
let identity_matrix = Matrix3::identity();
identity_matrix
}
AxisSelect::AYBX => {
let swap_matrix = Matrix3::new(
0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 );
swap_matrix
}
}
};
mirroring_matrix * scaling_matrix * translate_offset * rotation_matrix * axis_assignment_matrix
}
}
#[derive(Clone, Debug, Copy)]
pub enum AxisAssignment {
AXBY,
AYBX,
}
impl Default for AxisAssignment {
fn default() -> Self {
AxisAssignment::AXBY
}
}