use euclid::{Angle, Transform3D, UnknownUnit};
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Transform {
pub local_transform: Transform3D<f32, UnknownUnit, UnknownUnit>,
pub world_transform: Transform3D<f32, UnknownUnit, UnknownUnit>,
pub origin: (f32, f32),
pub position_relative_to_parent: (f32, f32),
pub parent_container_camera_perspective: Option<Transform3D<f32, UnknownUnit, UnknownUnit>>,
}
impl Default for Transform {
fn default() -> Self {
Self::new()
}
}
impl Transform {
pub fn new() -> Self {
Self {
local_transform: Transform3D::identity(),
world_transform: Transform3D::identity(),
origin: (0.0, 0.0),
position_relative_to_parent: (0.0, 0.0),
parent_container_camera_perspective: None,
}
}
pub fn compose(&mut self, parent: &Transform) {
let origin_translation: Transform3D<f32, UnknownUnit, UnknownUnit> =
Transform3D::translation(-self.origin.0, -self.origin.1, 0.0);
let origin_translation_inv: Transform3D<f32, UnknownUnit, UnknownUnit> =
Transform3D::translation(self.origin.0, self.origin.1, 0.0);
let local_matrix = origin_translation
.then(&self.local_transform)
.then(&origin_translation_inv);
let position_matrix: Transform3D<f32, UnknownUnit, UnknownUnit> = Transform3D::translation(
self.position_relative_to_parent.0,
self.position_relative_to_parent.1,
0.0,
);
let perspective_matrix = self
.parent_container_camera_perspective
.unwrap_or(Transform3D::identity());
self.world_transform = local_matrix
.then(&position_matrix)
.then(&perspective_matrix)
.then(&parent.world_transform);
}
pub fn compose_2(mut self, parent: &Transform) -> Self {
self.compose(parent);
self
}
pub fn set_origin(&mut self, ox: f32, oy: f32) {
self.origin = (ox, oy);
}
pub fn with_origin(mut self, ox: f32, oy: f32) -> Self {
self.set_origin(ox, oy);
self
}
pub fn set_position_relative_to_parent(&mut self, x: f32, y: f32) {
self.position_relative_to_parent.0 = x;
self.position_relative_to_parent.1 = y;
}
pub fn with_position_relative_to_parent(mut self, x: f32, y: f32) -> Self {
self.set_position_relative_to_parent(x, y);
self
}
pub fn set_parent_container_perspective(
&mut self,
distance: f32,
origin_x: f32,
origin_y: f32,
) {
let mut perspective: Transform3D<f32, UnknownUnit, UnknownUnit> = Transform3D::identity();
perspective.m34 = -1.0 / distance;
let center_transform: Transform3D<f32, UnknownUnit, UnknownUnit> =
Transform3D::translation(-origin_x, -origin_y, 0.0);
let uncenter_transform = Transform3D::translation(origin_x, origin_y, 0.0);
let z_correction = Transform3D::translation(0.0, 0.0, 78.0);
self.parent_container_camera_perspective = Some(
center_transform
.then(&z_correction)
.then(&perspective)
.then(&uncenter_transform),
);
}
pub fn with_parent_container_perspective(
mut self,
distance: f32,
origin_x: f32,
origin_y: f32,
) -> Self {
self.set_parent_container_perspective(distance, origin_x, origin_y);
self
}
pub fn translate(&mut self, tx: f32, ty: f32) {
self.translate_3d(tx, ty, 0.0);
}
pub fn then_translate(mut self, tx: f32, ty: f32) -> Self {
self.translate(tx, ty);
self
}
pub fn translate_3d(&mut self, tx: f32, ty: f32, tz: f32) {
self.local_transform = self
.local_transform
.then(&euclid::Transform3D::translation(tx, ty, tz));
}
pub fn then_translate_3d(mut self, tx: f32, ty: f32, tz: f32) -> Self {
self.translate_3d(tx, ty, tz);
self
}
pub fn translate_x(&mut self, tx: f32) {
self.local_transform = self
.local_transform
.then(&euclid::Transform3D::translation(tx, 0.0, 0.0));
}
pub fn then_translate_x(mut self, tx: f32) -> Self {
self.translate_x(tx);
self
}
pub fn translate_y(&mut self, ty: f32) {
self.local_transform = self
.local_transform
.then(&euclid::Transform3D::translation(0.0, ty, 0.0));
}
pub fn then_translate_y(mut self, ty: f32) -> Self {
self.translate_y(ty);
self
}
pub fn translate_z(&mut self, tz: f32) {
self.local_transform = self
.local_transform
.then(&euclid::Transform3D::translation(0.0, 0.0, tz));
}
pub fn then_translate_z(mut self, tz: f32) -> Self {
self.translate_z(tz);
self
}
pub fn translate_2d(&mut self, tx: f32, ty: f32) {
self.local_transform = self
.local_transform
.then(&euclid::Transform3D::translation(tx, ty, 0.0));
}
pub fn then_translate_2d(mut self, tx: f32, ty: f32) -> Self {
self.translate_2d(tx, ty);
self
}
pub fn rotate_x_deg(degrees: f32) -> Self {
Transform::new().then_rotate_x(Angle::degrees(degrees))
}
pub fn rotate_x_rad(radians: f32) -> Self {
Transform::new().then_rotate_x(Angle::radians(radians))
}
fn then_rotate_x(mut self, angle: Angle<f32>) -> Self {
self.local_transform = self
.local_transform
.then(&euclid::Transform3D::rotation(1.0, 0.0, 0.0, angle));
self
}
pub fn then_rotate_x_deg(self, degrees: f32) -> Self {
self.then_rotate_x(Angle::degrees(degrees))
}
pub fn then_rotate_x_rad(self, radians: f32) -> Self {
self.then_rotate_x(Angle::radians(radians))
}
pub fn rotate_y_deg(degrees: f32) -> Self {
Transform::new().then_rotate_y(Angle::degrees(degrees))
}
pub fn rotate_y_rad(radians: f32) -> Self {
Transform::new().then_rotate_y(Angle::radians(radians))
}
fn then_rotate_y(mut self, angle: Angle<f32>) -> Self {
self.local_transform = self
.local_transform
.then(&euclid::Transform3D::rotation(0.0, 1.0, 0.0, angle));
self
}
pub fn then_rotate_y_deg(self, degrees: f32) -> Self {
self.then_rotate_y(Angle::degrees(degrees))
}
pub fn then_rotate_y_rad(self, radians: f32) -> Self {
self.then_rotate_y(Angle::radians(radians))
}
pub fn rotate_z_deg(degrees: f32) -> Self {
Transform::new().then_rotate_z(Angle::degrees(degrees))
}
pub fn rotate_z_rad(radians: f32) -> Self {
Transform::new().then_rotate_z(Angle::radians(radians))
}
pub fn then_rotate_z_deg(self, degrees: f32) -> Self {
self.then_rotate_z(Angle::degrees(degrees))
}
pub fn then_rotate_z_rad(self, radians: f32) -> Self {
self.then_rotate_z(Angle::radians(radians))
}
fn then_rotate_z(mut self, angle: Angle<f32>) -> Self {
self.local_transform = self
.local_transform
.then(&euclid::Transform3D::rotation(0.0, 0.0, 1.0, angle));
self
}
pub fn rotate(axis_x: f32, axis_y: f32, axis_z: f32, angle: Angle<f32>) -> Self {
Self::new().then_rotate(axis_x, axis_y, axis_z, angle)
}
pub fn then_rotate(mut self, axis_x: f32, axis_y: f32, axis_z: f32, angle: Angle<f32>) -> Self {
self.local_transform = self
.local_transform
.then_rotate(axis_x, axis_y, axis_z, angle);
self
}
pub fn scale(sx: f32, sy: f32) -> Self {
Transform::new().then_scale(sx, sy)
}
pub fn then_scale(mut self, sx: f32, sy: f32) -> Self {
self.local_transform = self
.local_transform
.then(&euclid::Transform3D::scale(sx, sy, 1.0));
self
}
pub fn scale_3d(sx: f32, sy: f32, sz: f32) -> Self {
Transform::new().then_scale_3d(sx, sy, sz)
}
pub fn then_scale_3d(mut self, sx: f32, sy: f32, sz: f32) -> Self {
self.local_transform = self
.local_transform
.then(&euclid::Transform3D::scale(sx, sy, sz));
self
}
pub fn transform_local_point2d_to_world(&self, x: f32, y: f32) -> (f32, f32) {
let hom = self
.world_transform
.transform_point3d_homogeneous(euclid::Point3D::new(x, y, 0.0));
if hom.w.abs() < 1e-6 {
return (0.0, 0.0);
}
(hom.x / hom.w, hom.y / hom.w)
}
pub fn transform_world_point_to_local(&self, x: f32, y: f32, z: f32) -> Option<(f32, f32)> {
let inv = self.world_transform.inverse()?;
let hom = inv.transform_point3d_homogeneous(euclid::Point3D::new(x, y, z));
if hom.w.abs() < 1e-6 {
return None;
}
Some((hom.x / hom.w, hom.y / hom.w))
}
pub fn project_screen_point_to_local_2d(&self, screen_pos: (f32, f32)) -> Option<(f32, f32)> {
let inv = self.world_transform.inverse()?;
let ray_origin_hom = inv.transform_point3d_homogeneous(euclid::Point3D::new(
screen_pos.0,
screen_pos.1,
0.0,
));
if ray_origin_hom.w.abs() < 1e-6 {
return None;
}
let ray_origin: euclid::Point3D<f32, euclid::UnknownUnit> = euclid::Point3D::new(
ray_origin_hom.x / ray_origin_hom.w,
ray_origin_hom.y / ray_origin_hom.w,
ray_origin_hom.z / ray_origin_hom.w,
);
let ray_end_hom = inv.transform_point3d_homogeneous(euclid::Point3D::new(
screen_pos.0,
screen_pos.1,
1.0,
));
if ray_end_hom.w.abs() < 1e-6 {
return None;
}
let ray_end: euclid::Point3D<f32, euclid::UnknownUnit> = euclid::Point3D::new(
ray_end_hom.x / ray_end_hom.w,
ray_end_hom.y / ray_end_hom.w,
ray_end_hom.z / ray_end_hom.w,
);
let ray_dir: euclid::Vector3D<f32, euclid::UnknownUnit> = ray_end - ray_origin;
if ray_dir.z.abs() < 1e-6 {
return None;
}
let t = -ray_origin.z / ray_dir.z;
let intersection_x = ray_origin.x + t * ray_dir.x;
let intersection_y = ray_origin.y + t * ray_dir.y;
Some((intersection_x, intersection_y))
}
pub fn rows_local(&self) -> [[f32; 4]; 4] {
self.local_transform.to_arrays()
}
pub fn rows_world(&self) -> [[f32; 4]; 4] {
self.world_transform.to_arrays()
}
pub fn as_bytes_world(&self) -> &[u8] {
bytemuck::bytes_of(&self.world_transform)
}
pub fn as_bytes_local(&self) -> &[u8] {
bytemuck::bytes_of(&self.local_transform)
}
}
#[cfg(test)]
pub mod tests {
use super::Transform;
#[test]
pub fn test_a() {
let viewport_center = (400.0, 300.0);
let rect_size = (100.0, 100.0);
let inner_rect_size = (35.0, 80.0);
let parent = Transform::new()
.with_position_relative_to_parent(viewport_center.0 - 50.0, viewport_center.1 - 50.0)
.with_parent_container_perspective(500.0, viewport_center.0, viewport_center.1)
.with_origin(50.0, 50.0)
.then_rotate_x_deg(45.0)
.compose_2(&Transform::new());
let child1 = Transform::new()
.with_position_relative_to_parent(10.0, 10.0)
.compose_2(&parent);
let child2 = Transform::new()
.with_position_relative_to_parent(55.0, 10.0) .compose_2(&parent);
let rect_corners_after_transform_expected = [
(346.0, 264.0),
(455.0, 264.0),
(465.0, 348.0),
(336.0, 348.0),
];
let inner_rect_after_transform_expected = [
(355.0, 270.0),
(395.0, 270.0),
(394.0, 338.0),
(348.0, 338.0),
];
let inner_rect2_after_transform_expected = [
(406.0, 270.0),
(446.0, 270.0),
(453.0, 338.0),
(405.0, 338.0),
];
let actual_rect_corners = [
parent.transform_local_point2d_to_world(0.0, 0.0),
parent.transform_local_point2d_to_world(rect_size.0, 0.0),
parent.transform_local_point2d_to_world(rect_size.0, rect_size.1),
parent.transform_local_point2d_to_world(0.0, rect_size.1),
];
println!("Actual rect corners: {:?}", actual_rect_corners);
for (actual, expected) in actual_rect_corners
.iter()
.zip(rect_corners_after_transform_expected.iter())
{
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 5.0 && dy < 5.0,
"Parent rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
let inner_rect1_corners = [
child1.transform_local_point2d_to_world(0.0, 0.0),
child1.transform_local_point2d_to_world(inner_rect_size.0, 0.0),
child1.transform_local_point2d_to_world(inner_rect_size.0, inner_rect_size.1),
child1.transform_local_point2d_to_world(0.0, inner_rect_size.1),
];
println!("Child 1 rect corners: {:?}", inner_rect1_corners);
for (actual, expected) in inner_rect1_corners
.iter()
.zip(inner_rect_after_transform_expected.iter())
{
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 5.0 && dy < 5.0,
"Child 1 rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
let inner_rect2_corners = [
child2.transform_local_point2d_to_world(0.0, 0.0),
child2.transform_local_point2d_to_world(inner_rect_size.0, 0.0),
child2.transform_local_point2d_to_world(inner_rect_size.0, inner_rect_size.1),
child2.transform_local_point2d_to_world(0.0, inner_rect_size.1),
];
println!("Child 2 rect corners: {:?}", inner_rect2_corners);
for (actual, expected) in inner_rect2_corners
.iter()
.zip(inner_rect2_after_transform_expected.iter())
{
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 5.0 && dy < 5.0,
"Child 2 rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
}
#[test]
pub fn test_b() {
let viewport_center = (400.0, 300.0);
let rect_size = (100.0, 100.0);
let inner_rect_size = (35.0, 80.0);
let parent = Transform::new()
.with_position_relative_to_parent(viewport_center.0 - 50.0, viewport_center.1 - 50.0)
.with_parent_container_perspective(500.0, viewport_center.0, viewport_center.1)
.then_rotate_y_deg(30.0)
.then_rotate_x_deg(45.0)
.with_origin(50.0, 50.0)
.compose_2(&Transform::new());
let child1 = Transform::new()
.with_position_relative_to_parent(10.0, 10.0)
.compose_2(&parent);
let child2 = Transform::new()
.with_position_relative_to_parent(55.0, 10.0) .compose_2(&parent);
let rect_corners_after_transform_expected = [
(352.0, 242.0),
(446.0, 285.0),
(455.0, 369.0),
(342.0, 327.0),
];
let inner_rect_after_transform_expected = [
(360.0, 253.0),
(395.0, 268.0),
(395.0, 338.0),
(353.0, 321.0),
];
let inner_rect2_after_transform_expected = [
(405.0, 272.0),
(439.0, 287.0),
(446.0, 356.0),
(405.0, 341.0),
];
let actual_rect_corners = [
parent.transform_local_point2d_to_world(0.0, 0.0),
parent.transform_local_point2d_to_world(rect_size.0, 0.0),
parent.transform_local_point2d_to_world(rect_size.0, rect_size.1),
parent.transform_local_point2d_to_world(0.0, rect_size.1),
];
println!("Actual rect corners: {:?}", actual_rect_corners);
for (actual, expected) in actual_rect_corners
.iter()
.zip(rect_corners_after_transform_expected.iter())
{
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 5.0 && dy < 5.0,
"Parent rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
let inner_rect1_corners = [
child1.transform_local_point2d_to_world(0.0, 0.0),
child1.transform_local_point2d_to_world(inner_rect_size.0, 0.0),
child1.transform_local_point2d_to_world(inner_rect_size.0, inner_rect_size.1),
child1.transform_local_point2d_to_world(0.0, inner_rect_size.1),
];
println!("Child 1 rect corners: {:?}", inner_rect1_corners);
for (actual, expected) in inner_rect1_corners
.iter()
.zip(inner_rect_after_transform_expected.iter())
{
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 5.0 && dy < 5.0,
"Child 1 rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
let inner_rect2_corners = [
child2.transform_local_point2d_to_world(0.0, 0.0),
child2.transform_local_point2d_to_world(inner_rect_size.0, 0.0),
child2.transform_local_point2d_to_world(inner_rect_size.0, inner_rect_size.1),
child2.transform_local_point2d_to_world(0.0, inner_rect_size.1),
];
println!("Child 2 rect corners: {:?}", inner_rect2_corners);
for (actual, expected) in inner_rect2_corners
.iter()
.zip(inner_rect2_after_transform_expected.iter())
{
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 5.0 && dy < 5.0,
"Child 2 rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
}
#[test]
pub fn test_c() {
let viewport_center = (400.0, 300.0);
let rect_size = (100.0, 100.0);
let inner_rect_size = (35.0, 80.0);
let parent = Transform::new()
.with_position_relative_to_parent(viewport_center.0 - 50.0, viewport_center.1 - 50.0)
.with_parent_container_perspective(500.0, viewport_center.0, viewport_center.1)
.then_rotate_y_deg(30.0)
.then_rotate_x_deg(45.0)
.with_origin(50.0, 50.0)
.compose_2(&Transform::new());
let child1 = Transform::new()
.with_position_relative_to_parent(10.0, 10.0)
.then_rotate_y_deg(20.0)
.with_origin(17.5, 40.0)
.compose_2(&parent);
let child2 = Transform::new()
.with_position_relative_to_parent(55.0, 10.0) .then_rotate_y_deg(20.0)
.with_origin(17.5, 40.0)
.compose_2(&parent);
let rect_corners_after_transform_expected = [
(352.0, 242.0),
(446.0, 285.0),
(455.0, 369.0),
(342.0, 327.0),
];
let inner_rect_after_transform_expected = [
(364.0, 248.0),
(391.0, 272.0),
(390.0, 342.0),
(358.0, 317.0),
];
let inner_rect2_after_transform_expected = [
(410.0, 269.0),
(436.0, 292.0),
(439.0, 360.0),
(410.0, 339.0),
];
let actual_rect_corners = [
parent.transform_local_point2d_to_world(0.0, 0.0),
parent.transform_local_point2d_to_world(rect_size.0, 0.0),
parent.transform_local_point2d_to_world(rect_size.0, rect_size.1),
parent.transform_local_point2d_to_world(0.0, rect_size.1),
];
println!("Actual rect corners: {:?}", actual_rect_corners);
for (actual, expected) in actual_rect_corners
.iter()
.zip(rect_corners_after_transform_expected.iter())
{
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 5.0 && dy < 5.0,
"Parent rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
let inner_rect1_corners = [
child1.transform_local_point2d_to_world(0.0, 0.0),
child1.transform_local_point2d_to_world(inner_rect_size.0, 0.0),
child1.transform_local_point2d_to_world(inner_rect_size.0, inner_rect_size.1),
child1.transform_local_point2d_to_world(0.0, inner_rect_size.1),
];
println!("Child 1 rect corners: {:?}", inner_rect1_corners);
for (actual, expected) in inner_rect1_corners
.iter()
.zip(inner_rect_after_transform_expected.iter())
{
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 5.0 && dy < 5.0,
"Child 1 rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
let inner_rect2_corners = [
child2.transform_local_point2d_to_world(0.0, 0.0),
child2.transform_local_point2d_to_world(inner_rect_size.0, 0.0),
child2.transform_local_point2d_to_world(inner_rect_size.0, inner_rect_size.1),
child2.transform_local_point2d_to_world(0.0, inner_rect_size.1),
];
println!("Child 2 rect corners: {:?}", inner_rect2_corners);
for (actual, expected) in inner_rect2_corners
.iter()
.zip(inner_rect2_after_transform_expected.iter())
{
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 5.0 && dy < 5.0,
"Child 2 rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
}
#[test]
pub fn test_inverse() {
let viewport_center = (400.0, 300.0);
let rect_size = (100.0, 100.0);
let parent = Transform::new()
.with_position_relative_to_parent(viewport_center.0 - 50.0, viewport_center.1 - 50.0)
.with_parent_container_perspective(500.0, viewport_center.0, viewport_center.1)
.then_rotate_y_deg(30.0)
.then_rotate_x_deg(45.0)
.with_origin(50.0, 50.0)
.compose_2(&Transform::new());
let rect_corners_after_transform_expected = [
(352.0, 242.0),
(446.0, 285.0),
(455.0, 369.0),
(342.0, 327.0),
];
let actual_rect_corners = [
parent.transform_local_point2d_to_world(0.0, 0.0),
parent.transform_local_point2d_to_world(rect_size.0, 0.0),
parent.transform_local_point2d_to_world(rect_size.0, rect_size.1),
parent.transform_local_point2d_to_world(0.0, rect_size.1),
];
println!("Actual rect corners: {:?}", actual_rect_corners);
for (actual, expected) in actual_rect_corners
.iter()
.zip(rect_corners_after_transform_expected.iter())
{
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 5.0 && dy < 5.0,
"Parent rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
let local_corners = [(0.0, 0.0), (100.0, 0.0), (100.0, 100.0), (0.0, 100.0)];
let world_z_coords: Vec<f32> = local_corners
.iter()
.map(|(x, y)| {
let hom = parent
.world_transform
.transform_point3d_homogeneous(euclid::Point3D::new(*x, *y, 0.0));
hom.z / hom.w
})
.collect();
println!("World Z coordinates for corners: {:?}", world_z_coords);
let inversed_parents_corners: Vec<(f32, f32)> = actual_rect_corners
.iter()
.zip(world_z_coords.iter())
.map(|((x, y), z)| parent.transform_world_point_to_local(*x, *y, *z).unwrap())
.collect();
println!("Inversed corners: {:?}", inversed_parents_corners);
for (actual, expected) in inversed_parents_corners.iter().zip(local_corners.iter()) {
let dx = (actual.0 - expected.0).abs();
let dy = (actual.1 - expected.1).abs();
assert!(
dx < 0.01 && dy < 0.01,
"Inversed parent rect corner deviated: got {:?}, expected {:?}, delta=({},{})",
actual,
expected,
dx,
dy
);
}
}
#[test]
pub fn test_project_screen_point_to_local_2d() {
let viewport_center = (400.0, 300.0);
let transform = Transform::new()
.with_position_relative_to_parent(viewport_center.0 - 50.0, viewport_center.1 - 50.0)
.with_parent_container_perspective(500.0, viewport_center.0, viewport_center.1)
.then_rotate_y_deg(30.0)
.then_rotate_x_deg(45.0)
.with_origin(50.0, 50.0)
.compose_2(&Transform::new());
let world_origin = transform.transform_local_point2d_to_world(0.0, 0.0);
let local_back = transform
.project_screen_point_to_local_2d((world_origin.0, world_origin.1))
.unwrap();
println!("Origin: world {:?} -> local {:?}", world_origin, local_back);
let dx = (local_back.0 - 0.0).abs();
let dy = (local_back.1 - 0.0).abs();
assert!(
dx < 0.01 && dy < 0.01,
"Origin roundtrip failed: {:?}",
local_back
);
let world_center = transform.transform_local_point2d_to_world(50.0, 50.0);
let local_center_back = transform
.project_screen_point_to_local_2d((world_center.0, world_center.1))
.unwrap();
println!(
"Center: world {:?} -> local {:?}",
world_center, local_center_back
);
let dx = (local_center_back.0 - 50.0).abs();
let dy = (local_center_back.1 - 50.0).abs();
assert!(
dx < 0.01 && dy < 0.01,
"Center roundtrip failed: {:?}",
local_center_back
);
let world_far = transform.transform_local_point2d_to_world(100.0, 100.0);
let local_far_back = transform
.project_screen_point_to_local_2d((world_far.0, world_far.1))
.unwrap();
println!(
"Far point: world {:?} -> local {:?}",
world_far, local_far_back
);
let dx = (local_far_back.0 - 100.0).abs();
let dy = (local_far_back.1 - 100.0).abs();
assert!(
dx < 0.01 && dy < 0.01,
"Far point roundtrip failed: {:?}",
local_far_back
);
}
}