/* * Software License Agreement (BSD License) * * Copyright (c) 2018. Toyota Research Institute * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of CNRS-LAAS and AIST 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 OWNER 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. */ /** @author Sean Curtis (sean@tri.global) (2018) */ // Tests the implementation of a convex polytope geometry. #include "fcl/geometry/shape/convex.h" #include #include #include #include "eigen_matrix_compare.h" #include "fcl/common/types.h" namespace fcl { namespace { using std::max; // Necessary to satisfy Eigen's alignment requirements. See // http://eigen.tuxfamily.org/dox-devel/group__TopicStlContainers.html#StlContainers_vector template using PoseVector = std::vector, Eigen::aligned_allocator>>; // Utilities to print scalar type in error messages. template struct ScalarString { static std::string value() { return "unknown"; } }; template <> struct ScalarString { static std::string value() { return "double"; } }; template <> struct ScalarString { static std::string value() { return "float"; } }; // Base definition of a "unit" convex polytope. Specific instances should define // faces, vertices, and quantities such as volume, center of mass, and moment of // inertia in terms of a scale factor. template class Polytope { public: explicit Polytope(S scale) : vertices_(std::make_shared>>()), polygons_(std::make_shared>()), scale_(scale) {} Polytope(const Polytope &other) : vertices_(std::make_shared>>(*(other.vertices_))), polygons_(std::make_shared>(*(other.polygons_))), scale_(other.scale_) {} virtual int face_count() const = 0; virtual int vertex_count() const = 0; virtual S volume() const = 0; virtual Vector3 com() const = 0; virtual Matrix3 principal_inertia_tensor() const = 0; virtual std::string description() const = 0; // The scale of the polytope to use with test tolerances. S scale() const { return scale_; } std::shared_ptr>> points() const { return vertices_; } std::shared_ptr> polygons() const { return polygons_; } Convex MakeConvex() const { // The Polytope class makes the pointers to vertices and faces const access. // The Convex class calls for non-const pointers. Temporarily const-casting // them to make it compatible. return Convex(points(), face_count(), polygons()); } Vector3 min_point() const { Vector3 m; m.setConstant(std::numeric_limits::max()); for (const auto& v : *vertices_) { for (int i = 0; i < 3; ++i) { if (v(i) < m(i)) m(i) = v(i); } } return m; } Vector3 max_point() const { Vector3 m; m.setConstant(-std::numeric_limits::max()); for (const auto& v : *vertices_) { for (int i = 0; i < 3; ++i) { if (v(i) > m(i)) m(i) = v(i); } } return m; } Vector3 aabb_center() const { return (max_point() + min_point()) / 2; } S aabb_radius() const { return (min_point() - aabb_center()).norm(); } void SetPose(const Transform3& X_WP) { for (auto& v : *vertices_) { v = X_WP * v; } } protected: void add_vertex(const Vector3& vertex) { vertices_->push_back(vertex); } void add_face(std::initializer_list indices) { polygons_->push_back(static_cast(indices.size())); polygons_->insert(polygons_->end(), indices); } // Confirms the number of vertices and number of polygons matches the counts // implied by vertex_count() and face_count(), respectively. void confirm_data() { // Confirm point count. GTEST_ASSERT_EQ(vertex_count(), static_cast(vertices_->size())); // Confirm face count. // Count the number of faces encoded in polygons_; int count = 0; int i = 0; while (i < static_cast(polygons_->size())) { ++count; i += (*polygons_)[i] + 1; } GTEST_ASSERT_EQ(i, static_cast(polygons_->size())) << "Badly defined polygons"; GTEST_ASSERT_EQ(face_count(), count); } private: std::shared_ptr>> vertices_; std::shared_ptr> polygons_; S scale_{}; }; // A simple regular tetrahedron with edges of length `scale` centered on the // origin. template class EquilateralTetrahedron : public Polytope { public: // Constructs the tetrahedron (of edge length `s`). explicit EquilateralTetrahedron(S scale) : Polytope(scale), scale_(scale) { // Tetrahedron vertices in the tet's canonical frame T. The tet is // "centered" on the origin so that it's center of mass is simple [0, 0, 0]. const S z_base = -1 / S(2 * sqrt(6.)); Vector3 points_T[] = {{S(0.5), S(-0.5 / sqrt(3.)), z_base}, {S(-0.5), S(-0.5 / sqrt(3.)), z_base}, {S(0), S(1. / sqrt(3.)), z_base}, {S(0), S(0), S(sqrt(3. / 8))}}; for (const auto& v : points_T) { this->add_vertex(scale * v); }; // Now add the polygons this->add_face({0, 1, 2}); this->add_face({1, 0, 3}); this->add_face({0, 2, 3}); this->add_face({2, 1, 3}); this->confirm_data(); } // Properties of the polytope. int face_count() const final { return 4; } int vertex_count() const final { return 4; } virtual S volume() const final { // This assumes unit mass. S s = this->scale(); return S(sqrt(2) / 12) * s * s * s; } virtual Vector3 com() const final { return Vector3::Zero(); } virtual Matrix3 principal_inertia_tensor() const { // TODO(SeanCurtis-TRI): Replace this with a legitimate tensor. throw std::logic_error("Not implemented yet"); }; std::string description() const final { return "Tetrahedron with scale: " + std::to_string(this->scale()); } private: S scale_{0}; }; // A simple cube with sides of length `scale`. template class Cube : public Polytope { public: Cube(S scale) : Polytope(scale) { // Cube vertices in the cube's canonical frame C. Vector3 points_C[] = {{S(-0.5), S(-0.5), S(-0.5)}, // v0 {S(0.5), S(-0.5), S(-0.5)}, // v1 {S(-0.5), S(0.5), S(-0.5)}, // v2 {S(0.5), S(0.5), S(-0.5)}, // v3 {S(-0.5), S(-0.5), S(0.5)}, // v4 {S(0.5), S(-0.5), S(0.5)}, // v5 {S(-0.5), S(0.5), S(0.5)}, // v6 {S(0.5), S(0.5), S(0.5)}}; // v7 for (const auto& v : points_C) { this->add_vertex(scale * v); } // Now add the polygons this->add_face({1, 3, 7, 5}); // +x this->add_face({0, 4, 6, 2}); // -x this->add_face({4, 5, 7, 6}); // +y this->add_face({0, 2, 3, 1}); // -y this->add_face({6, 7, 3, 2}); // +z this->add_face({0, 1, 5, 4}); // -z this->confirm_data(); } // Polytope properties int face_count() const final { return 6; } int vertex_count() const final { return 8; } virtual S volume() const final { S s = this->scale(); return s * s * s; } virtual Vector3 com() const final { return Vector3::Zero(); } virtual Matrix3 principal_inertia_tensor() const { S scale_sqd = this->scale() * this->scale(); // This assumes unit mass. return Eigen::DiagonalMatrix(1. / 6., 1. / 6., 1. / 6.) * scale_sqd; }; std::string description() const final { return "Cube with scale: " + std::to_string(this->scale()); } }; void testConvexConstruction() { Cube cube{1}; // Set the cube at some other location to make sure that the interior point // test/ doesn't pass just because it initialized to zero. Vector3 p_WB(1, 2, 3); cube.SetPose(Transform3(Eigen::Translation3d(p_WB))); Convex convex = cube.MakeConvex(); // This doesn't depend on the correct logic in the constructor. But this is // as convenient a time as any to test that it reports the right node type. EXPECT_EQ(convex.getNodeType(), GEOM_CONVEX); // The constructor computes the interior point. EXPECT_TRUE(CompareMatrices(convex.getInteriorPoint(), p_WB)); } template