Commit 94cbffc9 by Jan Möbius

### Merge branch 'c++11_matrix' into 'master'

C++11 matrix

Added a modern (C++11) 3x3 matrix class and a closest point in triangle query that builds on top of it.

See merge request !148
parents d03842e6 ae668cf2
Pipeline #3358 passed with stage
in 57 minutes and 37 seconds
 ... ... @@ -61,6 +61,8 @@ #include #include #include "../Math/Matrix3x3T.hh" namespace ACG { namespace Geometry { ... ... @@ -631,7 +633,72 @@ roundness( const VectorT& _v0, const VectorT& _v1, const VectorT& _v2 ); /** @} */ /** @} */ template Vector closestPointLineSegment(Vector x, Vector p1, Vector p2) { const auto delta = ((p2-p1)|(x-p1)) / (p2-p1).sqrnorm(); //std::cout << "\x1b[32mdelta = " << delta << "\x1b[0m" << std::endl; if (delta <= 0) { //std::cout << "\x1b[43mdelta <= 0\x1b[0m" << std::endl; return p1; } else if (delta >= 1) { //std::cout << "\x1b[43mdelta >= 1\x1b[0m" << std::endl; return p2; } else if (delta != delta) { // p1 = p2 or x = p1 //std::cout << "\x1b[43mdelta != delta\x1b[0m" << std::endl; return (x-p1).sqrnorm() < (x-p2).sqrnorm() ? p1 : p2; } else { //std::cout << "\x1b[43mdelta \\in [0, 1]\x1b[0m" << std::endl; return (1 - delta) * p1 + delta * p2; } }; template Vector closestPointTri(Vector p, Vector a, Vector b, Vector c) { constexpr double thresh = 1e-8; const auto n = ((b - a) % (c - a)); // normalization unnecessary if ((b-a).sqrnorm() < thresh || (c-a).sqrnorm() < thresh || n.sqrnorm() < thresh) { //std::cout << "\x1b[42mDegenerate case.\x1b[0m" << std::endl; // Degenerate triangle. Find distance to longest segment. std::array max_segment = {a, b}; double max_len = (b-a).sqrnorm(); if ((c-a).sqrnorm() > max_len) max_segment = {a, c}; if ((c-b).sqrnorm() > max_len) max_segment = {b, c}; // closestPointLineSegment is stable, even if the segment is super short return closestPointLineSegment(p, max_segment[0], max_segment[1]); } const auto abd = Matrix3x3d::fromColumns(a-c, b-c, n).inverse() * (p - c); const bool alpha = (abd[0] >= 0.0), beta = (abd[1] >= 0.0), gamma = (1.0-abd[0]-abd[1] >= 0.0); if (alpha && beta && gamma) { //std::cout << "\x1b[42mInside case.\x1b[0m" << std::endl; // Inside triangle. return abd[0] * a + abd[1] * b + (1.0 - abd[0] - abd[1]) * c; } else if (!alpha) { //std::cout << "\x1b[42m!alpha case.\x1b[0m" << std::endl; // Closest to line segment (b, c). return closestPointLineSegment(p, b, c); } else if (!beta) { //std::cout << "\x1b[42m!beta case.\x1b[0m" << std::endl; // Closest to line segment (a, c). return closestPointLineSegment(p, a, c); } else if (!gamma) { //std::cout << "\x1b[42m!gamma case.\x1b[0m" << std::endl; // Closest to line segment (a, b). return closestPointLineSegment(p, a, b); } else { throw std::logic_error("This cannot happen."); } } //============================================================================= } // namespace Geometry ... ...
 #ifndef ACG_MATH_MATRIX3X3T_HH_ #define ACG_MATH_MATRIX3X3T_HH_ #include #include #include #include #include #if defined(_MSC_VER) && _MSC_VER < 1900 #define constexpr typedef unsigned char uint_fast8_t; #endif namespace ACG { /** * Small, kinda fast 3x3 matrix class. */ template class Matrix3x3T { public: typedef typename OpenMesh::VectorT Vec3; static Matrix3x3T fromColumns(Vec3 c1, Vec3 c2, Vec3 c3) { return Matrix3x3T {{ c1[0], c2[0], c3[0], c1[1], c2[1], c3[1], c1[2], c2[2], c3[2], }}; } static Matrix3x3T fromRows(Vec3 r1, Vec3 r2, Vec3 r3) { return Matrix3x3T {{ r1[0], r1[1], r1[2], r2[0], r2[1], r2[2], r3[0], r3[1], r3[2] }}; } static constexpr Matrix3x3T identity() { return {{ 1, 0, 0, 0, 1, 0, 0, 0, 1 }}; } static constexpr Matrix3x3T zero() { return {{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }}; } public: Matrix3x3T() = default; /** * Initialize matrix from array in row major format. */ constexpr Matrix3x3T(std::array row_major) : values_(std::move(row_major)) {} constexpr bool operator==(const Matrix3x3T &rhs) const { return values_ == rhs.values_; } //Matrix3x3T(std::initializer_list row_major) { // static_assert(row_major.size() == 9, "Need exactly 9 values."); // std::copy(row_major.begin(), row_major.end(), this->begin()); //} /// Map row/column index to linearized index. constexpr static uint_fast8_t indexof(uint_fast8_t r, uint_fast8_t c) { return r*3+c; } constexpr const Scalar &operator() (uint_fast8_t r, uint_fast8_t c) const { return values_[indexof(r, c)]; } Scalar &operator() (uint_fast8_t r, uint_fast8_t c) { return values_[indexof(r, c)]; } /// Linearized row major access. constexpr const Scalar &operator[] (uint_fast8_t i) const { return values_[i]; } /// Linearized row major access. Scalar &operator[] (uint_fast8_t i) { return values_[i]; } constexpr Matrix3x3T operator*(const Matrix3x3T &rhs) const { return Matrix3x3T {{ (*this)(0, 0) * rhs(0, 0) + (*this)(0, 1) * rhs(1, 0) + (*this)(0, 2) * rhs(2, 0), (*this)(0, 0) * rhs(0, 1) + (*this)(0, 1) * rhs(1, 1) + (*this)(0, 2) * rhs(2, 1), (*this)(0, 0) * rhs(0, 2) + (*this)(0, 1) * rhs(1, 2) + (*this)(0, 2) * rhs(2, 2), (*this)(1, 0) * rhs(0, 0) + (*this)(1, 1) * rhs(1, 0) + (*this)(1, 2) * rhs(2, 0), (*this)(1, 0) * rhs(0, 1) + (*this)(1, 1) * rhs(1, 1) + (*this)(1, 2) * rhs(2, 1), (*this)(1, 0) * rhs(0, 2) + (*this)(1, 1) * rhs(1, 2) + (*this)(1, 2) * rhs(2, 2), (*this)(2, 0) * rhs(0, 0) + (*this)(2, 1) * rhs(1, 0) + (*this)(2, 2) * rhs(2, 0), (*this)(2, 0) * rhs(0, 1) + (*this)(2, 1) * rhs(1, 1) + (*this)(2, 2) * rhs(2, 1), (*this)(2, 0) * rhs(0, 2) + (*this)(2, 1) * rhs(1, 2) + (*this)(2, 2) * rhs(2, 2), }}; } constexpr Vec3 operator*(const Vec3 &rhs) const { return Vec3( (*this)(0, 0) * rhs[0] + (*this)(0, 1) * rhs[1] + (*this)(0, 2) * rhs[2], (*this)(1, 0) * rhs[0] + (*this)(1, 1) * rhs[1] + (*this)(1, 2) * rhs[2], (*this)(2, 0) * rhs[0] + (*this)(2, 1) * rhs[1] + (*this)(2, 2) * rhs[2] ); } constexpr friend Vec3 operator*(Vec3 v, const Matrix3x3T &rhs) { return Vec3( rhs(0, 0) * v[0] + rhs(0, 1) * v[1] + rhs(0, 2) * v[2], rhs(1, 0) * v[0] + rhs(1, 1) * v[1] + rhs(1, 2) * v[2], rhs(2, 0) * v[0] + rhs(2, 1) * v[1] + rhs(2, 2) * v[2] ); } constexpr Matrix3x3T operator*(Scalar c) const { return Matrix3x3T {{ (*this)[0] * c, (*this)[1] * c, (*this)[2] * c, (*this)[3] * c, (*this)[4] * c, (*this)[5] * c, (*this)[6] * c, (*this)[7] * c, (*this)[8] * c, }}; } constexpr friend Matrix3x3T operator*(Scalar c, const Matrix3x3T &rhs) { return Matrix3x3T {{ rhs[0] * c, rhs[1] * c, rhs[2] * c, rhs[3] * c, rhs[4] * c, rhs[5] * c, rhs[6] * c, rhs[7] * c, rhs[8] * c, }}; } constexpr Matrix3x3T operator+ (const Matrix3x3T &rhs) const { return Matrix3x3T {{ (*this)[0] + rhs[0], (*this)[1] + rhs[1], (*this)[2] + rhs[2], (*this)[3] + rhs[3], (*this)[4] + rhs[4], (*this)[5] + rhs[5], (*this)[6] + rhs[6], (*this)[7] + rhs[7], (*this)[8] + rhs[8], }}; } constexpr Matrix3x3T operator- (const Matrix3x3T &rhs) const { return Matrix3x3T {{ (*this)[0] - rhs[0], (*this)[1] - rhs[1], (*this)[2] - rhs[2], (*this)[3] - rhs[3], (*this)[4] - rhs[4], (*this)[5] - rhs[5], (*this)[6] - rhs[6], (*this)[7] - rhs[7], (*this)[8] - rhs[8], }}; } constexpr Matrix3x3T operator- () const { return Matrix3x3T {{ -values_[0], -values_[1], -values_[2], -values_[3], -values_[4], -values_[5], -values_[6], -values_[7], -values_[8] }}; } const Matrix3x3T &operator*=(const Matrix3x3T &rhs) { (*this) = operator*(rhs); return *this; } constexpr Scalar det() const { return (*this)(0, 0) * ((*this)(1, 1) * (*this)(2, 2) - (*this)(2, 1) * (*this)(1, 2)) - (*this)(0, 1) * ((*this)(1, 0) * (*this)(2, 2) - (*this)(1, 2) * (*this)(2, 0)) + (*this)(0, 2) * ((*this)(1, 0) * (*this)(2, 1) - (*this)(1, 1) * (*this)(2, 0)); /* return (*this)(0, 0) * (*this)(1, 1) * (*this)(2, 2) + (*this)(1, 0) * (*this)(2, 1) * (*this)(0, 2) + (*this)(2, 0) * (*this)(0, 1) * (*this)(1, 2) - (*this)(0, 0) * (*this)(2, 1) * (*this)(1, 2) - (*this)(2, 0) * (*this)(1, 1) * (*this)(0, 2) - (*this)(1, 0) * (*this)(0, 1) * (*this)(2, 2) */ } constexpr Scalar trace() const { return (*this)[0] + (*this)[4] + (*this)[8]; } void transpose() { std::swap(values_[1], values_[3]); std::swap(values_[2], values_[6]); std::swap(values_[5], values_[7]); } constexpr Matrix3x3T transposed() const { return Matrix3x3T {{ values_[0], values_[3], values_[6], values_[1], values_[4], values_[7], values_[2], values_[5], values_[8], }}; } void invert() { *this = inverse(); } Matrix3x3T inverse() const { const Scalar invdet = 1.0 / det(); return Matrix3x3T {{ ((*this)(1, 1) * (*this)(2, 2) - (*this)(2, 1) * (*this)(1, 2)) * invdet, ((*this)(0, 2) * (*this)(2, 1) - (*this)(0, 1) * (*this)(2, 2)) * invdet, ((*this)(0, 1) * (*this)(1, 2) - (*this)(0, 2) * (*this)(1, 1)) * invdet, ((*this)(1, 2) * (*this)(2, 0) - (*this)(1, 0) * (*this)(2, 2)) * invdet, ((*this)(0, 0) * (*this)(2, 2) - (*this)(0, 2) * (*this)(2, 0)) * invdet, ((*this)(1, 0) * (*this)(0, 2) - (*this)(0, 0) * (*this)(1, 2)) * invdet, ((*this)(1, 0) * (*this)(2, 1) - (*this)(2, 0) * (*this)(1, 1)) * invdet, ((*this)(2, 0) * (*this)(0, 1) - (*this)(0, 0) * (*this)(2, 1)) * invdet, ((*this)(0, 0) * (*this)(1, 1) - (*this)(1, 0) * (*this)(0, 1)) * invdet, }}; } constexpr Scalar frobeniusSquared() const { return std::inner_product( values_.begin(), values_.end(), values_.begin(), Scalar(0.0)); } constexpr double frobenius() const { return std::sqrt(frobeniusSquared()); } friend std::ostream &operator<< (std::ostream &os, const Matrix3x3T &m) { os << "[[" << m[0] << ", " << m[1] << ", " << m[2] << "], " "[" << m[3] << ", " << m[4] << ", " << m[5] << "], " "[" << m[6] << ", " << m[7] << ", " << m[8] << "]]"; return os; } private: std::array values_; }; typedef Matrix3x3T Matrix3x3f; typedef Matrix3x3T Matrix3x3d; } /* namespace ACG */ #if defined(_MSC_VER) && _MSC_VER < 1900 #undef constexpr #endif #endif /* ACG_MATH_MATRIX3X3T_HH_ */
 /*===========================================================================*\ * * * OpenFlipper * * Copyright (c) 2001-2015, RWTH-Aachen University * * Department of Computer Graphics and Multimedia * * All rights reserved. * * www.openflipper.org * * * *---------------------------------------------------------------------------* * This file is part of OpenFlipper. * *---------------------------------------------------------------------------* * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * * * 1. Redistributions of source code must retain the above copyright notice, * * this list of conditions and the following disclaimer. * * * * 2. Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * * * 3. Neither the name of the copyright holder nor the names of its * * contributors may be used to endorse or promote products derived from * * this software without specific prior written permission. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * \*===========================================================================*/ #include #include #include #include "../Math/MatrixTestHelper.hh" namespace { TEST(Algorithms_ClosestPoints, closestPointTri_inside) { // Closest point inside { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(0.180189728737, 0.284434795380, 0.685222446918), ACG::Vec3d(0.780881822109, -0.711405277252, 0.000000000000), ACG::Vec3d(-1.080165147781, 0.470909714699, 0.000000000000), ACG::Vec3d(0.663306236267, 1.208429455757, 0.000000000000) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(0.180189728737, 0.284434795380, 0.000000000000))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(0.180189728737, 0.284434795380, 0.685222446918), ACG::Vec3d(0.898327171803, -0.711405336857, 0.402669787407), ACG::Vec3d(-1.138887882233, 0.470909774303, -0.511726200581), ACG::Vec3d(0.738806843758, 1.208429455757, -0.109056398273) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(0.379620872542, 0.129005603666, 0.039932640059))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(0.383401751518, -0.044320285320, -0.729106009007), ACG::Vec3d(0.898327171803, -0.711405336857, 0.402669787407), ACG::Vec3d(-1.138887882233, 0.470909774303, -0.511726200581), ACG::Vec3d(0.738806843758, 1.208429455757, -0.109056398273) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(0.165861088939, 0.125222789570, -0.025220099004))); } } TEST(Algorithms_ClosestPoints, closestPointTri_onEdge) { // Closest point on edges. { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(2.194746255875, 0.654435634613, 1.605020284653), ACG::Vec3d(0.898327171803, -0.711405336857, 0.402669787407), ACG::Vec3d(-1.138887882233, 0.470909774303, -0.511726200581), ACG::Vec3d(0.738806843758, 1.208429455757, -0.109056398273) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(0.826052171821, 0.158427751505, 0.170818395715))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(-1.639497995377, -1.687432765961, 0.645231306553), ACG::Vec3d(0.898327171803, -0.711405336857, 0.402669787407), ACG::Vec3d(-1.138887882233, 0.470909774303, -0.511726200581), ACG::Vec3d(0.738806843758, 1.208429455757, -0.109056398273) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(-0.312445889897, -0.008722876212, -0.140780953244))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(-0.954960584641, 3.141639709473, -0.399959266186), ACG::Vec3d(0.898327171803, -0.711405336857, 0.402669787407), ACG::Vec3d(-1.138887882233, 0.470909774303, -0.511726200581), ACG::Vec3d(0.738806843758, 1.208429455757, -0.109056398273) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(-0.091698979114, 0.882223932862, -0.287157561834))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(-0.326786577702, -0.595147013664, -0.012022850104), ACG::Vec3d(0.898327171803, -0.711405336857, 0.402669787407), ACG::Vec3d(-1.138887882233, 0.470909774303, -0.511726200581), ACG::Vec3d(0.738806843758, 1.208429455757, -0.109056398273) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(-0.062953975601, -0.153517634035, -0.028797485905))); } } TEST(Algorithms_ClosestPoints, closestPointTri_onCorner) { { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(-1.840421676636, 0.351687341928, -1.691396117210), ACG::Vec3d(0.898327171803, -0.711405336857, 0.402669787407), ACG::Vec3d(-1.138887882233, 0.470909774303, -0.511726200581), ACG::Vec3d(0.738806843758, 1.208429455757, -0.109056398273) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(-1.138887882233, 0.470909774303, -0.511726200581))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(1.548038601875, 1.783731102943, -0.995657980442), ACG::Vec3d(0.898327171803, -0.711405336857, 0.402669787407), ACG::Vec3d(-1.138887882233, 0.470909774303, -0.511726200581), ACG::Vec3d(0.738806843758, 1.208429455757, -0.109056398273) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(0.738806843758, 1.208429455757, -0.109056398273))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(1.674694657326, -1.574745774269, -0.140889823437), ACG::Vec3d(0.898327171803, -0.711405336857, 0.402669787407), ACG::Vec3d(-1.138887882233, 0.470909774303, -0.511726200581), ACG::Vec3d(0.738806843758, 1.208429455757, -0.109056398273) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(0.898327171803, -0.711405336857, 0.402669787407))); } } TEST(Algorithms_ClosestPoints, closestPointTri_degenerate) { { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(0.379400968552, -0.866840600967, -1.375681400299), ACG::Vec3d(-2.827600002289, -1.685648560524, -0.758169531822), ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070630312), ACG::Vec3d(-0.661723732948, 1.627503752708, -0.181971758604) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(-1.568279320469, 0.240740866643, -0.423146806300))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(0.842265009880, 3.628707885742, -0.395066857338), ACG::Vec3d(-2.827600002289, -1.685648560524, -0.758169531822), ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070630312), ACG::Vec3d(-0.661723732948, 1.627503752708, -0.181971758604) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(-0.661723732948, 1.627503752708, -0.181971758604))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(0.842265009880, 3.628707885742, -0.395066857338), ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070600510), ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070630312), ACG::Vec3d(-0.661723732948, 1.627503752708, -0.181971758604) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(-0.661723732948, 1.627503752708, -0.181971758604))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(-3.731231689453, -2.141752481461, -0.150896072388), ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070600510), ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070630312), ACG::Vec3d(-0.661723732948, 1.627503752708, -0.181971758604) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070600510))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(-0.966588973999, 0.534245491028, 0.345366060734), ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070600510), ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070630312), ACG::Vec3d(-0.661723732948, 1.627503752708, -0.181971758604) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(-1.200293249127, 0.803651109932, -0.325249806970))); } { ACG::Vec3d closest_point = ACG::Geometry::closestPointTri( ACG::Vec3d(-0.966588973999, 0.534245491028, 0.345366060734), ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070600510), ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070630312), ACG::Vec3d(-1.744661808014, -0.029072403908, -0.470070600510) ); EXPECT_TRUE(areClose(closest_point, ACG::Vec3d(-1.744661808014, -0.029072359204, -0.470070600510))); } } } /* namespace */
 #include #include #include "MatrixTestHelper.hh" namespace { template class Matrix3x3Test: public testing::Test { public: Matrix3x3Test() {} virtual ~Matrix3x3Test() {} typedef typename ACG::Matrix3x3T Matrix3x3; }; typedef testing::Types Implementations; TYPED_TEST_CASE(Matrix3x3Test, Implementations); TYPED_TEST(Matrix3x3Test, access) { using Matrix3x3 = typename Matrix3x3Test::Matrix3x3; Matrix3x3 m {{