1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
use nalgebra::{Matrix3, Matrix4, Rotation3, Vector3};
/// Describes where an object is, how it is rotated,
/// and how large it is.
///
/// All arrays use this order:
/// [x, y, z]
#[derive(bevy_ecs::component::Component, Copy, Clone, Debug, PartialEq)]
pub struct Transform {
/// Object position in the world:
/// [left/right, down/up, backward/forward]
pub position: [f32; 3],
/// Rotation around the X, Y, and Z axes.
///
/// IMPORTANT: These values are in radians, not degrees.
///
/// rotation[0] = rotation around X
/// rotation[1] = rotation around Y
/// rotation[2] = rotation around Z
pub rotation: [f32; 3],
/// Object size along each local axis:
///
/// scale[0] = size along X
/// scale[1] = size along Y
/// scale[2] = size along Z
///
/// [1.0, 1.0, 1.0] means normal size.
/// [2.0, 2.0, 2.0] means twice as large.
pub scale: [f32; 3],
}
impl Default for Transform {
fn default() -> Self {
Self {
// Start at the center of the world.
position: [0.0, 0.0, 0.0],
// No rotation.
rotation: [0.0, 0.0, 0.0],
// Normal size.
scale: [1.0, 1.0, 1.0],
}
}
}
impl Transform {
/// Creates a transform at the given position.
///
/// Rotation defaults to zero.
/// Scale defaults to one.
pub fn new(position: [f32; 3]) -> Self {
Self {
position,
..Default::default()
}
}
pub fn with_position(mut self, x: f32, y: f32, z: f32) -> Self {
self.position = [x, y, z];
self
}
/// Sets rotation in radians.
pub fn with_rotation(mut self, x: f32, y: f32, z: f32) -> Self {
self.rotation = [x, y, z];
self
}
pub fn with_scale(mut self, x: f32, y: f32, z: f32) -> Self {
self.scale = [x, y, z];
self
}
/// Combines position, rotation, and scale into one 4×4 matrix.
pub fn to_matrix(&self) -> [[f32; 4]; 4] {
// Creates this translation matrix:
//
// ┌ ┐
// │ 1 0 0 position.x │
// │ 0 1 0 position.y │
// │ 0 0 1 position.z │
// │ 0 0 0 1 │
// └ ┘
//
// It moves the object to its world position.
let translation =
Matrix4::new_translation(&Vector3::from(self.position));
// Creates a 3D rotation from:
//
// rotation[0] = angle around X
// rotation[1] = angle around Y
// rotation[2] = angle around Z
//
// to_homogeneous() turns the 3×3 rotation matrix
// into a 4×4 matrix.
let rotation = Rotation3::from_euler_angles(
self.rotation[0],
self.rotation[1],
self.rotation[2],
)
.to_homogeneous();
// Creates this scale matrix:
//
// ┌ ┐
// │ scale.x 0 0 0 │
// │ 0 scale.y 0 0 │
// │ 0 0 scale.z 0 │
// │ 0 0 0 1 │
// └ ┘
let scale = Matrix4::new_nonuniform_scaling(&Vector3::from(self.scale));
// Transformations are applied from right to left:
//
// 1. Scale the object
// 2. Rotate the object
// 3. Move the object
let model_matrix = translation * rotation * scale;
// nalgebra converts the matrix into a column-major array:
//
// [
// [Xx, Xy, Xz, 0.0], // transformed local X axis
// [Yx, Yy, Yz, 0.0], // transformed local Y axis
// [Zx, Zy, Zz, 0.0], // transformed local Z axis
// [px, py, pz, 1.0], // world position
// ]
//
// Therefore:
//
// result[0] = object's local X axis
// result[1] = object's local Y axis
// result[2] = object's local Z axis
// result[3] = object's position
model_matrix.into()
}
/// Extracts position, rotation, and scale from a 4×4 matrix.
///
/// nalgebra indexes matrices as:
///
/// m[(row, column)]
///
/// The mathematical matrix looks like:
///
/// ┌ ┐
/// │ m00 m01 m02 position.x │
/// │ m10 m11 m12 position.y │
/// │ m20 m21 m22 position.z │
/// │ 0 0 0 1 │
/// └ ┘
pub fn from_matrix(m: Matrix4<f32>) -> Self {
// Translation is stored in column 3:
//
// m[(0, 3)] = X position
// m[(1, 3)] = Y position
// m[(2, 3)] = Z position
let position = [m[(0, 3)], m[(1, 3)], m[(2, 3)]];
// The first three columns describe the object's
// transformed local axes.
//
// Column 0 = local X direction multiplied by X scale
// Column 1 = local Y direction multiplied by Y scale
// Column 2 = local Z direction multiplied by Z scale
//
// The length of each column gives us its scale.
let scale = [
// Length of column 0: X scale
Vector3::new(m[(0, 0)], m[(1, 0)], m[(2, 0)]).norm(),
// Length of column 1: Y scale
Vector3::new(m[(0, 1)], m[(1, 1)], m[(2, 1)]).norm(),
// Length of column 2: Z scale
Vector3::new(m[(0, 2)], m[(1, 2)], m[(2, 2)]).norm(),
];
// Each axis currently contains both rotation and scale.
//
// Dividing each column by its scale removes the scale,
// leaving only the rotation.
//
// For example:
//
// m[(0, 0)] / scale[0]
//
// means:
// "world-X part of the local X axis, without X scale."
let rotation_matrix = Matrix3::new(
// Row 0: how local X, Y, and Z affect world X
m[(0, 0)] / scale[0],
m[(0, 1)] / scale[1],
m[(0, 2)] / scale[2],
// Row 1: how local X, Y, and Z affect world Y
m[(1, 0)] / scale[0],
m[(1, 1)] / scale[1],
m[(1, 2)] / scale[2],
// Row 2: how local X, Y, and Z affect world Z
m[(2, 0)] / scale[0],
m[(2, 1)] / scale[1],
m[(2, 2)] / scale[2],
);
// Convert the rotation matrix back into:
//
// rotation.0 = X angle
// rotation.1 = Y angle
// rotation.2 = Z angle
let rotation =
Rotation3::from_matrix_unchecked(rotation_matrix).euler_angles();
Self {
position,
rotation: [rotation.0, rotation.1, rotation.2],
scale,
}
}
}