From bd0e70727248d9784acb9782290c1c6a90b96542 Mon Sep 17 00:00:00 2001 From: Cary Phillips Date: Thu, 29 Jun 2023 16:48:29 -0700 Subject: [PATCH] Improve test coverage for Frustum, Quat, Euler, Shear (#328) * Increase test coverage for Euler, Frustum, Quat, Shear - Added python symbols "EULER_XYZLayout" and "EULER_IJKLayout" to complete API for Euler constructors - Added some comments indicating what looks to be dead, unreachable code. I left the code in place because I'm not 100% sure. Signed-off-by: Cary Phillips * Fix Quat.identity() Signed-off-by: Cary Phillips * Revert inadvertent whitespace-only changes Signed-off-by: Cary Phillips --------- Signed-off-by: Cary Phillips --- src/Imath/ImathFrustum.h | 2 + src/Imath/ImathMatrix.h | 2 +- src/ImathTest/testFrustum.cpp | 183 +++++++++- src/ImathTest/testQuat.cpp | 31 ++ src/ImathTest/testShear.cpp | 45 +++ src/python/PyImath/PyImathEuler.cpp | 47 ++- src/python/PyImath/PyImathFrustum.cpp | 5 + src/python/PyImath/PyImathQuat.cpp | 10 +- src/python/PyImath/PyImathShear.cpp | 1 + src/python/PyImath/imathmodule.cpp | 2 + src/python/PyImathTest/pyImathTest.in | 488 +++++++++++++++++++++++++- 11 files changed, 794 insertions(+), 22 deletions(-) diff --git a/src/Imath/ImathFrustum.h b/src/Imath/ImathFrustum.h index 5935af13..90337467 100644 --- a/src/Imath/ImathFrustum.h +++ b/src/Imath/ImathFrustum.h @@ -560,6 +560,7 @@ Frustum::projectionMatrixExc () const abs (farTimesNear) > std::numeric_limits::max () * abs (farMinusNear)) { + // impossible condition: already tested above throw std::domain_error ("Bad viewing frustum: " "projection matrix cannot be computed."); } @@ -575,6 +576,7 @@ Frustum::projectionMatrixExc () const abs (twoTimesNear) > std::numeric_limits::max () * abs (topMinusBottom))) { + // impossible condition: already tested above throw std::domain_error ("Bad viewing frustum: " "projection matrix cannot be computed."); } diff --git a/src/Imath/ImathMatrix.h b/src/Imath/ImathMatrix.h index 3f2e4c36..15625f6a 100644 --- a/src/Imath/ImathMatrix.h +++ b/src/Imath/ImathMatrix.h @@ -680,7 +680,7 @@ template class IMATH_EXPORT_TEMPLATE_TYPE Matrix33 /// Trace IMATH_HOSTDEVICE constexpr T trace() const IMATH_NOEXCEPT; - /// Set matrix to rotation by r (in radians) + /// Set matrix to rotation by r (in radians, assumed to be a scalar) around (0, 0, 1) /// @return const referenced to this template IMATH_HOSTDEVICE const Matrix33& setRotation (S r) IMATH_NOEXCEPT; diff --git a/src/ImathTest/testFrustum.cpp b/src/ImathTest/testFrustum.cpp index c3416b0e..0b0f6c76 100644 --- a/src/ImathTest/testFrustum.cpp +++ b/src/ImathTest/testFrustum.cpp @@ -172,6 +172,8 @@ testFrustum () { cout << "Testing functions in ImathFrustum.h"; + bool caught; + cout << "\nperspective "; float n = 1.7f; @@ -248,13 +250,85 @@ testFrustum () IMATH_INTERNAL_NAMESPACE::abs (m[3][3]) < 1e-6f); cout << "3"; + IMATH_INTERNAL_NAMESPACE::Frustum badFrustum; + + badFrustum.set(n, f, l, r, t, t, false); + + caught = false; + try + { + badFrustum.projectionMatrixExc(); + assert(!"projectionMatrixExc() didn't throw with bad frustum"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); + cout << "4"; + + badFrustum.set(n, n, l, r, b, t, false); + + caught = false; + try + { + badFrustum.projectionMatrixExc(); + assert(!"projectionMatrixExc() didn't throw with bad frustum"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); + cout << "5"; + + badFrustum.set(n, n, l, r, b, t, false); + + caught = false; + try + { + badFrustum.projectionMatrixExc(); + assert(!"projectionMatrixExc() didn't throw with bad frustum"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); + cout << "6"; + + badFrustum.set(n, f, l, r, t, t, true); + + caught = false; + try + { + badFrustum.projectionMatrixExc(); + assert(!"projectionMatrixExc() didn't throw with bad frustum"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); + cout << "7"; + + caught = false; + try + { + badFrustum.aspectExc(); + assert (!"aspectExc didn't throw when top-bottom==0"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); + cout << "8"; + cout << "\nplanes "; testFrustumPlanes (frustum); cout << "\nexceptions "; - IMATH_INTERNAL_NAMESPACE::Frustum badFrustum; - - bool caught; badFrustum.set (n, n, l, r, t, b, false); caught = false; @@ -373,7 +447,110 @@ testFrustum () assert (f1.screenRadius (v3, one) == f1.screenRadiusExc (v3, one)); assert (f1.projectPointToScreen (v3) == f1.projectPointToScreenExc (v3)); + f1.set (n, f, zero, one, one); + f2.setExc (n, f, zero, one, one); + + assert (f1 == f2); + + assert ( + f1.ZToDepth (zMin, zMin, zMax) == f1.ZToDepthExc (zMin, zMin, zMax)); + assert (f1.normalizedZToDepth (float(zMin)) == f1.normalizedZToDepthExc (float(zMin))); + assert (f1.DepthToZ (n, zMin, zMax) == f1.DepthToZExc (n, zMin, zMax)); + assert (f1.worldRadius (v3, one) == f1.worldRadiusExc (v3, one)); + assert (f1.screenRadius (v3, one) == f1.screenRadiusExc (v3, one)); + assert (f1.projectPointToScreen (v3) == f1.projectPointToScreenExc (v3)); + + f1.setOrthographic (true); + assert ( + f1.ZToDepth (zMin, zMin, zMax) == f1.ZToDepthExc (zMin, zMin, zMax)); + assert (f1.normalizedZToDepth (float(zMin)) == f1.normalizedZToDepthExc (float(zMin))); + assert (f1.DepthToZ (n, zMin, zMax) == f1.DepthToZExc (n, zMin, zMax)); + assert (f1.worldRadius (v3, one) == f1.worldRadiusExc (v3, one)); + assert (f1.screenRadius (v3, one) == f1.screenRadiusExc (v3, one)); + assert (f1.projectPointToScreen (v3) == f1.projectPointToScreenExc (v3)); + + f1.set(n, n, l, l, b, b, true); + caught = false; + try + { + f1.projectPointToScreenExc (v3); + assert (!"projectPointToScreenExc failed to throw with bad frustum"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); + + caught = false; + try + { + f1.ZToDepthExc(100, 100, 100); + assert (!"ZToDepthExc failed to throw with zmax=zmin"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); + + caught = false; + try + { + f1.DepthToZExc (100, 100, 100); + assert (!"normalizedZToDepth failed to throw with bad frustum"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); + + f1.setOrthographic(false); + + caught = false; + try + { + f1.DepthToZExc (100, 100, 100); + assert (!"DepthToZExc failed to throw with bad frustum"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); + + f1.set(n, f, l, r, b, t, true); + caught = false; + try + { + v3.z = 1.0 / std::numeric_limits::max(); + f1.screenRadiusExc (v3, 1.0); + assert (!"screenRadiusExc failed to throw with bad p"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); + cout << "\npassed noexcept equality verification"; + + float fovx = 1.0; + float fovy = 1.0; + float aspect = 1.0; + + caught = false; + try + { + badFrustum.setExc (n, f, fovx, fovy, aspect); + assert (!"nfovx and fovy can't both be 0.0"); + } + catch (std::domain_error&) + { + caught = true; + } + assert (caught); cout << "\nok\n\n"; } diff --git a/src/ImathTest/testQuat.cpp b/src/ImathTest/testQuat.cpp index 274820bc..4211e5f3 100644 --- a/src/ImathTest/testQuat.cpp +++ b/src/ImathTest/testQuat.cpp @@ -50,6 +50,32 @@ testQuatT () assert (q1.r == 6 && q1.v == Vec3 (7, 8, 9)); } + // m * q + { + Matrix33 m; + m.setRotation(T(M_PI_2)); + Quat q; + q.setAxisAngle (Vec3 (0, 0, 1), T(M_PI_2)); + Matrix33 mp = m * q; + Matrix33 r (-9.999999e-01f, 1.589326e-08f, 0.000000e+00f, + -1.589326e-08f, -9.999999e-01f, 0.000000e+00f, + 0.000000e+00f, 0.000000e+00f, 1.000000e+00); + assert(mp.equalWithAbsError(r, 1e-5)); + } + + // q * m + { + Matrix33 m; + m.setRotation(T(M_PI_2)); + Quat q; + q.setAxisAngle (Vec3 (0, 0, 1), T(M_PI_2)); + Matrix33 mp = m * q; + Matrix33 r (-9.999999e-01f, 1.589326e-08f, 0.000000e+00f, + -1.589326e-08f, -9.999999e-01f, 0.000000e+00f, + 0.000000e+00f, 0.000000e+00f, 1.000000e+00); + assert(mp.equalWithAbsError(r, 1e-5)); + } + // // invert(), inverse() // @@ -78,6 +104,11 @@ testQuatT () q.normalize (); assert (q == Quat (0, 0, 1, 0)); + + q = Quat (0, Vec3 (0, 0, 0)); + q.normalize(); + assert (q.r == 1 && q.v == Vec3(0,0,0)); + } // diff --git a/src/ImathTest/testShear.cpp b/src/ImathTest/testShear.cpp index 110488f8..c8f16ee7 100644 --- a/src/ImathTest/testShear.cpp +++ b/src/ImathTest/testShear.cpp @@ -23,6 +23,15 @@ testShear () { cout << "Testing functions in ImathShear.h" << endl; + assert (IMATH_INTERNAL_NAMESPACE::Shear6f::baseTypeLowest() == + std::numeric_limits::lowest()); + assert (IMATH_INTERNAL_NAMESPACE::Shear6f::baseTypeMax() == + std::numeric_limits::max()); + assert (IMATH_INTERNAL_NAMESPACE::Shear6f::baseTypeSmallest() == + std::numeric_limits::min()); + assert (IMATH_INTERNAL_NAMESPACE::Shear6f::baseTypeEpsilon() == + std::numeric_limits::epsilon()); + cout << "Imath::Shear6 constructors" << endl; const float epsilon = std::numeric_limits::epsilon (); @@ -195,5 +204,41 @@ testShear () std::fabs ((X.zx / Y.zx) - tmp.zx) <= 1e-5f && std::fabs ((X.zy / Y.zy) - tmp.zy) <= 1e-5f); + + IMATH_INTERNAL_NAMESPACE::Shear6f s (1.0, 2.0, 3.0, 4.0, 5.0, 6.0); + tmp.setValue(s.xy, s.xz, s.yz, s.yx, s.zx, s.zy); + assert (tmp.xy == s.xy && + tmp.xz == s.xz && + tmp.yz == s.yz && + tmp.yx == s.yx && + tmp.zx == s.zx && + tmp.zy == s.zy); + s = IMATH_INTERNAL_NAMESPACE::Shear6f(); + tmp.getValue(s.xy, s.xz, s.yz, s.yx, s.zx, s.zy); + assert (tmp.xy == s.xy && + tmp.xz == s.xz && + tmp.yz == s.yz && + tmp.yx == s.yx && + tmp.zx == s.zx && + tmp.zy == s.zy); + + s = IMATH_INTERNAL_NAMESPACE::Shear6f(); + s.setValue(tmp); + assert (tmp.xy == s.xy && + tmp.xz == s.xz && + tmp.yz == s.yz && + tmp.yx == s.yx && + tmp.zx == s.zx && + tmp.zy == s.zy); + + s = IMATH_INTERNAL_NAMESPACE::Shear6f(); + tmp.getValue(s); + assert (tmp.xy == s.xy && + tmp.xz == s.xz && + tmp.yz == s.yz && + tmp.yx == s.yx && + tmp.zx == s.zx && + tmp.zy == s.zy); + cout << "ok\n" << endl; } diff --git a/src/python/PyImath/PyImathEuler.cpp b/src/python/PyImath/PyImathEuler.cpp index 2de636f2..ff1fceae 100644 --- a/src/python/PyImath/PyImathEuler.cpp +++ b/src/python/PyImath/PyImathEuler.cpp @@ -175,7 +175,7 @@ setXYZTuple(Euler &euler, const tuple &t) euler.setXYZVector(v); } else - throw std::invalid_argument ("Color3 expects tuple of length 3"); + throw std::invalid_argument ("Euler expects tuple of length 3"); } // needed to convert Eulerf::Order to Euler::Order @@ -288,6 +288,15 @@ static typename Euler::Order interpretOrder(typename IMATH_NAMESPACE::Eulerf: return o; } +// needed to convert Eulerf::InputLayout to Euler::InputLayout +template +static typename Euler::InputLayout interpretInputLayout(typename IMATH_NAMESPACE::Eulerf::InputLayout layout) +{ + if (layout == IMATH_NAMESPACE::Eulerf::XYZLayout) + return Euler::XYZLayout; + return Euler::IJKLayout; +} + // needed to convert Eulerf::Axis to Euler::Axis template static typename Euler::Axis interpretAxis(typename IMATH_NAMESPACE::Eulerf::Axis axis) @@ -302,10 +311,13 @@ static typename Euler::Axis interpretAxis(typename IMATH_NAMESPACE::Eulerf::A template static Euler * -eulerConstructor1(const Vec3 &v, typename IMATH_NAMESPACE::Eulerf::Order order) +eulerConstructor1(const Vec3 &v, + typename IMATH_NAMESPACE::Eulerf::Order order, + typename IMATH_NAMESPACE::Eulerf::InputLayout layout = IMATH_NAMESPACE::Eulerf::IJKLayout) { typename Euler::Order o = interpretOrder(order); - return new Euler(v, o); + typename Euler::InputLayout l = interpretInputLayout(layout); + return new Euler(v, o, l); } template @@ -323,12 +335,35 @@ eulerConstructor1b(const Vec3 &v, int iorder) return new Euler(v, o); } +// + template static Euler * -eulerConstructor2(T i, T j, T k, typename IMATH_NAMESPACE::Eulerf::Order order) +eulerConstructor1d(const Euler& e, int iorder) +{ + typename Euler::Order o = typename Euler::Order (iorder); + return new Euler(e, o); +} + +template +static Euler * +eulerConstructor1e(const Euler& e, int iorder, int layout) +{ + typename Euler::Order o = typename Euler::Order (iorder); + typename Euler::InputLayout l = typename Euler::InputLayout (layout); + return new Euler(e, o, l); +} + + +template +static Euler * +eulerConstructor2(T i, T j, T k, + typename IMATH_NAMESPACE::Eulerf::Order order, + typename IMATH_NAMESPACE::Eulerf::InputLayout layout = IMATH_NAMESPACE::Eulerf::IJKLayout) { typename Euler::Order o = interpretOrder(order); - return new Euler(i, j, k, o); + typename Euler::InputLayout l = interpretInputLayout(layout); + return new Euler(i, j, k, o, l); } template @@ -549,6 +584,8 @@ register_Euler() .def("__init__", make_constructor(eulerConstructor1)) .def("__init__", make_constructor(eulerConstructor1a)) .def("__init__", make_constructor(eulerConstructor1b)) + .def("__init__", make_constructor(eulerConstructor1d)) + .def("__init__", make_constructor(eulerConstructor1e)) .def("__init__", make_constructor(eulerConstructor2)) .def("__init__", make_constructor(eulerConstructor2a)) .def("__init__", make_constructor(eulerConstructor2b)) diff --git a/src/python/PyImath/PyImathFrustum.cpp b/src/python/PyImath/PyImathFrustum.cpp index adec1179..7b46a80a 100644 --- a/src/python/PyImath/PyImathFrustum.cpp +++ b/src/python/PyImath/PyImathFrustum.cpp @@ -112,6 +112,7 @@ projectScreenToRayTuple(Frustum &f, const tuple &t) } +// dead code? template static Vec2 projectPointToScreen (Frustum &f, const Vec3 &p) @@ -120,6 +121,7 @@ projectPointToScreen (Frustum &f, const Vec3 &p) return f.projectPointToScreen(p); } +// dead code? template static Vec2 projectPointToScreenTuple(Frustum &f, const tuple &t) @@ -224,6 +226,7 @@ screenRadiusTuple(Frustum &f, const tuple &t, T radius) throw std::invalid_argument ("screenRadius expects tuple of length 3"); } +// dead code? template static void planes1(Frustum &f, Plane3 *p) @@ -232,6 +235,7 @@ planes1(Frustum &f, Plane3 *p) f.planes(p); } +// dead code? template static void planes2(Frustum &f, Plane3 *p, const Matrix44 &m) @@ -466,6 +470,7 @@ register_FrustumTest() { const char *name = FrustumTestName::value; + // dead code? no wrapping for Sphere3 bool (FrustumTest::*isVisibleS)(const Sphere3 &) const = &FrustumTest::isVisible; bool (FrustumTest::*isVisibleB)(const Box > &) const = &FrustumTest::isVisible; bool (FrustumTest::*isVisibleV)(const Vec3 &) const = &FrustumTest::isVisible; diff --git a/src/python/PyImath/PyImathQuat.cpp b/src/python/PyImath/PyImathQuat.cpp index 413acc53..b0b2937e 100644 --- a/src/python/PyImath/PyImathQuat.cpp +++ b/src/python/PyImath/PyImathQuat.cpp @@ -79,6 +79,14 @@ invert(Quat &quat) return quat.invert(); } +template +static Quat +identity(Quat &quat) +{ + MATH_EXC_ON; + return Quat(); +} + template static Quat inverse(Quat &quat) @@ -430,7 +438,7 @@ register_Quat() .def("__init__", make_constructor(quatConstructor1)) .def("__init__", make_constructor(quatConstructor2)) .def("__init__", make_constructor(quatConstructor3)) - .def("identity",&Quat::identity) + .def("identity",&identity, "q.identity() -- return an identity quaternion\n") .def("invert",&invert,return_internal_reference<>(), "q.invert() -- inverts quaternion q\n" "(modifying q); returns q") diff --git a/src/python/PyImath/PyImathShear.cpp b/src/python/PyImath/PyImathShear.cpp index ca6b7900..0092cd1e 100644 --- a/src/python/PyImath/PyImathShear.cpp +++ b/src/python/PyImath/PyImathShear.cpp @@ -217,6 +217,7 @@ subtract1(Shear6 &v, tuple t) return w; } +// obsolete? duplicate with subtract1 template static Shear6 subtract2(Shear6 &v, tuple t) diff --git a/src/python/PyImath/imathmodule.cpp b/src/python/PyImath/imathmodule.cpp index bc68fbe8..21a60106 100644 --- a/src/python/PyImath/imathmodule.cpp +++ b/src/python/PyImath/imathmodule.cpp @@ -625,6 +625,8 @@ BOOST_PYTHON_MODULE(imath) scope().attr("EULER_X_AXIS") = IMATH_NAMESPACE::Eulerf::X; scope().attr("EULER_Y_AXIS") = IMATH_NAMESPACE::Eulerf::Y; scope().attr("EULER_Z_AXIS") = IMATH_NAMESPACE::Eulerf::Z; + scope().attr("EULER_IJKLayout") = IMATH_NAMESPACE::Eulerf::IJKLayout; + scope().attr("EULER_XYZLayout") = IMATH_NAMESPACE::Eulerf::XYZLayout; scope().attr("INT_MIN") = std::numeric_limits::min(); scope().attr("INT_MAX") = std::numeric_limits::max(); diff --git a/src/python/PyImathTest/pyImathTest.in b/src/python/PyImathTest/pyImathTest.in index 339d15ee..d7904366 100644 --- a/src/python/PyImathTest/pyImathTest.in +++ b/src/python/PyImathTest/pyImathTest.in @@ -4379,6 +4379,13 @@ def testShear6x (Shear): assert h[0] == 0 and h[1] == 1 and h[2] == 2 and \ h[3] == 3 and h[4] == 4 and h[5] == 5 + try: + h = Shear((0, 1, 2, 3, 4, 5, 6)) + except: + pass + else: + assert False + h = Shear() h.setValue(0, 1, 2, 3, 4, 5) assert h[0] == 0 and h[1] == 1 and h[2] == 2 and \ @@ -4389,6 +4396,10 @@ def testShear6x (Shear): h = Shear(1/9., 2/9., 3/9., 4/9., 5/9., 6/9.) assert h == eval(repr(h)) + s = eval(str(h)) + for i in range(6): + assert equalWithAbsError(h[i], s[i], 1e-5) + # Sequence length. h = Shear() @@ -4483,10 +4494,24 @@ def testShear6x (Shear): assert h1 + 1 == Shear(11, 21, 31, -9, -19, -29) assert 1 + h1 == h1 + 1 + h3 = Shear(h1) + h3 += h2 + assert h3 == Shear(40, 60, 80, -40, -60, -80) + + h3 -= h2 + assert h3 == h1 + # (with the switch to python2, we now allow ops between vectors and tuples) assert h1 + (1, 2, 3, 4, 5, 6) == Shear(11, 22, 33, -6, -15, -24) assert (1, 2, 3, 4, 5, 6) + h1 == h1 + (1, 2, 3, 4, 5, 6) + try: + h1 + (1, 2, 3, 4, 5, 6, 7) + except: + pass + else: + assert False + # Subtraction and negation. h1 = Shear(10, 20, 30, -10, -20, -30) @@ -4500,6 +4525,13 @@ def testShear6x (Shear): assert h1 - (1, 2, 3, 4, 5, 6) == Shear(9, 18, 27, -14, -25, -36) assert (1, 2, 3, 4, 5, 6) - h1 == - (h1 - (1, 2, 3, 4, 5, 6)) + try: + h1 - (1, 2, 3, 4, 5, 6, 7) + except: + pass + else: + assert False + assert h1.negate() == Shear(-10, -20, -30, 10, 20, 30) # Multiplication. @@ -4512,9 +4544,30 @@ def testShear6x (Shear): assert 2 * h1 == Shear(2, 4, 6, -2, -4, -6) assert h1 * 2 == 2 * h1 + h3 = Shear(h1) + h3 *= h2 + assert h3 == Shear(3, 8, 15, 3, 8, 15) + + h3 /= h2 + assert h3 == h1 + + h3 = Shear(h1) + h3 *= 2 + assert h3 == h1 * 2 + + h3 /= 2 + assert h3 == h1 + # (with the switch to python2, we now allow ops between vectors and tuples) assert h1 * (1, 2, 3, 4, 5, 6) == Shear(1, 4, 9, -4, -10, -18) - assert (1, 2, 3, 4, 5, 6) * h1 == h1 * (1, 2, 3, 4, 5, 6) + assert (1, 2, 3, 4, 5, 6) * h1 == Shear(1, 4, 9, -4, -10, -18) + + try: + h1 * (1, 2, 3, 4, 5, 6, 7) + except: + pass + else: + assert False # Division. @@ -4527,7 +4580,45 @@ def testShear6x (Shear): # (with the switch to python2, we now allow ops between vectors and tuples) assert h1 / (1, 2, 4, -1, -2, -4) == Shear(10, 10, 10, 10, 10, 10) - assert Shear(50, 40, 80, -50, -40, -80) / h1 == Shear(5, 2, 2, 5, 2, 2) + assert (50, 40, 80, -50, -40, -80) / h1 == Shear(5, 2, 2, 5, 2, 2) + + assert 1.0 / Shear(1,2,3,4,5,6) == Shear(1.0/1, 1.0/2, 1.0/3, 1.0/4, 1.0/5, 1.0/6) + + try: + h = 2.0 / Shear(0, 1, 2, 3, 4, 5, 6) + except: + pass + else: + assert False + + + try: + h = h1 / (1, 2, 3, 4, 5, 6, 7) + except: + pass + else: + assert False + + try: + h = (1, 2, 3, 4, 5, 6, 7) / h1 + except: + pass + else: + assert False + + try: + h = h1 / (0, 0, 0, 0, 0, 0) + except: + pass + else: + assert False + + try: + h = (1,2,3,4,5,6) / Shear(0,1,2,3,4,5) + except: + pass + else: + assert False print ("ok") @@ -6878,7 +6969,7 @@ testList.append (('testBoxConversions',testBoxConversions)) # ------------------------------------------------------------------------- # Tests for Quatx -def testQuatx (Quat, Vec, M33, M44): +def testQuatx (Quat, Vec, M33, M44, Euler, VecArray): # constructors, r(), v() e = 4 * Vec.baseTypeEpsilon() @@ -6895,12 +6986,55 @@ def testQuatx (Quat, Vec, M33, M44): q1 = Quat (q) assert q1.r() == 6 and q1.v() == Vec (7, 8, 9) + q1 = q.identity() + assert q1.r() == 1 and q1.v() == Vec (0, 0, 0) + + u = Euler (Vec (1, 2, 3), EULER_XYZ) + q = Quat(u) + assert equalWithAbsError(q.r(), 0.4359528422355652, 1e-5) and q.v().equalWithAbsError(Vec(-0.718287, 0.310622, 0.444435), 1e-5) + + m = M33() + q = Quat(m) + assert equalWithAbsError(q.r(), 1.0, 1e-5) and q.v().equalWithAbsError(Vec(0, 0, 0), 1e-5) + + m = M44() + q = Quat(m) + assert equalWithAbsError(q.r(), 1.0, 1e-5) and q.v().equalWithAbsError(Vec(0, 0, 0), 1e-5) + + # setR(), setV() q.setR (1) q.setV (Vec (2, 3, 4)) assert q.r() == 1 and q.v() == Vec (2, 3, 4) + # operator *= + q1 = Quat(q) + q1 *= 2.0 + assert q1.r() == q.r() * 2.0 and q1.v() == q.v() * 2.0 + + q1 *= q + assert equalWithAbsError(q1.r(),-56,1e-5) and q1.v().equalWithAbsError (Vec (8,12,16), 1e-5) + + # operator /= + q1 = Quat(q) + q1 /= 2 + assert q1.r() == q.r() / 2 and q1.v() == q.v() / 2 + + # operator /= + q1 = Quat(q) + q1 /= q + assert q1.r() == 1 and q1.v().equalWithAbsError (Vec (0, 0, 0), 1e-5) + + # operator += + q1 = Quat(q) + q1 += q + assert q1.r() == q.r() * 2 and q1.v() == q.v() * 2 + + # operator -= + q1 -= q + assert q1.r() == q.r() and q1.v() == q.v() + # invert(), inverse() q = Quat (1, 0, 0, 1) @@ -6920,6 +7054,10 @@ def testQuatx (Quat, Vec, M33, M44): q.normalize() assert q == Quat (0, 0, 1, 0) + q = Quat(0, 0, 0, 0) + q.normalize() + assert q.r() == 1 and q.v() == Vec (0, 0, 0) + # length() q = Quat (3, 0, 4, 0) @@ -6941,6 +7079,31 @@ def testQuatx (Quat, Vec, M33, M44): assert v.equalWithAbsError (Vec (0, 0, 1), e) assert equal(a, pi/2, e) + q.setRotation (Vec (0, 1, 0), Vec (1, 0, 0)) + v = q.axis() + a = q.angle() + assert v.equalWithAbsError (Vec (0, 0, -1), e) + assert equal(a, pi/2, e) + + q.setRotation (Vec (0, 1, 0), Vec (0, -1, 1)) + v = q.axis() + a = q.angle() + assert v.equalWithAbsError (Vec (1, 0, 0), e) + assert equal(a, 3*pi/4.0, e) + + q.setRotation (Vec (0, 1, 0), Vec (0, -1, 0)) + v = q.axis() + a = q.angle() + assert v.equalWithAbsError (Vec (0, 0, -1), e) + assert equal(a, pi, e) + + # rotateVector + + v = Vec(0, 0, 1) + q.setAxisAngle (Vec (0, 1, 0), pi/2) + vp = q.rotateVector(v) + assert vp.equalWithAbsError (Vec (1, 0, 0), e) + # slerp() q = Quat() @@ -6959,6 +7122,15 @@ def testQuatx (Quat, Vec, M33, M44): assert r.v().equalWithAbsError (Vec (sqrt(2) / 2, 0, 0), e) + # slerpShortestArg() + + q = Quat(0, 0, 1, 0) + p = Quat(0, 0, -1, 1) + + r = p.slerpShortestArc (q, 0.5) + assert equal (r.r(), 0, e) + assert r.v().equalWithAbsError (Vec (0, -0.894427, 0.447214), 1e-5) + # toMatrix33(), toMatrix44() q.setRotation (Vec (1, 0, 0), Vec (0, 1, 0)) @@ -6996,22 +7168,62 @@ def testQuatx (Quat, Vec, M33, M44): assert Quat (1, 2, 3, 4) ^ Quat (2, 2, 2, 2) == 20 + m = M33() + m1 = q * m + assert m1.equalWithAbsError(M33((0, 1, 0), (-1, 0, 0), (0, 0, 1)), 1e-5) + m1 = m * q + assert m1.equalWithAbsError(M33((0, 1, 0), (-1, 0, 0), (0, 0, 1)), 1e-5) + + v = Vec(1,0,0) * q1 + assert v.equalWithAbsError(V3f(-49, 20, 10), 1e-5) + + a = VecArray(3) + a[0] = Vec(11,17,3).normalized() + a[1] = Vec(7,19,31).normalized() + a[2] = Vec(23,5,13).normalized() + a1 = a * q + assert a[0].equalWithAbsError(Vec(0.537385, 0.830504, 0.14656), 1e-5) + assert a[1].equalWithAbsError(Vec(0.189051, 0.513139, 0.837227), 1e-5) + assert a[2].equalWithAbsError(Vec(0.855379, 0.185952, 0.483475), 1e-5) + # repr q = Quat (1/9., 2/9., 3/9., 4/9.) assert q == eval (repr (q)) + q1 = eval (str (q)) + assert equalWithAbsError(q1.r(), q.r(), 1e-5) and q1.v().equalWithAbsError (q.v(), 1e-5) # extract() m1 = M44 () vFrom = Vec (1, 0, 0) vTo = Vec (0, 1, 1) + m1.rotationMatrix(vFrom, vTo) q = Quat () q.extract(m1) m2 = q.toMatrix44() assert m2.equalWithAbsError(m1, 2*m1.baseTypeEpsilon()) + # log/exp + q = Quat(1, 0, 0, 0) + l = q.log() + assert l == Quat(0,0,0,0) + x = q.exp() + assert x == Quat(1,0,0,0) + + q.setAxisAngle (Vec (0, 0, 1), pi/2) + l = q.log() + assert l.r() == 0 and l.v().equalWithAbsError (Vec (0, 0, 0.785398), 1e-5) + x = q.exp() + assert equalWithAbsError (x.r(), 0.760245, 1e-5) and x.v().equalWithAbsError (Vec (0, 0, 0.649637), 1e-5) + + q = Quat(0, 0, 0, 0) + l = q.log() + assert l == Quat(0,0,0,0) + x = q.exp() + assert x == Quat(1,0,0,0) + print ("ok") return @@ -7033,9 +7245,9 @@ def testQuatConversions (): def testQuat(): print ("Quatf") - testQuatx (Quatf, V3f, M33f, M44f) + testQuatx (Quatf, V3f, M33f, M44f, Eulerf, V3fArray) print ("Quatd") - testQuatx (Quatd, V3d, M33d, M44d) + testQuatx (Quatd, V3d, M33d, M44d, Eulerd, V3dArray) print ("conversions") testQuatConversions() @@ -7046,7 +7258,7 @@ testList.append (('testQuat',testQuat)) # ------------------------------------------------------------------------- # Tests for Eulerx -def testEulerx (Euler, Vec, M33, M44): +def testEulerx (Euler, Vec, M33, M44, Quat): # constructors, toXYZVector(), order() @@ -7062,12 +7274,18 @@ def testEulerx (Euler, Vec, M33, M44): e1 = Euler (e) assert e1.toXYZVector() == Vec (3, 2, 1) and e1.order() == EULER_ZYX + e1 = Euler (e, EULER_XYZ) + assert e1.toXYZVector().equalWithAbsError(Vec(-1.02689, -0.649926, -1.85712), 1e-5) and e1.order() == EULER_XYZ + e = Euler (4, 5, 6) assert e.toXYZVector() == Vec (4, 5, 6) and e.order() == EULER_XYZ e = Euler (4, 5, 6, EULER_ZXY) assert e.toXYZVector() == Vec (5, 6, 4) and e.order() == EULER_ZXY + e = Euler (4, 5, 6, EULER_ZXY, EULER_XYZLayout) + assert e.toXYZVector() == Vec (4, 5, 6) and e.order() == EULER_ZXY + e = Euler (M33()) assert e.toXYZVector() == Vec (0, 0, 0) and e.order() == EULER_XYZ @@ -7080,15 +7298,70 @@ def testEulerx (Euler, Vec, M33, M44): e = Euler (M44(), EULER_ZYX) assert e.toXYZVector() == Vec (0, 0, 0) and e.order() == EULER_ZYX + e = Eulerf (V3f (1, 2, 3), EULER_XYZ) + e1 = Eulerf (e, EULER_YZX) + + assert e1.toXYZVector().equalWithAbsError(Vec (-2.45463, -0.72857, 0.985843), 1e-5) and e1.order() == EULER_YZX + + e = Euler (Vec (1, 2, 3), EULER_ZYX, EULER_XYZLayout) + assert e.toXYZVector() == Vec (1, 2, 3) and e.order() == EULER_ZYX + + e1 = Euler (e, EULER_XYZ, EULER_XYZLayout) + assert e1.toXYZVector() == Vec (3, 2, 1) and e1.order() == EULER_XYZ + + e = Eulerf (Quatf(), EULER_XYZ) + assert e.toXYZVector().equalWithAbsError(Vec(0,0,0),1e-5) and e.order() == EULER_XYZ + + orders = [ + EULER_XYZ, + EULER_XZY, + EULER_YZX, + EULER_YXZ, + EULER_ZXY, + EULER_ZYX, + EULER_XZX, + EULER_XYX, + EULER_YXY, + EULER_YZY, + EULER_ZYZ, + EULER_ZXZ, + EULER_XYZr, + EULER_XZYr, + EULER_YZXr, + EULER_YXZr, + EULER_ZXYr, + EULER_ZYXr, + EULER_XZXr, + EULER_XYXr, + EULER_YXYr, + EULER_YZYr, + EULER_ZYZr, + EULER_ZXZr + ] + + # repr/str for all orders + for o in orders: + e = Euler (1, 2, 3, o) + assert e.order() == o + + assert e == eval(repr(e)) + e1 = eval (str (e)) + assert (e1.order() == e.order() and + equalWithAbsError(e1.x, e.x, 1e-5) and + equalWithAbsError(e1.y, e.y, 1e-5) and + equalWithAbsError(e1.z, e.z, 1e-5)) + # comparison e = Euler (1, 2, 3, EULER_XYZ) e1 = e assert e1 == e + assert not (e1 != e) e1 = Euler (1, 2, 3, EULER_XZY) assert e1 != e + assert not (e1 == e) e1 = Euler (1, 1, 3, EULER_XYZ) assert e1 != e @@ -7100,6 +7373,21 @@ def testEulerx (Euler, Vec, M33, M44): e.setOrder (EULER_ZYX) assert e.order() == EULER_ZYX and e.toXYZVector() == Vec (9, 8, 7) + e = Euler() + e.setXYZVector ((7, 8, 9)) + e.setOrder (EULER_ZYX) + assert e.order() == EULER_ZYX and e.toXYZVector() == Vec (9, 8, 7) + + e = Euler() + try: + e.setXYZVector ((7, 8, 9, 10)) + except: + pass + else: + assert 0 + + e = Euler() + e.setXYZVector (Vec (7, 8, 9)) e.setOrder (EULER_XYZ) assert e.order() == EULER_XYZ and e.toXYZVector() == Vec (7, 8, 9) @@ -7149,6 +7437,16 @@ def testEulerx (Euler, Vec, M33, M44): assert v.equalWithAbsError (Vec (0, 0, pi/2), v.baseTypeEpsilon()) + q = Quat (1, 0, 0, 1) + eq = Euler() + eq.extract(q) + assert eq.toXYZVector().equalWithAbsError(Vec(0, -0, 2.03444), 1e-5) and eq.order() == EULER_XYZ + + eq = Euler(EULER_XZX) + m33 = M33() + eq.extract(m33) + assert eq.toXYZVector().equalWithAbsError(Vec(0, 0, 0), 1e-5) and eq.order() == EULER_XZX + # toMatrix33(), toMatrix44() m = e.toMatrix33(); @@ -7221,16 +7519,55 @@ def testEulerConversions (): print ("ok") return +def testEulerArrays(Euler, EulerArray, QuatArray, Vec, VecArray): + + # construct from Quat array + + Q = QuatArray(3) + Q[0].setAxisAngle (Vec ( 1., 0., 0.), math.pi) + Q[1].setAxisAngle (Vec ( 1., 1., 0.), math.pi) + Q[2].setAxisAngle (Vec ( 0., 1., 0.), math.pi) + + E = EulerArray(Q) + for i in range(3): + e = Euler() + e.extract(Q[i]) + assert E[i] == e + + # construct from Vec array + + V = VecArray(3) + V[0] = Vec(1,0,0) + V[1] = Vec(0,1,0) + V[2] = Vec(0,0,1) + + E = EulerArray(V) + for i in range(3): + assert E[i] == Euler(V[i]) + + E = EulerArray(V, EULER_ZYX) + for i in range(3): + assert E[i] == Euler(V[i], EULER_ZYX) + + # toXYZVector + + X = E.toXYZVector() + assert X[0] == V[2] + assert X[1] == V[1] + assert X[2] == V[0] def testEuler(): print ("Eulerf") - testEulerx (Eulerf, V3f, M33f, M44f) + testEulerx (Eulerf, V3f, M33f, M44f, Quatf) print ("Eulerd") - testEulerx (Eulerd, V3d, M33d, M44d) + testEulerx (Eulerd, V3d, M33d, M44d, Quatd) print ("conversions") testEulerConversions() - + print("EulerfArray") + testEulerArrays(Eulerf, EulerfArray, QuatfArray, V3f, V3fArray) + print("EulerdArray") + testEulerArrays(Eulerd, EulerdArray, QuatdArray, V3d, V3dArray) testList.append (('testEuler',testEuler)) @@ -7926,7 +8263,7 @@ testList.append (('testColorConversions',testColorConversions)) # ------------------------------------------------------------------------- # Tests for Frustumx -def testFrustumx (Frustum, Vec3, Mat): +def testFrustumx (Frustum, Plane, Vec2, Vec3, Mat): # Constructors (and accessors). @@ -7955,6 +8292,13 @@ def testFrustumx (Frustum, Vec3, Mat): f.orthographic() == ortho assert f.near() == nearPlane and f.far() == farPlane + f = eval(repr(f)) + assert f.nearPlane() == nearPlane and f.farPlane() == farPlane and \ + f.left() == left and f.right() == right and \ + f.bottom() == bottom and f.top() == top and \ + f.orthographic() == ortho + assert f.near() == nearPlane and f.far() == farPlane + # Assignment. f1 = Frustum(nearPlane, farPlane, left, right, top, bottom, ortho) @@ -8021,6 +8365,13 @@ def testFrustumx (Frustum, Vec3, Mat): assert equal(f.nearPlane(), m_near, 2 * farPlaneN.baseTypeEpsilon()) assert equal(f.farPlane(), m_far, 2 * farPlaneN.baseTypeEpsilon()) + ortho = 1 + f = Frustum(nearPlane, farPlane, left, right, top, bottom, ortho) + m_near = f.nearPlane() - 0.1 + m_far = f.farPlane() + 100 + f.modifyNearAndFar(m_near, m_far) + assert equal(f.nearPlane(), m_near, 2 * farPlaneN.baseTypeEpsilon()) + # Fovy, aspect, projection matrix. nearPlane = 1 @@ -8086,9 +8437,32 @@ def testFrustumx (Frustum, Vec3, Mat): bottom + (top - bottom) * (1 + t) / 2.0, -nearPlane) assert iszero(l.distanceTo(p3d), 2 * p3d.baseTypeEpsilon()) + l = f.projectScreenToRay(Vec2(s, t)) + + p3d = Vec3(left + (right - left) * (1 + s) / 2.0, + bottom + (top - bottom) * (1 + t) / 2.0, -nearPlane) + assert iszero(l.distanceTo(p3d), 2 * p3d.baseTypeEpsilon()) + + try: + f.projectScreenToRay((0, 0, 0)) + except: + pass + else: + assert False + + p2d = f.projectPointToScreen((p3d.x, p3d.y, p3d.z)) + assert p2d.equalWithAbsError((s, t), p2d.baseTypeEpsilon()) + p2d = f.projectPointToScreen(p3d) assert p2d.equalWithAbsError((s, t), p2d.baseTypeEpsilon()) + try: + f.projectPointToScreen((0, 0, 0, 0)) + except: + pass + else: + assert False + p3df = V3f (p3d) p2d = f.projectPointToScreen(p3df) assert p2d.equalWithAbsError((s, t), p2d.baseTypeEpsilon()) @@ -8144,9 +8518,29 @@ def testFrustumx (Frustum, Vec3, Mat): r1 = f.screenRadius((0, 0, -d), d * s) assert equal(r1, s, Vec3().baseTypeEpsilon()) + try: + f.screenRadius((0, 0, 0, 0), r1) + except: + pass + else: + assert False + + r1 = f.screenRadius(Vec3(0, 0, -d), d * s) + assert equal(r1, s, Vec3().baseTypeEpsilon()) + r2 = f.worldRadius((0, 0, -d), r1) assert equal(r2, d * s, Vec3().baseTypeEpsilon()) + try: + f.worldRadius((0, 0, 0, 0), r1) + except: + pass + else: + assert False + + r2 = f.worldRadius(Vec3(0, 0, -d), r1) + assert equal(r2, d * s, Vec3().baseTypeEpsilon()) + print ("ok") return @@ -8154,11 +8548,36 @@ def testFrustumx (Frustum, Vec3, Mat): def testFrustum (): print ("Frustumf") - testFrustumx (Frustumf, V3f, M44f) + testFrustumx (Frustumf, Plane3f, V2f, V3f, M44f) testList.append (('testFrustum',testFrustum)) +def testFrustumTest (): + + f = Frustumf() + + nearPlane = 1 + farPlane = 1000 + left = -2 + right = 2 + top = 2 + bottom = -2 + ortho = 1 + + f = Frustumf(nearPlane, farPlane, left, right, top, bottom, ortho) + m = M44f() + t = FrustumTestf(f, m) + + t.isVisible(Box3f()) + t.isVisible(V3f(0,0,0)) + + V = V3fArray(2) + t.isVisible(V) + t.completelyContains(Box3f()) + +testList.append (('testFrustumTest',testFrustumTest)) + # ------------------------------------------------------------------------- # Tests for random number generators @@ -10117,6 +10536,29 @@ def testQuatArrays(): assert (abs (q1.x[3] + x) <= 1.e-6) assert (abs (q1.y[3] - x) <= 1.e-6) + u = EulerfArray(5) + for i in range(5): + u[i] = Eulerf (V3f (1, 2, 3), EULER_XYZ) + q = QuatfArray(u) + for i in range(5): + assert equalWithAbsError(q[i].r(), 0.4359528422355652, 1e-5) and q[i].v().equalWithAbsError(V3f(-0.718287, 0.310622, 0.444435), 1e-5) + + axis = q1.axis() + assert axis[0].equalWithAbsError(V3f(1, 0, 0), 1e-5) + assert axis[1].equalWithAbsError(V3f(0.707107, 0.707107, 0), 1e-5) + assert axis[2].equalWithAbsError(V3f(0, 1, 0), 1e-5) + assert axis[3].equalWithAbsError(V3f(-0.707107, 0.707107, 0), 1e-5) + + angle = q1.angle() + for i in range(5): + assert equalWithAbsError(angle[i], pi, 1e-5) + + m = M44dArray(5) + qm = QuatfArray(5) + qm.extract(m) + for i in range(5): + assert equalWithAbsError(qm[i].r(), 1, 1e-5) and qm[i].v().equalWithAbsError(V3f(0,0,0),1e-5) + q2 = -q1 for i in range(5): assert (q2[i] == -q1[i]) @@ -10130,6 +10572,27 @@ def testQuatArrays(): assert (q3[i] == q1[i] * qA) q3 *= 10. + a = V3f(1,0,0) * q1 + assert a[0].equalWithAbsError(V3f(1, 0, 0), 1e-5) + assert a[1].equalWithAbsError(V3f(5.96046e-08, 1, 6.18172e-08), 1e-5) + assert a[2].equalWithAbsError(V3f(-1, 0, 8.74228e-08), 1e-5) + assert a[3].equalWithAbsError(V3f(5.96046e-08, -1, 6.18172e-08), 1e-5) + + v = V3fArray(5) + for i in range(5): + v[i] = V3f(1,0,0) + a = v * q1 + assert a[0].equalWithAbsError(V3f(1, 0, 0), 1e-5) + assert a[1].equalWithAbsError(V3f(5.96046e-08, 1, 6.18172e-08), 1e-5) + assert a[2].equalWithAbsError(V3f(-1, 0, 8.74228e-08), 1e-5) + assert a[3].equalWithAbsError(V3f(5.96046e-08, -1, 6.18172e-08), 1e-5) + + a = q1.rotateVector(v) + assert a[0].equalWithAbsError(V3f(1, 0, 0), 1e-5) + assert a[1].equalWithAbsError(V3f(0, 1, 6.18172e-08), 1e-5) + assert a[2].equalWithAbsError(V3f(-1, 0, 8.74228e-08), 1e-5) + assert a[3].equalWithAbsError(V3f(0, -1, 6.18172e-08), 1e-5) + tmp = QuatfArray (5) tmp[:] = q3 q3.normalize() @@ -10140,7 +10603,8 @@ def testQuatArrays(): q4 = q1.slerp (q3, 0.5) for i in range(5): assert (q4[i] == q1[i].slerpShortestArc (q3[i], 0.5)) - + assert (q4[i] == q3[i].slerpShortestArc (q1[i], 0.5)) + tmp[:] = q4 q4 *= q3.inverse() for i in range(5):