From b50fb5a50eac5363657447dac5aca55f73c38a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Aug 2013 17:02:43 +0200 Subject: [PATCH] DebugTools: shape renderer for 2D/3D capsule. --- src/DebugTools/CMakeLists.txt | 1 + .../Implementation/CapsuleRenderer.cpp | 116 +++++++++++++++ .../Implementation/CapsuleRenderer.h | 66 +++++++++ .../CapsuleRendererTransformation.h | 93 ++++++++++++ src/DebugTools/ResourceManager.cpp | 3 +- src/DebugTools/ResourceManager.h | 6 +- src/DebugTools/ShapeRenderer.cpp | 7 + src/DebugTools/Test/CMakeLists.txt | 1 + src/DebugTools/Test/CapsuleRendererTest.cpp | 137 ++++++++++++++++++ 9 files changed, 427 insertions(+), 3 deletions(-) create mode 100644 src/DebugTools/Implementation/CapsuleRenderer.cpp create mode 100644 src/DebugTools/Implementation/CapsuleRenderer.h create mode 100644 src/DebugTools/Implementation/CapsuleRendererTransformation.h create mode 100644 src/DebugTools/Test/CapsuleRendererTest.cpp diff --git a/src/DebugTools/CMakeLists.txt b/src/DebugTools/CMakeLists.txt index 66546c172..00d88ecc8 100644 --- a/src/DebugTools/CMakeLists.txt +++ b/src/DebugTools/CMakeLists.txt @@ -33,6 +33,7 @@ set(MagnumDebugTools_SRCS Implementation/AbstractShapeRenderer.cpp Implementation/AxisAlignedBoxRenderer.cpp Implementation/BoxRenderer.cpp + Implementation/CapsuleRenderer.cpp Implementation/LineSegmentRenderer.cpp Implementation/PointRenderer.cpp Implementation/SphereRenderer.cpp) diff --git a/src/DebugTools/Implementation/CapsuleRenderer.cpp b/src/DebugTools/Implementation/CapsuleRenderer.cpp new file mode 100644 index 000000000..d6e48d5b5 --- /dev/null +++ b/src/DebugTools/Implementation/CapsuleRenderer.cpp @@ -0,0 +1,116 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "CapsuleRenderer.h" + +#include "MeshView.h" +#include "DebugTools/ResourceManager.h" +#include "DebugTools/ShapeRenderer.h" +#include "Primitives/Capsule.h" +#include "Shapes/Capsule.h" +#include "Shaders/Flat.h" +#include "Trade/MeshData2D.h" +#include "Trade/MeshData3D.h" + +#include "DebugTools/Implementation/CapsuleRendererTransformation.h" + +namespace Magnum { namespace DebugTools { namespace Implementation { + +AbstractCapsuleRenderer<2>::AbstractCapsuleRenderer(): AbstractShapeRenderer<2>("capsule2d", "capsule2d-vertices", "capsule2d-indices") { + constexpr UnsignedInt rings = 10; + if(!wireframeMesh) createResources(Primitives::Capsule2D::wireframe(rings, 1, 1.0f)); + + /* Bottom hemisphere */ + if(!(bottom = ResourceManager::instance().get("capsule2d-bottom"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(0, rings*4, 0, rings*2+1); + ResourceManager::instance().set(bottom.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } + + /* Cylinder */ + if(!(cylinder = ResourceManager::instance().get("capsule2d-cylinder"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(rings*4, 4, rings*2+1, rings*2+3); + ResourceManager::instance().set(cylinder.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } + + /* Top hemisphere */ + if(!(top = ResourceManager::instance().get("capsule2d-top"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(rings*4+4, rings*4, rings*2+3, rings*4+4); + ResourceManager::instance().set(top.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } +} + +AbstractCapsuleRenderer<3>::AbstractCapsuleRenderer(): AbstractShapeRenderer<3>("capsule3d", "capsule3d-vertices", "capsule3d-indices") { + constexpr UnsignedInt rings = 10; + constexpr UnsignedInt segments = 20; + if(!wireframeMesh) createResources(Primitives::Capsule3D::wireframe(rings, 1, segments, 1.0f)); + + /* Bottom hemisphere */ + if(!(bottom = ResourceManager::instance().get("capsule3d-bottom"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(0, rings*8, 0, rings*4+1); + ResourceManager::instance().set(bottom.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } + + /* Cylinder */ + if(!(cylinder = ResourceManager::instance().get("capsule3d-cylinder"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(rings*8, segments*4+8, rings*4+1, rings*4+segments*2+5); + ResourceManager::instance().set(cylinder.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } + + /* Top */ + if(!(top = ResourceManager::instance().get("capsule3d-top"))) { + auto view = new MeshView(wireframeMesh); + view->setIndexRange(rings*8+segments*4+8, rings*8, rings*4+segments*2+5, rings*8+segments*2+6); + ResourceManager::instance().set(top.key(), view, ResourceDataState::Final, ResourcePolicy::Manual); + } +} + +template CapsuleRenderer::CapsuleRenderer(const Shapes::Implementation::AbstractShape& capsule): capsule(static_cast>&>(capsule).shape) {} + +template void CapsuleRenderer::draw(Resource& options, const typename DimensionTraits::MatrixType& projectionMatrix) { + std::array::MatrixType, 3> transformations = Implementation::capsuleRendererTransformation(capsule.a(), capsule.b(), capsule.radius()); + AbstractShapeRenderer::wireframeShader->setColor(options->color()) + .use(); + + /* Bottom */ + AbstractShapeRenderer::wireframeShader->setTransformationProjectionMatrix(projectionMatrix*transformations[0]); + AbstractCapsuleRenderer::bottom->draw(); + + /* Cylinder */ + AbstractShapeRenderer::wireframeShader->setTransformationProjectionMatrix(projectionMatrix*transformations[1]); + AbstractCapsuleRenderer::cylinder->draw(); + + /* Top */ + AbstractShapeRenderer::wireframeShader->setTransformationProjectionMatrix(projectionMatrix*transformations[2]); + AbstractCapsuleRenderer::top->draw(); +} + +template class CapsuleRenderer<2>; +template class CapsuleRenderer<3>; + +}}} diff --git a/src/DebugTools/Implementation/CapsuleRenderer.h b/src/DebugTools/Implementation/CapsuleRenderer.h new file mode 100644 index 000000000..d00c5e47f --- /dev/null +++ b/src/DebugTools/Implementation/CapsuleRenderer.h @@ -0,0 +1,66 @@ +#ifndef Magnum_DebugTools_Implementation_CapsuleRenderer_h +#define Magnum_DebugTools_Implementation_CapsuleRenderer_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "AbstractShapeRenderer.h" + +#include "Shapes/Shapes.h" + +#include "corradeCompatibility.h" + +namespace Magnum { namespace DebugTools { namespace Implementation { + +template class AbstractCapsuleRenderer; + +template<> class AbstractCapsuleRenderer<2>: public AbstractShapeRenderer<2> { + public: + explicit AbstractCapsuleRenderer(); + + protected: + Resource bottom, cylinder, top; +}; + +template<> class AbstractCapsuleRenderer<3>: public AbstractShapeRenderer<3> { + public: + explicit AbstractCapsuleRenderer(); + + protected: + Resource bottom, cylinder, top; +}; + +template class CapsuleRenderer: public AbstractCapsuleRenderer { + public: + explicit CapsuleRenderer(const Shapes::Implementation::AbstractShape& capsule); + CapsuleRenderer(const Shapes::Implementation::AbstractShape&&) = delete; + + void draw(Resource& options, const typename DimensionTraits::MatrixType& projectionMatrix) override; + + private: + const Shapes::Capsule& capsule; +}; + +}}} + +#endif diff --git a/src/DebugTools/Implementation/CapsuleRendererTransformation.h b/src/DebugTools/Implementation/CapsuleRendererTransformation.h new file mode 100644 index 000000000..ec4120491 --- /dev/null +++ b/src/DebugTools/Implementation/CapsuleRendererTransformation.h @@ -0,0 +1,93 @@ +#ifndef Magnum_DebugTools_Implementation_ForceRendererTransformation_h +#define Magnum_DebugTools_Implementation_ForceRendererTransformation_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include "Math/Matrix3.h" +#include "Math/Matrix4.h" +#include "Magnum.h" +#include "DimensionTraits.h" + +namespace Magnum { namespace DebugTools { namespace Implementation { + +template std::array::MatrixType, 3> capsuleRendererTransformation(const typename DimensionTraits::VectorType& a, const typename DimensionTraits::VectorType& b, Float radius); + +template<> inline std::array capsuleRendererTransformation<2>(const Vector2& a, const Vector2& b, const Float radius) { + /* Vector from capsule center to top hemisphere center */ + const Vector2 direction = 0.5f*(b - a); + const Float length = direction.length(); + + /* Capsule rotation and distance to caps after they are scaled to proper + radius (if nonzero cylinder length) */ + Matrix3 rotation; + Vector2 capDistance; + if(length >= Math::TypeTraits::epsilon()) { + rotation.up() = direction/length; + rotation.right() = rotation.up().perpendicular(); + CORRADE_INTERNAL_ASSERT(rotation.right().isNormalized()); + + capDistance = direction*(radius/length); + } + + /* Scaling and translation of all parts */ + const auto rotationScaling = rotation*Matrix3::scaling(Vector2(radius)); + return {{ + Matrix3::translation(a+capDistance)*rotationScaling, + Matrix3::translation(0.5f*(a + b))*rotation*Matrix3::scaling({radius, length}), + Matrix3::translation(b-capDistance)*rotationScaling + }}; +} + +template<> std::array capsuleRendererTransformation<3>(const Vector3& a, const Vector3& b, const Float radius) { + /* Vector from capsule center to top hemisphere center */ + const Vector3 direction = 0.5f*(b - a); + const Float length = direction.length(); + + /* Capsule rotation and distance to caps after they are scaled to proper + radius (if nonzero cylinder length) */ + Matrix4 rotation; + Vector3 capDistance; + if(length >= Math::TypeTraits::epsilon()) { + rotation.up() = direction/length; + rotation.right() = Vector3::cross(rotation.up(), Vector3::zAxis()).normalized(); + rotation.backward() = Vector3::cross(rotation.right(), rotation.up()); + CORRADE_INTERNAL_ASSERT(rotation.up().isNormalized() && rotation.backward().isNormalized()); + + capDistance = direction*(radius/length); + } + + /* Scaling and translation of all parts */ + const auto rotationScaling = rotation*Matrix4::scaling(Vector3(radius)); + return {{ + Matrix4::translation(a+capDistance)*rotationScaling, + Matrix4::translation(0.5f*(a + b))*rotation*Matrix4::scaling({radius, length, radius}), + Matrix4::translation(b-capDistance)*rotationScaling + }}; +} + +}}} + +#endif diff --git a/src/DebugTools/ResourceManager.cpp b/src/DebugTools/ResourceManager.cpp index 204c28357..6b385af7d 100644 --- a/src/DebugTools/ResourceManager.cpp +++ b/src/DebugTools/ResourceManager.cpp @@ -28,13 +28,14 @@ #include "Buffer.h" #include "Mesh.h" +#include "MeshView.h" #include "DebugTools/ForceRenderer.h" #include "DebugTools/ObjectRenderer.h" #include "DebugTools/ShapeRenderer.h" namespace Magnum { -template class ResourceManager; +template class ResourceManager; namespace DebugTools { diff --git a/src/DebugTools/ResourceManager.h b/src/DebugTools/ResourceManager.h index ccf907718..f5205e044 100644 --- a/src/DebugTools/ResourceManager.h +++ b/src/DebugTools/ResourceManager.h @@ -43,7 +43,9 @@ namespace Magnum { -extern template ResourceManager MAGNUM_DEBUGTOOLS_EXPORT *& ResourceManager::internalInstance(); +/** @todo Do the listing in one place, not five thousand! */ + +extern template ResourceManager MAGNUM_DEBUGTOOLS_EXPORT *& ResourceManager::internalInstance(); namespace DebugTools { @@ -53,7 +55,7 @@ namespace DebugTools { Stores various data used by debug renderers. See @ref debug-tools for more information. */ -class MAGNUM_DEBUGTOOLS_EXPORT ResourceManager: public Magnum::ResourceManager { +class MAGNUM_DEBUGTOOLS_EXPORT ResourceManager: public Magnum::ResourceManager { public: explicit ResourceManager(); ~ResourceManager(); diff --git a/src/DebugTools/ShapeRenderer.cpp b/src/DebugTools/ShapeRenderer.cpp index f659779eb..62cbc8f37 100644 --- a/src/DebugTools/ShapeRenderer.cpp +++ b/src/DebugTools/ShapeRenderer.cpp @@ -31,6 +31,7 @@ #include "Implementation/AxisAlignedBoxRenderer.h" #include "Implementation/BoxRenderer.h" +#include "Implementation/CapsuleRenderer.h" #include "Implementation/LineSegmentRenderer.h" #include "Implementation/PointRenderer.h" #include "Implementation/SphereRenderer.h" @@ -56,6 +57,9 @@ template<> void createDebugMesh(ShapeRenderer<2>& renderer, const Shapes::Implem case Shapes::AbstractShape2D::Type::Sphere: renderer.renderers.push_back(new Implementation::SphereRenderer<2>(shape)); break; + case Shapes::AbstractShape2D::Type::Capsule: + renderer.renderers.push_back(new Implementation::CapsuleRenderer<2>(shape)); + break; case Shapes::AbstractShape2D::Type::Composition: { const Shapes::Composition2D& composition = static_cast&>(shape).shape; @@ -84,6 +88,9 @@ template<> void createDebugMesh(ShapeRenderer<3>& renderer, const Shapes::Implem case Shapes::AbstractShape3D::Type::Sphere: renderer.renderers.push_back(new Implementation::SphereRenderer<3>(shape)); break; + case Shapes::AbstractShape3D::Type::Capsule: + renderer.renderers.push_back(new Implementation::CapsuleRenderer<3>(shape)); + break; case Shapes::AbstractShape3D::Type::Composition: { const Shapes::Composition3D& composition = static_cast&>(shape).shape; diff --git a/src/DebugTools/Test/CMakeLists.txt b/src/DebugTools/Test/CMakeLists.txt index 1e945d0c0..ba5e678e2 100644 --- a/src/DebugTools/Test/CMakeLists.txt +++ b/src/DebugTools/Test/CMakeLists.txt @@ -22,5 +22,6 @@ # DEALINGS IN THE SOFTWARE. # +corrade_add_test(DebugToolsCapsuleRendererTest CapsuleRendererTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(DebugToolsForceRendererTest ForceRendererTest.cpp LIBRARIES MagnumMathTestLib) corrade_add_test(DebugToolsLineSegmentRendererTest LineSegmentRendererTest.cpp LIBRARIES MagnumMathTestLib) diff --git a/src/DebugTools/Test/CapsuleRendererTest.cpp b/src/DebugTools/Test/CapsuleRendererTest.cpp new file mode 100644 index 000000000..9f00b1c9a --- /dev/null +++ b/src/DebugTools/Test/CapsuleRendererTest.cpp @@ -0,0 +1,137 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include "DebugTools/Implementation/CapsuleRendererTransformation.h" + +namespace Magnum { namespace DebugTools { namespace Test { + +class CapsuleRendererTest: public TestSuite::Tester { + public: + explicit CapsuleRendererTest(); + + void zeroLength2D(); + void common2D(); + + void zeroLength3D(); + void common3D(); +}; + +CapsuleRendererTest::CapsuleRendererTest() { + addTests({&CapsuleRendererTest::zeroLength2D, + &CapsuleRendererTest::common2D, + + &CapsuleRendererTest::zeroLength3D, + &CapsuleRendererTest::common3D}); +} + +void CapsuleRendererTest::zeroLength2D() { + const Vector2 a(0.5f, 3.0f); + std::array transformation = Implementation::capsuleRendererTransformation<2>(a, a, 3.5f); + + const auto scaling = Math::Matrix<2, Float>::fromDiagonal(Vector2(3.5f)); + CORRADE_COMPARE(transformation[0].rotationScaling(), scaling); + CORRADE_COMPARE(transformation[1].rotationScaling(), (Math::Matrix<2, Float>::fromDiagonal({3.5f, 0.0f}))); + CORRADE_COMPARE(transformation[2].rotationScaling(), scaling); + + CORRADE_COMPARE(transformation[0].translation(), a); + CORRADE_COMPARE(transformation[1].translation(), a); + CORRADE_COMPARE(transformation[2].translation(), a); +} + +void CapsuleRendererTest::common2D() { + const Vector2 a(0.5f, 3.0f); + const Vector2 b(7.5f, -1.0f); + std::array transformation = Implementation::capsuleRendererTransformation<2>(a, b, 3.5f); + + /* Vector from capsule center to top hemisphere center */ + const Vector2 up(3.5f, -2.0f); + CORRADE_COMPARE(transformation[0].up(), up.resized(3.5f)); + CORRADE_COMPARE(transformation[1].up(), up); + CORRADE_COMPARE(transformation[2].up(), up.resized(3.5f)); + + const Vector2 right = Vector2(4.0f, 7.0f).resized(3.5f); + CORRADE_COMPARE(transformation[0].right(), right); + CORRADE_COMPARE(transformation[1].right(), right); + CORRADE_COMPARE(transformation[2].right(), right); + + /* Orthogonality */ + CORRADE_VERIFY(Vector2::dot(transformation[0].up(), transformation[0].right()) < Math::TypeTraits::epsilon()); + + const Vector2 capDistance = up.resized(3.5f); + CORRADE_COMPARE(transformation[0].translation(), a+capDistance); + CORRADE_COMPARE(transformation[1].translation(), 0.5f*(a + b)); + CORRADE_COMPARE(transformation[2].translation(), b-capDistance); +} + +void CapsuleRendererTest::zeroLength3D() { + const Vector3 a(0.5f, 3.0f, 7.0f); + std::array transformation = Implementation::capsuleRendererTransformation<3>(a, a, 3.5f); + + const auto scaling = Math::Matrix<3, Float>::fromDiagonal(Vector3(3.5f)); + CORRADE_COMPARE(transformation[0].rotationScaling(), scaling); + CORRADE_COMPARE(transformation[1].rotationScaling(), (Math::Matrix<3, Float>::fromDiagonal({3.5f, 0.0f, 3.5f}))); + CORRADE_COMPARE(transformation[2].rotationScaling(), scaling); + + CORRADE_COMPARE(transformation[0].translation(), a); + CORRADE_COMPARE(transformation[1].translation(), a); + CORRADE_COMPARE(transformation[2].translation(), a); +} + +void CapsuleRendererTest::common3D() { + const Vector3 a(0.5f, 3.0f, 7.0f); + const Vector3 b(7.5f, -1.0f, 1.5f); + std::array transformation = Implementation::capsuleRendererTransformation<3>(a, b, 3.5f); + + /* Vector from capsule center to top hemisphere center */ + const Vector3 up(3.5f, -2.0f, -2.75f); + CORRADE_COMPARE(transformation[0].up(), up.resized(3.5f)); + CORRADE_COMPARE(transformation[1].up(), up); + CORRADE_COMPARE(transformation[2].up(), up.resized(3.5f)); + + const auto right = Vector3(-2.0f, -3.5f, 0.0f).resized(3.5f); + CORRADE_COMPARE(transformation[0].right(), right); + CORRADE_COMPARE(transformation[1].right(), right); + CORRADE_COMPARE(transformation[2].right(), right); + + const auto backward = Vector3(9.625f, -5.5f, 16.25f).resized(3.5f); + CORRADE_COMPARE(transformation[0].backward(), backward); + CORRADE_COMPARE(transformation[1].backward(), backward); + CORRADE_COMPARE(transformation[2].backward(), backward); + + /* Orthogonality */ + CORRADE_VERIFY(Vector3::dot(transformation[0].up(), transformation[0].right()) < Math::TypeTraits::epsilon()); + CORRADE_VERIFY(Vector3::dot(transformation[0].up(), transformation[0].backward()) < Math::TypeTraits::epsilon()); + CORRADE_VERIFY(Vector3::dot(transformation[0].right(), transformation[0].backward()) < Math::TypeTraits::epsilon()); + + const Vector3 capDistance = up.resized(3.5f); + CORRADE_COMPARE(transformation[0].translation(), a+capDistance); + CORRADE_COMPARE(transformation[1].translation(), 0.5f*(a + b)); + CORRADE_COMPARE(transformation[2].translation(), b-capDistance); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::DebugTools::Test::CapsuleRendererTest)