1use std::cmp::Ordering;
9use std::fmt::Display;
10use std::ops::{Mul, MulAssign};
11
12use godot_ffi as sys;
13use sys::{ffi_methods, ExtVariantType, GodotFfi};
14
15use crate::builtin::math::{ApproxEq, FloatExt, GlamConv, GlamType, XformInv};
16use crate::builtin::real_consts::FRAC_PI_2;
17use crate::builtin::{real, EulerOrder, Quaternion, RMat3, RQuat, RVec2, RVec3, Vector3};
18
19#[derive(Copy, Clone, PartialEq, Debug)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50#[repr(C)]
51pub struct Basis {
52 pub rows: [Vector3; 3],
58}
59
60impl Basis {
61 pub const IDENTITY: Self = Self::from_diagonal(1.0, 1.0, 1.0);
65
66 pub const FLIP_X: Self = Self::from_diagonal(-1.0, 1.0, 1.0);
70
71 pub const FLIP_Y: Self = Self::from_diagonal(1.0, -1.0, 1.0);
75
76 pub const FLIP_Z: Self = Self::from_diagonal(1.0, 1.0, -1.0);
80
81 pub const fn from_rows(x: Vector3, y: Vector3, z: Vector3) -> Self {
83 Self { rows: [x, y, z] }
84 }
85
86 pub const fn from_cols(a: Vector3, b: Vector3, c: Vector3) -> Self {
88 Self::from_rows_array(&[a.x, b.x, c.x, a.y, b.y, c.y, a.z, b.z, c.z])
89 }
90
91 pub fn from_axis_angle(axis: Vector3, angle: real) -> Self {
95 RMat3::from_axis_angle(axis.to_glam(), angle).to_front()
96 }
97
98 pub const fn from_diagonal(x: real, y: real, z: real) -> Self {
100 Self {
101 rows: [
102 Vector3::new(x, 0.0, 0.0),
103 Vector3::new(0.0, y, 0.0),
104 Vector3::new(0.0, 0.0, z),
105 ],
106 }
107 }
108
109 pub const fn from_scale(scale: Vector3) -> Self {
113 Self::from_diagonal(scale.x, scale.y, scale.z)
114 }
115
116 const fn from_rows_array(rows: &[real; 9]) -> Self {
117 let [ax, bx, cx, ay, by, cy, az, bz, cz] = rows;
118 Self::from_rows(
119 Vector3::new(*ax, *bx, *cx),
120 Vector3::new(*ay, *by, *cy),
121 Vector3::new(*az, *bz, *cz),
122 )
123 }
124
125 pub fn from_quaternion(quat: Quaternion) -> Self {
129 RMat3::from_quat(quat.to_glam()).to_front()
130 }
131
132 pub fn from_euler(order: EulerOrder, angles: Vector3) -> Self {
137 let Vector3 { x: a, y: b, z: c } = angles;
144 let xmat =
145 Basis::from_rows_array(&[1.0, 0.0, 0.0, 0.0, a.cos(), -a.sin(), 0.0, a.sin(), a.cos()]);
146 let ymat =
147 Basis::from_rows_array(&[b.cos(), 0.0, b.sin(), 0.0, 1.0, 0.0, -b.sin(), 0.0, b.cos()]);
148 let zmat =
149 Basis::from_rows_array(&[c.cos(), -c.sin(), 0.0, c.sin(), c.cos(), 0.0, 0.0, 0.0, 1.0]);
150
151 match order {
152 EulerOrder::XYZ => xmat * ymat * zmat,
153 EulerOrder::XZY => xmat * zmat * ymat,
154 EulerOrder::YXZ => ymat * xmat * zmat,
155 EulerOrder::YZX => ymat * zmat * xmat,
156 EulerOrder::ZXY => zmat * xmat * ymat,
157 EulerOrder::ZYX => zmat * ymat * xmat,
158 }
159 }
160
161 pub fn looking_at(target: Vector3, up: Vector3, use_model_front: bool) -> Self {
167 super::inner::InnerBasis::looking_at(target, up, use_model_front)
168 }
169
170 pub fn to_cols(&self) -> [Vector3; 3] {
172 self.transposed().rows
173 }
174
175 const fn to_rows_array(self) -> [real; 9] {
176 let [Vector3 {
177 x: ax,
178 y: bx,
179 z: cx,
180 }, Vector3 {
181 x: ay,
182 y: by,
183 z: cy,
184 }, Vector3 {
185 x: az,
186 y: bz,
187 z: cz,
188 }] = self.rows;
189 [ax, bx, cx, ay, by, cy, az, bz, cz]
190 }
191
192 #[doc(alias = "get_rotation_quaternion")]
196 pub fn get_quaternion(&self) -> Quaternion {
197 RQuat::from_mat3(&self.orthonormalized().to_glam()).to_front()
198 }
199
200 #[must_use]
204 pub fn get_scale(&self) -> Vector3 {
205 let det = self.determinant();
206 let det_sign = if det < 0.0 { -1.0 } else { 1.0 };
207
208 Vector3::new(
209 self.col_a().length(),
210 self.col_b().length(),
211 self.col_c().length(),
212 ) * det_sign
213 }
214
215 pub fn get_euler(&self) -> Vector3 {
219 self.get_euler_with(EulerOrder::YXZ)
220 }
221
222 pub fn get_euler_with(&self, order: EulerOrder) -> Vector3 {
228 use glam::swizzles::Vec3Swizzles as _;
229
230 let col_a = self.col_a().to_glam();
231 let col_b = self.col_b().to_glam();
232 let col_c = self.col_c().to_glam();
233
234 let row_a = self.rows[0].to_glam();
235 let row_b = self.rows[1].to_glam();
236 let row_c = self.rows[2].to_glam();
237
238 let major = match order {
239 EulerOrder::XYZ => self.rows[0].z,
240 EulerOrder::XZY => self.rows[0].y,
241 EulerOrder::YXZ => self.rows[1].z,
242 EulerOrder::YZX => self.rows[1].x,
243 EulerOrder::ZXY => self.rows[2].y,
244 EulerOrder::ZYX => self.rows[2].x,
245 };
246
247 if let Some(pure_rotation) = match order {
249 EulerOrder::XYZ => self
250 .to_euler_pure_rotation(major, 1, row_a.zx())
251 .map(RVec3::yxz),
252 EulerOrder::YXZ => {
253 self.to_euler_pure_rotation(major, 0, RVec2::new(-major, self.rows[1].y))
254 }
255 _ => None,
256 } {
257 return pure_rotation.to_front();
258 }
259
260 match order {
261 EulerOrder::XYZ => {
262 -Self::to_euler_inner(major, col_c.yz(), row_a.yx(), col_b.zy()).yxz()
263 }
264 EulerOrder::XZY => {
265 Self::to_euler_inner(major, col_b.zy(), row_a.zx(), col_c.yz()).yzx()
266 }
267 EulerOrder::YXZ => {
268 let mut vec = Self::to_euler_inner(major, col_c.xz(), row_b.xy(), row_a.yx());
269 if Self::is_between_neg1_1(major).is_lt() {
270 vec.y = -vec.y;
271 }
272 vec
273 }
274 EulerOrder::YZX => {
275 -Self::to_euler_inner(major, row_b.zy(), col_a.zx(), row_c.yz()).yzx()
276 }
277 EulerOrder::ZXY => {
278 -Self::to_euler_inner(major, row_c.xz(), col_b.xy(), row_a.zx()).xyz()
279 }
280 EulerOrder::ZYX => {
281 Self::to_euler_inner(major, col_a.yx(), row_c.yz(), col_b.xy()).zxy()
282 }
283 }
284 .to_front()
285 }
286
287 fn is_between_neg1_1(f: real) -> Ordering {
288 if f >= (1.0 - real::CMP_EPSILON) {
289 Ordering::Greater
290 } else if f <= -(1.0 - real::CMP_EPSILON) {
291 Ordering::Less
292 } else {
293 Ordering::Equal
294 }
295 }
296
297 fn is_identity_index(&self, index: usize) -> bool {
300 let row = self.rows[index];
301 let col = self.transposed().rows[index];
302 if row != col {
303 return false;
304 }
305 match index {
306 0 => row == Vector3::RIGHT,
307 1 => row == Vector3::UP,
308 2 => row == Vector3::BACK,
309 _ => panic!("Unknown Index {index}"),
310 }
311 }
312
313 #[allow(clippy::wrong_self_convention)]
314 fn to_euler_pure_rotation(
315 &self,
316 major: real,
317 index: usize,
318 rotation_vec: RVec2,
319 ) -> Option<RVec3> {
320 if Self::is_between_neg1_1(major).is_ne() {
321 return None;
322 }
323
324 if !self.is_identity_index(index) {
325 return None;
326 }
327
328 Some(RVec3::new(
329 real::atan2(rotation_vec.x, rotation_vec.y),
330 0.0,
331 0.0,
332 ))
333 }
334
335 #[allow(clippy::wrong_self_convention)]
336 fn to_euler_inner(major: real, pair0: RVec2, pair1: RVec2, pair2: RVec2) -> RVec3 {
337 match Self::is_between_neg1_1(major) {
338 Ordering::Less => RVec3::new(FRAC_PI_2, -real::atan2(pair2.x, pair2.y), 0.0),
340 Ordering::Equal => RVec3::new(
342 real::asin(-major),
343 real::atan2(pair0.x, pair0.y),
344 real::atan2(pair1.x, pair1.y),
345 ),
346 Ordering::Greater => RVec3::new(-FRAC_PI_2, -real::atan2(pair2.x, pair2.y), 0.0),
348 }
349 }
350
351 pub fn determinant(&self) -> real {
355 self.to_glam().determinant()
356 }
357
358 #[must_use]
362 pub fn scaled(&self, scale: Vector3) -> Self {
363 Self::from_diagonal(scale.x, scale.y, scale.z) * (*self)
364 }
365
366 #[must_use]
370 pub fn inverse(&self) -> Basis {
371 self.glam(|mat| mat.inverse())
372 }
373
374 #[must_use]
378 pub fn transposed(&self) -> Self {
379 Self::from_cols(self.rows[0], self.rows[1], self.rows[2])
380 }
381
382 #[must_use]
392 pub fn orthonormalized(&self) -> Self {
393 assert!(
394 !self.determinant().is_zero_approx(),
395 "Determinant should not be zero."
396 );
397
398 let mut x = self.col_a();
400 let mut y = self.col_b();
401 let mut z = self.col_c();
402
403 x = x.normalized();
404 y = y - x * x.dot(y);
405 y = y.normalized();
406 z = z - x * x.dot(z) - y * y.dot(z);
407 z = z.normalized();
408
409 Self::from_cols(x, y, z)
410 }
411
412 #[must_use]
417 pub fn rotated(&self, axis: Vector3, angle: real) -> Self {
418 Self::from_axis_angle(axis, angle) * (*self)
419 }
420
421 #[must_use]
426 pub fn slerp(&self, other: &Self, weight: real) -> Self {
427 let from = self.get_quaternion();
428 let to = other.get_quaternion();
429
430 let mut result = Self::from_quaternion(from.slerp(to, weight));
431
432 for i in 0..3 {
433 result.rows[i] *= self.rows[i].length().lerp(other.rows[i].length(), weight);
434 }
435
436 result
437 }
438
439 #[must_use]
443 pub fn tdotx(&self, with: Vector3) -> real {
444 self.col_a().dot(with)
445 }
446
447 #[must_use]
451 pub fn tdoty(&self, with: Vector3) -> real {
452 self.col_b().dot(with)
453 }
454
455 #[must_use]
459 pub fn tdotz(&self, with: Vector3) -> real {
460 self.col_c().dot(with)
461 }
462
463 pub fn is_finite(&self) -> bool {
468 self.rows[0].is_finite() && self.rows[1].is_finite() && self.rows[2].is_finite()
469 }
470
471 #[doc(alias = "x")]
475 #[must_use]
476 pub fn col_a(&self) -> Vector3 {
477 Vector3::new(self.rows[0].x, self.rows[1].x, self.rows[2].x)
478 }
479
480 pub fn set_col_a(&mut self, col: Vector3) {
482 self.rows[0].x = col.x;
483 self.rows[1].x = col.y;
484 self.rows[2].x = col.z;
485 }
486
487 #[doc(alias = "y")]
491 #[must_use]
492 pub fn col_b(&self) -> Vector3 {
493 Vector3::new(self.rows[0].y, self.rows[1].y, self.rows[2].y)
494 }
495
496 pub fn set_col_b(&mut self, col: Vector3) {
498 self.rows[0].y = col.x;
499 self.rows[1].y = col.y;
500 self.rows[2].y = col.z;
501 }
502
503 #[doc(alias = "z")]
507 #[must_use]
508 pub fn col_c(&self) -> Vector3 {
509 Vector3::new(self.rows[0].z, self.rows[1].z, self.rows[2].z)
510 }
511
512 pub fn set_col_c(&mut self, col: Vector3) {
514 self.rows[0].z = col.x;
515 self.rows[1].z = col.y;
516 self.rows[2].z = col.z;
517 }
518}
519
520impl Display for Basis {
521 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
522 let [a, b, c] = self.to_cols();
526
527 write!(f, "[a: {a}, b: {b}, c: {c}]")
528 }
529}
530
531impl ApproxEq for Basis {
532 fn approx_eq(&self, other: &Self) -> bool {
534 Vector3::approx_eq(&self.rows[0], &other.rows[0])
535 && Vector3::approx_eq(&self.rows[1], &other.rows[1])
536 && Vector3::approx_eq(&self.rows[2], &other.rows[2])
537 }
538}
539
540impl GlamConv for Basis {
541 type Glam = RMat3;
542}
543
544impl GlamType for RMat3 {
545 type Mapped = Basis;
546
547 fn to_front(&self) -> Self::Mapped {
548 Basis::from_rows_array(&self.to_cols_array()).transposed()
549 }
550
551 fn from_front(mapped: &Self::Mapped) -> Self {
552 Self::from_cols_array(&mapped.to_rows_array()).transpose()
553 }
554}
555
556#[cfg(not(feature = "double-precision"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "double-precision"))))]
557impl GlamType for glam::Mat3A {
558 type Mapped = Basis;
559
560 fn to_front(&self) -> Self::Mapped {
561 Basis::from_rows_array(&self.to_cols_array()).transposed()
562 }
563
564 fn from_front(mapped: &Self::Mapped) -> Self {
565 Self::from_cols_array(&mapped.to_rows_array()).transpose()
566 }
567}
568
569impl Default for Basis {
570 fn default() -> Self {
571 Self::IDENTITY
572 }
573}
574
575impl Mul for Basis {
576 type Output = Self;
577
578 fn mul(self, rhs: Self) -> Self::Output {
579 self.glam2(&rhs, |a, b| a * b)
580 }
581}
582
583impl Mul<real> for Basis {
584 type Output = Self;
585
586 fn mul(mut self, rhs: real) -> Self::Output {
587 self *= rhs;
588 self
589 }
590}
591
592impl MulAssign<real> for Basis {
593 fn mul_assign(&mut self, rhs: real) {
594 self.rows[0] *= rhs;
595 self.rows[1] *= rhs;
596 self.rows[2] *= rhs;
597 }
598}
599
600impl Mul<Vector3> for Basis {
601 type Output = Vector3;
602
603 fn mul(self, rhs: Vector3) -> Self::Output {
604 self.glam2(&rhs, |a, b| a * b)
605 }
606}
607
608impl XformInv<Vector3> for Basis {
609 fn xform_inv(&self, rhs: Vector3) -> Vector3 {
624 Vector3::new(
625 self.col_a().dot(rhs),
626 self.col_b().dot(rhs),
627 self.col_c().dot(rhs),
628 )
629 }
630}
631
632unsafe impl GodotFfi for Basis {
635 const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::BASIS);
636
637 ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
638}
639
640crate::meta::impl_godot_as_self!(Basis: ByValue);
641
642#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
643mod test {
644 use super::*;
645 use crate::assert_eq_approx;
646 use crate::builtin::real_consts::{FRAC_PI_2, PI};
647
648 fn deg_to_rad(rotation: Vector3) -> Vector3 {
649 Vector3::new(
650 rotation.x.to_radians(),
651 rotation.y.to_radians(),
652 rotation.z.to_radians(),
653 )
654 }
655
656 fn test_rotation(deg_original_euler: Vector3, rot_order: EulerOrder) {
658 let original_euler: Vector3 = deg_to_rad(deg_original_euler);
677 let to_rotation: Basis = Basis::from_euler(rot_order, original_euler);
678
679 let euler_from_rotation: Vector3 = to_rotation.get_euler_with(rot_order);
681 let rotation_from_computed_euler: Basis = Basis::from_euler(rot_order, euler_from_rotation);
682
683 let res: Basis = to_rotation.inverse() * rotation_from_computed_euler;
684 assert!(
685 (res.col_a() - Vector3::RIGHT).length() <= 0.1,
686 "Fail due to X {} with {deg_original_euler} using {rot_order:?}",
687 res.col_a()
688 );
689 assert!(
690 (res.col_b() - Vector3::UP).length() <= 0.1,
691 "Fail due to Y {} with {deg_original_euler} using {rot_order:?}",
692 res.col_b()
693 );
694 assert!(
695 (res.col_c() - Vector3::BACK).length() <= 0.1,
696 "Fail due to Z {} with {deg_original_euler} using {rot_order:?}",
697 res.col_c()
698 );
699
700 let euler_xyz_from_rotation: Vector3 = to_rotation.get_euler_with(EulerOrder::XYZ);
702 let rotation_from_xyz_computed_euler: Basis =
703 Basis::from_euler(EulerOrder::XYZ, euler_xyz_from_rotation);
704
705 let res = to_rotation.inverse() * rotation_from_xyz_computed_euler;
706
707 assert!(
708 (res.col_a() - Vector3::new(1.0, 0.0, 0.0)).length() <= 0.1,
709 "Double check with XYZ rot order failed, due to X {} with {deg_original_euler} using {rot_order:?}",
710 res.col_a(),
711 );
712 assert!(
713 (res.col_b() - Vector3::new(0.0, 1.0, 0.0)).length() <= 0.1,
714 "Double check with XYZ rot order failed, due to Y {} with {deg_original_euler} using {rot_order:?}",
715 res.col_b(),
716 );
717 assert!(
718 (res.col_c() - Vector3::new(0.0, 0.0, 1.0)).length() <= 0.1,
719 "Double check with XYZ rot order failed, due to Z {} with {deg_original_euler} using {rot_order:?}",
720 res.col_c(),
721 );
722 }
723
724 #[test]
725 fn consts_behavior_correct() {
726 let v = Vector3::new(1.0, 2.0, 3.0);
727
728 assert_eq_approx!(Basis::IDENTITY * v, v);
729 assert_eq_approx!(Basis::FLIP_X * v, Vector3::new(-v.x, v.y, v.z),);
730 assert_eq_approx!(Basis::FLIP_Y * v, Vector3::new(v.x, -v.y, v.z),);
731 assert_eq_approx!(Basis::FLIP_Z * v, Vector3::new(v.x, v.y, -v.z),);
732 }
733
734 #[test]
735 fn basic_rotation_correct() {
736 assert_eq_approx!(
737 Basis::from_axis_angle(Vector3::FORWARD, 0.0) * Vector3::RIGHT,
738 Vector3::RIGHT,
739 );
740 assert_eq_approx!(
741 Basis::from_axis_angle(Vector3::FORWARD, FRAC_PI_2) * Vector3::RIGHT,
742 Vector3::DOWN,
743 );
744 assert_eq_approx!(
745 Basis::from_axis_angle(Vector3::FORWARD, PI) * Vector3::RIGHT,
746 Vector3::LEFT,
747 );
748 assert_eq_approx!(
749 Basis::from_axis_angle(Vector3::FORWARD, PI + FRAC_PI_2) * Vector3::RIGHT,
750 Vector3::UP,
751 );
752 }
753
754 #[test]
756 fn basis_euler_conversions() {
757 let euler_order_to_test: Vec<EulerOrder> = vec![
758 EulerOrder::XYZ,
759 EulerOrder::XZY,
760 EulerOrder::YZX,
761 EulerOrder::YXZ,
762 EulerOrder::ZXY,
763 EulerOrder::ZYX,
764 ];
765
766 let vectors_to_test: Vec<Vector3> = vec![
767 Vector3::new(0.0, 0.0, 0.0),
768 Vector3::new(0.5, 0.5, 0.5),
769 Vector3::new(-0.5, -0.5, -0.5),
770 Vector3::new(40.0, 40.0, 40.0),
771 Vector3::new(-40.0, -40.0, -40.0),
772 Vector3::new(0.0, 0.0, -90.0),
773 Vector3::new(0.0, -90.0, 0.0),
774 Vector3::new(-90.0, 0.0, 0.0),
775 Vector3::new(0.0, 0.0, 90.0),
776 Vector3::new(0.0, 90.0, 0.0),
777 Vector3::new(90.0, 0.0, 0.0),
778 Vector3::new(0.0, 0.0, -30.0),
779 Vector3::new(0.0, -30.0, 0.0),
780 Vector3::new(-30.0, 0.0, 0.0),
781 Vector3::new(0.0, 0.0, 30.0),
782 Vector3::new(0.0, 30.0, 0.0),
783 Vector3::new(30.0, 0.0, 0.0),
784 Vector3::new(0.5, 50.0, 20.0),
785 Vector3::new(-0.5, -50.0, -20.0),
786 Vector3::new(0.5, 0.0, 90.0),
787 Vector3::new(0.5, 0.0, -90.0),
788 Vector3::new(360.0, 360.0, 360.0),
789 Vector3::new(-360.0, -360.0, -360.0),
790 Vector3::new(-90.0, 60.0, -90.0),
791 Vector3::new(90.0, 60.0, -90.0),
792 Vector3::new(90.0, -60.0, -90.0),
793 Vector3::new(-90.0, -60.0, -90.0),
794 Vector3::new(-90.0, 60.0, 90.0),
795 Vector3::new(90.0, 60.0, 90.0),
796 Vector3::new(90.0, -60.0, 90.0),
797 Vector3::new(-90.0, -60.0, 90.0),
798 Vector3::new(60.0, 90.0, -40.0),
799 Vector3::new(60.0, -90.0, -40.0),
800 Vector3::new(-60.0, -90.0, -40.0),
801 Vector3::new(-60.0, 90.0, 40.0),
802 Vector3::new(60.0, 90.0, 40.0),
803 Vector3::new(60.0, -90.0, 40.0),
804 Vector3::new(-60.0, -90.0, 40.0),
805 Vector3::new(-90.0, 90.0, -90.0),
806 Vector3::new(90.0, 90.0, -90.0),
807 Vector3::new(90.0, -90.0, -90.0),
808 Vector3::new(-90.0, -90.0, -90.0),
809 Vector3::new(-90.0, 90.0, 90.0),
810 Vector3::new(90.0, 90.0, 90.0),
811 Vector3::new(90.0, -90.0, 90.0),
812 Vector3::new(20.0, 150.0, 30.0),
813 Vector3::new(20.0, -150.0, 30.0),
814 Vector3::new(-120.0, -150.0, 30.0),
815 Vector3::new(-120.0, -150.0, -130.0),
816 Vector3::new(120.0, -150.0, -130.0),
817 Vector3::new(120.0, 150.0, -130.0),
818 Vector3::new(120.0, 150.0, 130.0),
819 ];
820
821 for order in euler_order_to_test.iter() {
822 for vector in vectors_to_test.iter() {
823 test_rotation(*vector, *order);
824 }
825 }
826 }
827
828 #[test]
830 fn basis_finite_number_test() {
831 let x: Vector3 = Vector3::new(0.0, 1.0, 2.0);
832 let infinite: Vector3 = Vector3::new(real::NAN, real::NAN, real::NAN);
833
834 assert!(
835 Basis::from_cols(x, x, x).is_finite(),
836 "Basis with all components finite should be finite"
837 );
838
839 assert!(
840 !Basis::from_cols(infinite, x, x).is_finite(),
841 "Basis with one component infinite should not be finite."
842 );
843 assert!(
844 !Basis::from_cols(x, infinite, x).is_finite(),
845 "Basis with one component infinite should not be finite."
846 );
847 assert!(
848 !Basis::from_cols(x, x, infinite).is_finite(),
849 "Basis with one component infinite should not be finite."
850 );
851
852 assert!(
853 !Basis::from_cols(infinite, infinite, x).is_finite(),
854 "Basis with two components infinite should not be finite."
855 );
856 assert!(
857 !Basis::from_cols(infinite, x, infinite).is_finite(),
858 "Basis with two components infinite should not be finite."
859 );
860 assert!(
861 !Basis::from_cols(x, infinite, infinite).is_finite(),
862 "Basis with two components infinite should not be finite."
863 );
864
865 assert!(
866 !Basis::from_cols(infinite, infinite, infinite).is_finite(),
867 "Basis with three components infinite should not be finite."
868 );
869 }
870
871 #[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
872 #[test]
873 fn serde_roundtrip() {
874 let basis = Basis::IDENTITY;
875 let expected_json = "{\"rows\":[{\"x\":1.0,\"y\":0.0,\"z\":0.0},{\"x\":0.0,\"y\":1.0,\"z\":0.0},{\"x\":0.0,\"y\":0.0,\"z\":1.0}]}";
876
877 crate::builtin::test_utils::roundtrip(&basis, expected_json);
878 }
879}