use super::GlobalTransform;
use bevy_math::{Affine3A, Dir3, Isometry3d, Mat3, Mat4, Quat, Vec3};
use core::ops::Mul;
#[cfg(feature = "bevy-support")]
use bevy_ecs::component::Component;
#[cfg(feature = "bevy_reflect")]
use {bevy_ecs::reflect::ReflectComponent, bevy_reflect::prelude::*};
#[cfg(debug_assertions)]
fn assert_is_normalized(message: &str, length_squared: f32) {
use bevy_math::ops;
#[cfg(feature = "std")]
use std::eprintln;
let length_error_squared = ops::abs(length_squared - 1.0);
if length_error_squared > 2e-2 || length_error_squared.is_nan() {
panic!("Error: {message}",);
} else if length_error_squared > 2e-4 {
#[cfg(feature = "std")]
#[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]
{
eprintln!("Warning: {message}",);
}
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy-support",
derive(Component),
require(GlobalTransform, TransformTreeChanged)
)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Default, PartialEq, Debug, Clone)
)]
#[cfg_attr(
all(feature = "bevy_reflect", feature = "serialize"),
reflect(Serialize, Deserialize)
)]
pub struct Transform {
pub translation: Vec3,
pub rotation: Quat,
pub scale: Vec3,
}
impl Transform {
pub const IDENTITY: Self = Transform {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
#[inline]
pub const fn from_xyz(x: f32, y: f32, z: f32) -> Self {
Self::from_translation(Vec3::new(x, y, z))
}
#[inline]
pub fn from_matrix(world_from_local: Mat4) -> Self {
let (scale, rotation, translation) = world_from_local.to_scale_rotation_translation();
Transform {
translation,
rotation,
scale,
}
}
#[inline]
pub const fn from_translation(translation: Vec3) -> Self {
Transform {
translation,
..Self::IDENTITY
}
}
#[inline]
pub const fn from_rotation(rotation: Quat) -> Self {
Transform {
rotation,
..Self::IDENTITY
}
}
#[inline]
pub const fn from_scale(scale: Vec3) -> Self {
Transform {
scale,
..Self::IDENTITY
}
}
#[inline]
pub fn from_isometry(iso: Isometry3d) -> Self {
Transform {
translation: iso.translation.into(),
rotation: iso.rotation,
..Self::IDENTITY
}
}
#[inline]
#[must_use]
pub fn looking_at(mut self, target: Vec3, up: impl TryInto<Dir3>) -> Self {
self.look_at(target, up);
self
}
#[inline]
#[must_use]
pub fn looking_to(mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) -> Self {
self.look_to(direction, up);
self
}
#[inline]
#[must_use]
pub fn aligned_by(
mut self,
main_axis: impl TryInto<Dir3>,
main_direction: impl TryInto<Dir3>,
secondary_axis: impl TryInto<Dir3>,
secondary_direction: impl TryInto<Dir3>,
) -> Self {
self.align(
main_axis,
main_direction,
secondary_axis,
secondary_direction,
);
self
}
#[inline]
#[must_use]
pub const fn with_translation(mut self, translation: Vec3) -> Self {
self.translation = translation;
self
}
#[inline]
#[must_use]
pub const fn with_rotation(mut self, rotation: Quat) -> Self {
self.rotation = rotation;
self
}
#[inline]
#[must_use]
pub const fn with_scale(mut self, scale: Vec3) -> Self {
self.scale = scale;
self
}
#[inline]
pub fn to_matrix(&self) -> Mat4 {
Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
}
#[inline]
pub fn compute_affine(&self) -> Affine3A {
Affine3A::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
}
#[inline]
pub fn local_x(&self) -> Dir3 {
Dir3::new_unchecked(self.rotation * Vec3::X)
}
#[inline]
pub fn left(&self) -> Dir3 {
-self.local_x()
}
#[inline]
pub fn right(&self) -> Dir3 {
self.local_x()
}
#[inline]
pub fn local_y(&self) -> Dir3 {
Dir3::new_unchecked(self.rotation * Vec3::Y)
}
#[inline]
pub fn up(&self) -> Dir3 {
self.local_y()
}
#[inline]
pub fn down(&self) -> Dir3 {
-self.local_y()
}
#[inline]
pub fn local_z(&self) -> Dir3 {
Dir3::new_unchecked(self.rotation * Vec3::Z)
}
#[inline]
pub fn forward(&self) -> Dir3 {
-self.local_z()
}
#[inline]
pub fn back(&self) -> Dir3 {
self.local_z()
}
#[inline]
pub fn rotate(&mut self, rotation: Quat) {
self.rotation = rotation * self.rotation;
}
#[inline]
pub fn rotate_axis(&mut self, axis: Dir3, angle: f32) {
#[cfg(debug_assertions)]
assert_is_normalized(
"The axis given to `Transform::rotate_axis` is not normalized. This may be a result of obtaining \
the axis from the transform. See the documentation of `Transform::rotate_axis` for more details.",
axis.length_squared(),
);
self.rotate(Quat::from_axis_angle(axis.into(), angle));
}
#[inline]
pub fn rotate_x(&mut self, angle: f32) {
self.rotate(Quat::from_rotation_x(angle));
}
#[inline]
pub fn rotate_y(&mut self, angle: f32) {
self.rotate(Quat::from_rotation_y(angle));
}
#[inline]
pub fn rotate_z(&mut self, angle: f32) {
self.rotate(Quat::from_rotation_z(angle));
}
#[inline]
pub fn rotate_local(&mut self, rotation: Quat) {
self.rotation *= rotation;
}
#[inline]
pub fn rotate_local_axis(&mut self, axis: Dir3, angle: f32) {
#[cfg(debug_assertions)]
assert_is_normalized(
"The axis given to `Transform::rotate_axis_local` is not normalized. This may be a result of obtaining \
the axis from the transform. See the documentation of `Transform::rotate_axis_local` for more details.",
axis.length_squared(),
);
self.rotate_local(Quat::from_axis_angle(axis.into(), angle));
}
#[inline]
pub fn rotate_local_x(&mut self, angle: f32) {
self.rotate_local(Quat::from_rotation_x(angle));
}
#[inline]
pub fn rotate_local_y(&mut self, angle: f32) {
self.rotate_local(Quat::from_rotation_y(angle));
}
#[inline]
pub fn rotate_local_z(&mut self, angle: f32) {
self.rotate_local(Quat::from_rotation_z(angle));
}
#[inline]
pub fn translate_around(&mut self, point: Vec3, rotation: Quat) {
self.translation = point + rotation * (self.translation - point);
}
#[inline]
pub fn rotate_around(&mut self, point: Vec3, rotation: Quat) {
self.translate_around(point, rotation);
self.rotate(rotation);
}
#[inline]
pub fn look_at(&mut self, target: Vec3, up: impl TryInto<Dir3>) {
self.look_to(target - self.translation, up);
}
#[inline]
pub fn look_to(&mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) {
let back = -direction.try_into().unwrap_or(Dir3::NEG_Z);
let up = up.try_into().unwrap_or(Dir3::Y);
let right = up
.cross(back.into())
.try_normalize()
.unwrap_or_else(|| up.any_orthonormal_vector());
let up = back.cross(right);
self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, back.into()));
}
#[inline]
pub fn align(
&mut self,
main_axis: impl TryInto<Dir3>,
main_direction: impl TryInto<Dir3>,
secondary_axis: impl TryInto<Dir3>,
secondary_direction: impl TryInto<Dir3>,
) {
let main_axis = main_axis.try_into().unwrap_or(Dir3::X);
let main_direction = main_direction.try_into().unwrap_or(Dir3::X);
let secondary_axis = secondary_axis.try_into().unwrap_or(Dir3::Y);
let secondary_direction = secondary_direction.try_into().unwrap_or(Dir3::Y);
let first_rotation = Quat::from_rotation_arc(main_axis.into(), main_direction.into());
let secondary_image = first_rotation * secondary_axis;
let secondary_image_ortho = secondary_image
.reject_from_normalized(main_direction.into())
.try_normalize();
let secondary_direction_ortho = secondary_direction
.reject_from_normalized(main_direction.into())
.try_normalize();
self.rotation = match (secondary_image_ortho, secondary_direction_ortho) {
(Some(secondary_img_ortho), Some(secondary_dir_ortho)) => {
let second_rotation =
Quat::from_rotation_arc(secondary_img_ortho, secondary_dir_ortho);
second_rotation * first_rotation
}
_ => first_rotation,
};
}
#[inline]
#[must_use]
pub fn mul_transform(&self, transform: Transform) -> Self {
let translation = self.transform_point(transform.translation);
let rotation = self.rotation * transform.rotation;
let scale = self.scale * transform.scale;
Transform {
translation,
rotation,
scale,
}
}
#[inline]
pub fn transform_point(&self, mut point: Vec3) -> Vec3 {
point = self.scale * point;
point = self.rotation * point;
point += self.translation;
point
}
#[inline]
#[must_use]
pub fn is_finite(&self) -> bool {
self.translation.is_finite() && self.rotation.is_finite() && self.scale.is_finite()
}
#[inline]
pub fn to_isometry(&self) -> Isometry3d {
Isometry3d::new(self.translation, self.rotation)
}
}
impl Default for Transform {
fn default() -> Self {
Self::IDENTITY
}
}
impl From<GlobalTransform> for Transform {
fn from(transform: GlobalTransform) -> Self {
transform.compute_transform()
}
}
impl Mul<Transform> for Transform {
type Output = Transform;
fn mul(self, transform: Transform) -> Self::Output {
self.mul_transform(transform)
}
}
impl Mul<GlobalTransform> for Transform {
type Output = GlobalTransform;
#[inline]
fn mul(self, global_transform: GlobalTransform) -> Self::Output {
GlobalTransform::from(self) * global_transform
}
}
impl Mul<Vec3> for Transform {
type Output = Vec3;
fn mul(self, value: Vec3) -> Self::Output {
self.transform_point(value)
}
}
#[derive(Clone, Copy, Default, PartialEq, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy-support", derive(Component))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Default, PartialEq, Debug)
)]
#[cfg_attr(
all(feature = "bevy_reflect", feature = "serialize"),
reflect(Serialize, Deserialize)
)]
pub struct TransformTreeChanged;