Skip to content

Commit f3bc339

Browse files
HexorgBromeon
authored andcommitted
Added Godot's matching Transform3D operations
1 parent 8a82bda commit f3bc339

File tree

2 files changed

+291
-6
lines changed

2 files changed

+291
-6
lines changed

gdnative-core/src/core_types/geom/basis.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,46 @@ impl Basis {
247247
copy
248248
}
249249

250+
/// Returns linear interpolation on a sphere between two basis by weight amount (on the range of 0.0 to 1.0).
251+
#[inline]
252+
pub fn slerp(&self, other: &Basis, weight: f32) -> Self {
253+
#[inline]
254+
fn lerp_f(from: f32, to: f32, weight: f32) -> f32 {
255+
from + (to - from) * weight
256+
}
257+
let from = self.to_quat();
258+
let to = other.to_quat();
259+
let mut result = Basis::from_quat(from.slerp(to, weight));
260+
result.elements[0] *= lerp_f(
261+
self.elements[0].length(),
262+
other.elements[0].length(),
263+
weight,
264+
);
265+
result.elements[1] *= lerp_f(
266+
self.elements[1].length(),
267+
other.elements[1].length(),
268+
weight,
269+
);
270+
result.elements[2] *= lerp_f(
271+
self.elements[2].length(),
272+
other.elements[2].length(),
273+
weight,
274+
);
275+
276+
result
277+
}
278+
279+
/// Returns linear interpolation between two basis by weight amount (on the range of 0.0 to 1.0).
280+
#[inline]
281+
pub fn lerp(&self, other: Basis, weight: f32) -> Self {
282+
// this is how godot is doing it at https://github.com/godotengine/godot/blob/master/core/math/basis.cpp#L964
283+
// but Godot engine output for me differs than godot-rust
284+
let a = self.elements[0].linear_interpolate(other.elements[0], weight);
285+
let b = self.elements[1].linear_interpolate(other.elements[1], weight);
286+
let c = self.elements[2].linear_interpolate(other.elements[2], weight);
287+
Basis::from_rows(a, b, c)
288+
}
289+
250290
/// Transposes the matrix.
251291
#[inline]
252292
fn transpose(&mut self) {
@@ -309,6 +349,20 @@ impl Basis {
309349
m.is_equal_approx(&Self::IDENTITY)
310350
}
311351

352+
#[inline]
353+
pub fn orthogonalize(&mut self) {
354+
let scale = self.scale();
355+
self.orthonormalize();
356+
self.scale_local(scale);
357+
}
358+
359+
#[inline]
360+
pub fn orthogonalized(&self) -> Self {
361+
let mut copy = *self;
362+
copy.orthogonalize();
363+
copy
364+
}
365+
312366
/// Returns an orthonormalized version of the matrix: 3 orthogonal basis vectors of unit length.
313367
#[inline]
314368
pub fn orthonormalized(&self) -> Self {
@@ -369,6 +423,18 @@ impl Basis {
369423
copy
370424
}
371425

426+
/// In-place basis scaling in object-local coordinate system
427+
#[inline]
428+
fn scale_local(&mut self, scale: Vector3) {
429+
*self = self.scaled_local(scale);
430+
}
431+
432+
/// Returns basis scaled in object-local coordinate system
433+
#[inline]
434+
fn scaled_local(&self, scale: Vector3) -> Self {
435+
(*self) * Basis::from_diagonal(scale)
436+
}
437+
372438
/// Multiplies the matrix from left with the scaling matrix: M -> S·M
373439
///
374440
/// See the comment for [Basis::rotated](#method.rotated) for further explanation.

gdnative-core/src/core_types/geom/transform.rs

Lines changed: 225 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,9 @@ impl Transform {
116116
/// affine_inverse for transforms with scaling).
117117
#[inline]
118118
pub fn inverse(&self) -> Self {
119-
let basis_inv = self.basis.transposed();
120-
let origin_inv = basis_inv.xform(-self.origin);
121-
122-
Self {
123-
basis: basis_inv,
124-
origin: origin_inv,
119+
Transform {
120+
basis: self.basis.transposed(),
121+
origin: self.basis.xform(-self.origin),
125122
}
126123
}
127124

@@ -138,6 +135,25 @@ impl Transform {
138135
}
139136
}
140137

138+
/// In-place rotation of the transform around the given axis by the given
139+
/// angle (in radians), using matrix multiplication. The axis must be a
140+
/// normalized vector.
141+
/// Due to nature of the operation, a new transform is created first.
142+
#[inline]
143+
pub fn rotated(&self, axis: Vector3, phi: f32) -> Self {
144+
Transform {
145+
basis: Basis::from_axis_angle(axis, phi),
146+
origin: Vector3::default(),
147+
} * (*self)
148+
}
149+
150+
/// Returns the rotated transform around the given axis by the given angle (in radians),
151+
/// using matrix multiplication. The axis must be a normalized vector.
152+
#[inline]
153+
pub fn rotate(&mut self, axis: Vector3, phi: f32) {
154+
*self = self.rotated(axis, phi);
155+
}
156+
141157
/// Returns a copy of the transform rotated such that its -Z axis points
142158
/// towards the target position.
143159
///
@@ -157,6 +173,92 @@ impl Transform {
157173
}
158174
}
159175

176+
/// Scales basis and origin of the transform by the given scale factor,
177+
/// using matrix multiplication.
178+
#[inline]
179+
pub fn scaled(&self, scale: Vector3) -> Self {
180+
Transform {
181+
basis: self.basis.scaled(scale),
182+
origin: self.origin * scale,
183+
}
184+
}
185+
186+
/// In-place translates the transform by the given offset, relative to
187+
/// the transform's basis vectors.
188+
#[inline]
189+
fn translate_withbasis(&mut self, translation: Vector3) {
190+
// Note: Godot source uses origin + basis dot translation,
191+
// but self.translate() uses only origin + translation
192+
self.origin.x += self.basis.elements[0].dot(translation);
193+
self.origin.y += self.basis.elements[1].dot(translation);
194+
self.origin.z += self.basis.elements[2].dot(translation);
195+
}
196+
197+
/// Translates the transform by the given offset, relative to
198+
/// the transform's basis vectors.
199+
#[inline]
200+
pub fn translated_withbasis(&self, translation: Vector3) -> Self {
201+
let mut copy = *self;
202+
copy.translate_withbasis(translation);
203+
copy
204+
}
205+
206+
/// Returns the transform with the basis orthogonal (90 degrees),
207+
/// and normalized axis vectors.
208+
#[inline]
209+
pub fn orthonormalized(&self) -> Self {
210+
Transform {
211+
basis: self.basis.orthonormalized(),
212+
origin: self.origin,
213+
}
214+
}
215+
216+
/// Returns the transform with the basis orthogonal (90 degrees),
217+
/// but without normalizing the axis vectors.
218+
#[inline]
219+
pub fn orthogonalized(&self) -> Self {
220+
Transform {
221+
basis: self.basis.orthogonalized(),
222+
origin: self.origin,
223+
}
224+
}
225+
226+
#[inline]
227+
pub fn is_equal_approx(&self, other: Transform) -> bool {
228+
self.basis.is_equal_approx(&other.basis) && self.origin.is_equal_approx(other.origin)
229+
}
230+
231+
/// Interpolates the transform to other Transform by
232+
/// weight amount (on the range of 0.0 to 1.0).
233+
/// Assuming the two transforms are located on a sphere surface.
234+
#[inline]
235+
pub fn sphere_interpolate_with(&self, other: Transform, weight: f32) -> Self {
236+
let src_scale = self.basis.scale();
237+
let src_rot = self.basis.to_quat();
238+
let src_loc = self.origin;
239+
240+
let dst_scale = other.basis.scale();
241+
let dst_rot = other.basis.to_quat();
242+
let dst_loc = other.origin;
243+
244+
let new_basis = Basis::from_quat(src_rot.slerp(dst_rot, weight).normalized());
245+
let new_basis = new_basis.scaled(src_scale.linear_interpolate(dst_scale, weight));
246+
Transform {
247+
basis: new_basis,
248+
origin: src_loc.linear_interpolate(dst_loc, weight),
249+
}
250+
}
251+
252+
/// Interpolates the transform to other Transform by
253+
/// weight amount (on the range of 0.0 to 1.0).
254+
#[inline]
255+
pub fn interpolate_with(&self, other: Transform, weight: f32) -> Self {
256+
Transform {
257+
basis: self.basis.lerp(other.basis, weight),
258+
origin: self.origin.linear_interpolate(other.origin, weight),
259+
}
260+
}
261+
160262
#[doc(hidden)]
161263
#[inline]
162264
pub fn sys(&self) -> *const sys::godot_transform {
@@ -182,3 +284,120 @@ impl Mul<Transform> for Transform {
182284
Self { origin, basis }
183285
}
184286
}
287+
288+
#[cfg(test)]
289+
mod tests {
290+
use super::*;
291+
292+
fn test_inputs() -> (Transform, Transform) {
293+
let basis = Basis::from_euler(Vector3::new(37.51756, 20.39467, 49.96816));
294+
let mut t = Transform::from_basis_origin(
295+
basis.a(),
296+
basis.b(),
297+
basis.c(),
298+
Vector3::new(0.0, 0.0, 0.0),
299+
);
300+
t = t.translated_withbasis(Vector3::new(0.5, -1.0, 0.25));
301+
t = t.scaled(Vector3::new(0.25, 0.5, 2.0));
302+
303+
let basis = Basis::from_euler(Vector3::new(12.23, 50.46, 93.94));
304+
let mut t2 = Transform::from_basis_origin(
305+
basis.a(),
306+
basis.b(),
307+
basis.c(),
308+
Vector3::new(0.0, 0.0, 0.0),
309+
);
310+
t2 = t2.translated_withbasis(Vector3::new(1.5, -2.0, 1.25));
311+
t2 = t2.scaled(Vector3::new(0.5, 0.58, 1.0));
312+
// Godot reports:
313+
// t = 0.019358, -0.041264, 0.24581, -0.144074, 0.470205, 0.090279, -1.908901, -0.594598, 0.050514 - 0.112395, -0.519672, -0.347224
314+
// t2 = 0.477182, 0.118214, 0.09123, -0.165859, 0.521769, 0.191437, -0.086105, -0.367178, 0.926157 - 0.593383, -1.05303, 1.762894
315+
316+
(t, t2)
317+
}
318+
319+
#[test]
320+
fn translation_is_sane() {
321+
let translation = Vector3::new(1.0, 2.0, 3.0);
322+
let t = Transform::default().translated(translation);
323+
assert!(t.basis.elements[0] == Vector3::new(1.0, 0.0, 0.0));
324+
assert!(t.basis.elements[1] == Vector3::new(0.0, 1.0, 0.0));
325+
assert!(t.basis.elements[2] == Vector3::new(0.0, 0.0, 1.0));
326+
assert!(t.origin == translation);
327+
}
328+
329+
#[test]
330+
fn scale_is_sane() {
331+
let scale = Vector3::new(1.0, 2.0, 3.0);
332+
let t = Transform::default().scaled(scale);
333+
assert!(t.basis.elements[0] == Vector3::new(1.0, 0.0, 0.0));
334+
assert!(t.basis.elements[1] == Vector3::new(0.0, 2.0, 0.0));
335+
assert!(t.basis.elements[2] == Vector3::new(0.0, 0.0, 3.0));
336+
assert!(t.origin == Vector3::new(0.0, 0.0, 0.0));
337+
}
338+
339+
#[test]
340+
fn affine_inverse_is_sane() {
341+
// Godot reports:
342+
// From 0.019358, -0.041264, 0.24581, -0.144074, 0.470205, 0.090279, -1.908901, -0.594598, 0.050514 - 0.112395, -0.519672, -0.347224
343+
// To 0.309725, -0.576295, -0.477225, -0.66022, 1.880819, -0.148649, 3.932961, 0.361114, 0.012628 - -0.5, 1, -0.25
344+
let t = test_inputs().0.affine_inverse();
345+
let expected = Transform::from_basis_origin(
346+
Vector3::new(0.309725, -0.66022015, 3.9329607),
347+
Vector3::new(-0.57629496, 1.8808193, 0.3611141),
348+
Vector3::new(-0.47722515, -0.14864945, 0.012628445),
349+
Vector3::new(-0.5, 1.0, -0.25),
350+
);
351+
assert!(expected.is_equal_approx(t))
352+
}
353+
354+
#[test]
355+
fn orthonormalization_is_sane() {
356+
// Godot reports:
357+
// From 0.019358, -0.041264, 0.24581, -0.144074, 0.470205, 0.090279, -1.908901, -0.594598, 0.050514 - 0.112395, -0.519672, -0.347224
358+
// To 0.010112, -0.090928, 0.995806, -0.075257, 0.992963, 0.091432, -0.997113, -0.075866, 0.003197 - 0.112395, -0.519672, -0.347224
359+
let t = test_inputs().0.orthonormalized();
360+
let expected = Transform::from_basis_origin(
361+
Vector3::new(0.010111539, -0.0752568, -0.99711293),
362+
Vector3::new(-0.090927705, 0.9929635, -0.075865656),
363+
Vector3::new(0.99580616, 0.0914323, 0.0031974507),
364+
Vector3::new(0.11239518, -0.519672, -0.34722406),
365+
);
366+
assert!(expected.is_equal_approx(t))
367+
}
368+
369+
#[test]
370+
fn linear_interpolation_is_sane() {
371+
// Godot reports:
372+
// t = 0.019358, -0.041264, 0.24581, -0.144074, 0.470205, 0.090279, -1.908901, -0.594598, 0.050514 - 0.112395, -0.519672, -0.347224
373+
// t2 = 0.477182, 0.118214, 0.09123, -0.165859, 0.521769, 0.191437, -0.086105, -0.367178, 0.926157 - 0.593383, -1.05303, 1.762894
374+
// TODO: Get new godot result. https://github.com/godotengine/godot/commit/61759da5b35e44003ab3ffe3d4024dd611d17eff changed how Transform3D.linear_interpolate works
375+
// For now assuming this is sane - examined the new implementation manually.
376+
let (t, t2) = test_inputs();
377+
let result = t.interpolate_with(t2, 0.5);
378+
let expected = Transform::from_basis_origin(
379+
Vector3::new(0.24826992, -0.15496635, -0.997503),
380+
Vector3::new(0.038474888, 0.49598676, -0.4808879),
381+
Vector3::new(0.16852, 0.14085774, 0.48833522),
382+
Vector3::new(0.352889, -0.786351, 0.707835),
383+
);
384+
assert!(expected.is_equal_approx(result))
385+
}
386+
387+
#[test]
388+
fn sphere_linear_interpolation_is_sane() {
389+
// Godot reports:
390+
// t = 0.019358, -0.041264, 0.24581, -0.144074, 0.470205, 0.090279, -1.908901, -0.594598, 0.050514 - 0.112395, -0.519672, -0.347224
391+
// t2 = 0.477182, 0.118214, 0.09123, -0.165859, 0.521769, 0.191437, -0.086105, -0.367178, 0.926157 - 0.593383, -1.05303, 1.762894
392+
// result = 0.727909, -0.029075, 0.486138, -0.338385, 0.6514, 0.156468, -0.910002, -0.265481, 0.330678 - 0.352889, -0.786351, 0.707835
393+
let (t, t2) = test_inputs();
394+
let result = t.sphere_interpolate_with(t2, 0.5);
395+
let expected = Transform::from_basis_origin(
396+
Vector3::new(0.7279087, -0.19632529, -0.45626357),
397+
Vector3::new(-0.05011323, 0.65140045, -0.22942543),
398+
Vector3::new(0.9695858, 0.18105738, 0.33067825),
399+
Vector3::new(0.3528893, -0.78635097, 0.7078349),
400+
);
401+
assert!(expected.is_equal_approx(result))
402+
}
403+
}

0 commit comments

Comments
 (0)