控制台Linux

时间:2017-12-13 05:18:27

标签: c++ boost graphics

我使用boost几何库来存储多边形,我想在控制台上打印不同的多边形来调试我的几何图形,如矩形,n点多边形。在Linux中有没有可以这样做的库?谢谢!

1 个答案:

答案 0 :(得分:1)

为了纯粹的乐趣,我在昨天(Draw Line using c++ without graphics)修改了Canvas示例并使用了一些Boost Geometry支持。

您可以像以下一样使用它:

int main() {
    using manip::as_geo;

    Polygon poly;
    bg::read_wkt("POLYGON((0 0,0 7,4 2,2 0,0 0))", poly);

    std::cout << as_geo(poly);

    // a polygon with a hole
    std::cout << as_geo<Polygon>("POLYGON((0 0,0 7,4 2,2 0,0 0) (1.5 2.5, 1.5 3.0, 2.5 3.0, 2.5 2.5, 1.5 2.5))");

    // custom canvas size and glyphs
    std::cout << as_geo<60, 30>(poly, '@', '%');
}

将第一个多边形渲染为enter image description here

第二个多边形有一个矩形内环,显示为enter image description here

第三个选项使用自定义画布大小和绘制字形:enter image description here

实施

Utility Vec2

这是一个通用的x / y对类,包含许多转换和基本算术运算(用于缩放,偏移,逻辑 - 物理转换):

namespace Utility {
template <typename T> struct Vec2 {
    T x, y;

    Vec2(T x = {}, T y = {}) : x(x), y(y) {}
    template <typename U, typename V> Vec2(U const& x, V const& y) : x(x), y(y) {}
    template <typename U> Vec2(Vec2<U> const& rhs) : Vec2(rhs.x, rhs.y) {}

    template <typename U> Vec2& operator=(Vec2<U> const& rhs) {
        return *this = {rhs.x, rhs.y};
    }

#define VEC_OPS VEC_DECLARE_OP(*) VEC_DECLARE_OP(/) VEC_DECLARE_OP(+) VEC_DECLARE_OP(-)
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
    Vec2<R> operator op(Vec2<U> const& rhs) const { return {x op rhs.x, y op rhs.y}; }
    VEC_OPS
#undef VEC_DECLARE_OP

#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
    Vec2<R> operator op(U const& rhs) const { return {x op rhs, y op rhs}; }
    VEC_OPS
#undef VEC_DECLARE_OP

#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
    Vec2& operator op##=(U const& rhs) { return operator=((*this) op rhs); }
    VEC_OPS
#undef VEC_DECLARE_OP

  private:
    friend std::ostream& operator<<(std::ostream& os, Vec2 const& xy) {
        return os << "{" << xy.x << "," << xy.y << "}";
    }
};
}

画布类型

让我们从定义一些有用的构建块开始:

using Physical = Utility::Vec2<int>;
using Logical  = Utility::Vec2<double>;
using Scale    = Utility::Vec2<double>;

struct Extents { 
    Logical TopLeft, BottomRight; 

    void normalize() {
        if (TopLeft.y < BottomRight.y) std::swap(TopLeft.y, BottomRight.y);
        if (TopLeft.x > BottomRight.x) std::swap(TopLeft.x, BottomRight.x);
    }

    auto height() const { return std::abs(TopLeft.y - BottomRight.y); }
    auto width() const { return std::abs(BottomRight.x - TopLeft.x); }

    friend std::ostream& operator<<(std::ostream& os, Extents const& e) {
        return os << "{" << e.TopLeft << " - " << e.BottomRight << "; " << e.width() << "×" << e.height() << "}";
    }
};

现在我们可以继续使用画布,主要是the same as in the previous answer

template <int Columns = 100, int Rows = 50>
struct BasicCanvas {
    using Line   = std::array<char, Columns>;
    using Screen = std::array<Line, Rows>;

    static constexpr size_t rows()    { return Rows;    }
    static constexpr size_t columns() { return Columns; }

    BasicCanvas(Logical origin = {Columns/2, Rows/2}, Scale scale = {1.0, 1.0}) : origin(origin), scale(scale) {
        clear();
    }

    BasicCanvas(Extents extents) {
        extents.normalize();

        using Utility::Vec2;
        scale = Vec2{extents.width(), extents.height()} / Vec2{Columns, Rows};
        origin = { -extents.TopLeft.x, -extents.BottomRight.y };

        clear();
    }

    Screen screen;
    Logical origin;
    Scale scale; // physical * scale = logical

    Logical to_logical(Physical const& phys) const  { return (phys * scale) - origin; }
    Physical to_physical(Logical const& log)  const { return (log + origin) / scale; }

    Extents extents() const { return { to_logical({ 0, Rows }), to_logical({ Columns, 0}) }; }

    friend std::ostream& operator<<(std::ostream& os, BasicCanvas const& c) {
        for (auto& line : c.screen) {
            os.write(line.data(), line.size()) << "\n";
        }
        return os;
    }

    Line&       operator[](size_t y)             { return screen.at(screen.size()-(y+1)); }
    Line const& operator[](size_t y) const       { return screen.at(screen.size()-(y+1)); }
    char&       operator[](Physical coord)       { return operator[](coord.y).at(coord.x); }
    char const& operator[](Physical coord) const { return operator[](coord.y).at(coord.x); }

    void clear(char filler = '.') {
        Line empty;
        std::fill(empty.begin(), empty.end(), filler);
        std::fill(screen.begin(), screen.end(), empty);
    }

    void axes() {
        Physical phys_org = to_physical({0,0});

        auto const y_shown = (phys_org.x >= 0 && phys_org.x < Columns);
        auto const x_shown = (phys_org.y >= 0 && phys_org.y < Rows);

        if (y_shown)
            for (auto& line : screen)
                line.at(phys_org.x) = '|';

        if (x_shown) {
            auto& y_axis = operator[](phys_org.y);

            for (auto& cell : y_axis)
                cell = '-';

            if (y_shown)
                y_axis.at(phys_org.x) = '+';
        }
    }

    template <typename F>
    void plot(F f) {
        for (size_t x_tick = 0; x_tick < Columns; ++x_tick) {
            auto x = to_logical({ x_tick, 0 }).x;
            auto y = f(x);
            auto y_ = derivative(f, x, scale.x/2);

            size_t y_tick = to_physical({x, y}).y;
            if (y_tick < Rows)
                operator[]({x_tick, y_tick}) = line_glyph(y_);
        }
    }

  private:
    template <typename F>
    auto derivative(F const& f, double x, double dx = 0.01) {
        return (f(x+dx)-f(x-dx))/(2*dx);
    }

    char line_glyph(double tangent) {
        auto angle = atan(tangent);

        while (angle < 0) 
            angle += 2*M_PI;

        int angle_index = 2.0 * angle / atan(1);

        return R"(--/||\--)"[angle_index % 8];
    }

};
  

注意现在包含一些操作(axes()plot(),这些操作并非严格要求。

支持Boost Geometry

为了解耦,让我们在课外定义绘图操作。

  

出于本演示的目的,我只实现了一个操作fill(geo, filler_char),这意味着我们现在可以处理平面。可以类似于上面的plot操作添加线段和线串,但我建议查看Bresenham Algorithm以避免次优结果。

让我们介绍Boost Geometry:

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/io/io.hpp>

namespace bg = boost::geometry;

using Point   = bg::model::d2::point_xy<double, bg::cs::cartesian>;
using Polygon = bg::model::polygon<Point>;
using Box     = bg::model::box<Point>;

我们将使用Box来检测我们即将绘制的几何体的边界矩形。这样我们就不必担心缩放几何体以适应画布。

假设我们有画布设置,我们可以简单地将fill实现为:

template <typename Canvas, typename G>
void fill(Canvas& canvas, G const& geo, char filler = '*') {
    for (size_t x_tick = 0; x_tick < canvas.columns(); ++x_tick) {
        for (size_t y_tick = 0; y_tick < canvas.rows(); ++y_tick) {
            Physical phys { x_tick, y_tick };
            Logical log = canvas.to_logical(phys);

            if (bg::within(Point(log.x, log.y), geo))
                canvas[{x_tick, y_tick}] = filler;
        }
    }
}
  

注意:目前尚未考虑性能因素

Stream IO Manipulator

要像我们开始使用的样本一样将它们粘合在一起,我们创建一个IO manipulator来处理

  1. 计算边界框
  2. 实例化足够范围的画布以适合边界框
  3. 绘制背景
  4. 填充几何
  5. 可选择从WKT直接解析几何体,在这种情况下,调用者必须指定要反序列化的类型
  6. template <typename Geo, int Columns = 100, int Rows = 50>
    struct geo_manip {
        Geo _geo_or_ref;
        char _filler, _bg;
    
        friend std::ostream& operator<<(std::ostream& os, geo_manip const& gm) {
            Box bounding;
            bg::envelope(gm._geo_or_ref, bounding);
    
            BasicCanvas<Columns, Rows> canvas(Extents {
                    {bounding.max_corner().x(), bounding.max_corner().y()},
                    {bounding.min_corner().x(), bounding.min_corner().y()}});
    
            canvas.clear(gm._bg);
    
            fill(canvas, gm._geo_or_ref, gm._filler);
    
            os << "Canvas extents: " << canvas.extents() << "\n";
            os << "Canvas origin:" << canvas.origin << " scale:" << canvas.scale << "\n";
            return os << canvas;
        }
    };
    

    便利函数as_geo可以轻松创建操纵器,既可以用于现有几何图形(通过引用),也可以解析WKT片段:

    template <int Columns = 100, int Rows = 50, typename Geo>
    geo_manip<Geo const&, Columns, Rows> as_geo(Geo const& geo, char filler = '*', char background = ' ') {
        return {geo, filler, background};
    }
    
    template <typename Geo = Polygon, int Columns = 100, int Rows = 50>
    geo_manip<Geo, Columns, Rows> as_geo(std::string const& wkt, char filler = '*', char background = ' ') {
        Geo geo;
        bg::read_wkt(wkt, geo);
    
        return {geo, filler, background};
    }
    

    全面演示

    <强> Live On Coliru

    #include <iostream>
    #include <array>
    #include <limits>
    #include <cmath>
    
    namespace Utility {
        template <typename T> struct Vec2 {
            T x, y;
    
            Vec2(T x = {}, T y = {}) : x(x), y(y) {}
            template <typename U, typename V> Vec2(U const& x, V const& y) : x(x), y(y) {}
            template <typename U> Vec2(Vec2<U> const& rhs) : Vec2(rhs.x, rhs.y) {}
    
            template <typename U> Vec2& operator=(Vec2<U> const& rhs) {
                return *this = {rhs.x, rhs.y};
            }
    
        #define VEC_OPS VEC_DECLARE_OP(*) VEC_DECLARE_OP(/) VEC_DECLARE_OP(+) VEC_DECLARE_OP(-)
        #define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
            Vec2<R> operator op(Vec2<U> const& rhs) const { return {x op rhs.x, y op rhs.y}; }
            VEC_OPS
        #undef VEC_DECLARE_OP
    
        #define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
            Vec2<R> operator op(U const& rhs) const { return {x op rhs, y op rhs}; }
            VEC_OPS
        #undef VEC_DECLARE_OP
    
        #define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
            Vec2& operator op##=(U const& rhs) { return operator=((*this) op rhs); }
            VEC_OPS
        #undef VEC_DECLARE_OP
    
          private:
            friend std::ostream& operator<<(std::ostream& os, Vec2 const& xy) {
                return os << "{" << xy.x << "," << xy.y << "}";
            }
        };
    }
    
    using Physical = Utility::Vec2<int>;
    using Logical  = Utility::Vec2<double>;
    using Scale    = Utility::Vec2<double>;
    
    struct Extents { 
        Logical TopLeft, BottomRight; 
    
        void normalize() {
            if (TopLeft.y < BottomRight.y) std::swap(TopLeft.y, BottomRight.y);
            if (TopLeft.x > BottomRight.x) std::swap(TopLeft.x, BottomRight.x);
        }
    
        auto height() const { return std::abs(TopLeft.y - BottomRight.y); }
        auto width() const { return std::abs(BottomRight.x - TopLeft.x); }
    
        friend std::ostream& operator<<(std::ostream& os, Extents const& e) {
            return os << "{" << e.TopLeft << " - " << e.BottomRight << "; " << e.width() << "×" << e.height() << "}";
        }
    };
    
    template <int Columns = 100, int Rows = 50>
    struct BasicCanvas {
        using Line   = std::array<char, Columns>;
        using Screen = std::array<Line, Rows>;
    
        static constexpr size_t rows()    { return Rows;    }
        static constexpr size_t columns() { return Columns; }
    
        BasicCanvas(Logical origin = {Columns/2, Rows/2}, Scale scale = {1.0, 1.0}) : origin(origin), scale(scale) {
            clear();
        }
    
        BasicCanvas(Extents extents) {
            extents.normalize();
    
            using Utility::Vec2;
            scale = Vec2{extents.width(), extents.height()} / Vec2{Columns, Rows};
            origin = { -extents.TopLeft.x, -extents.BottomRight.y };
    
            clear();
        }
    
        Screen screen;
        Logical origin;
        Scale scale; // physical * scale = logical
    
        Logical to_logical(Physical const& phys) const  { return (phys * scale) - origin; }
        Physical to_physical(Logical const& log)  const { return (log + origin) / scale; }
    
        Extents extents() const { return { to_logical({ 0, Rows }), to_logical({ Columns, 0}) }; }
    
        friend std::ostream& operator<<(std::ostream& os, BasicCanvas const& c) {
            for (auto& line : c.screen) {
                os.write(line.data(), line.size()) << "\n";
            }
            return os;
        }
    
        Line&       operator[](size_t y)             { return screen.at(screen.size()-(y+1)); }
        Line const& operator[](size_t y) const       { return screen.at(screen.size()-(y+1)); }
        char&       operator[](Physical coord)       { return operator[](coord.y).at(coord.x); }
        char const& operator[](Physical coord) const { return operator[](coord.y).at(coord.x); }
    
        void clear(char filler = '.') {
            Line empty;
            std::fill(empty.begin(), empty.end(), filler);
            std::fill(screen.begin(), screen.end(), empty);
        }
    
        void axes() {
            Physical phys_org = to_physical({0,0});
    
            auto const y_shown = (phys_org.x >= 0 && phys_org.x < Columns);
            auto const x_shown = (phys_org.y >= 0 && phys_org.y < Rows);
    
            if (y_shown)
                for (auto& line : screen)
                    line.at(phys_org.x) = '|';
    
            if (x_shown) {
                auto& y_axis = operator[](phys_org.y);
    
                for (auto& cell : y_axis)
                    cell = '-';
    
                if (y_shown)
                    y_axis.at(phys_org.x) = '+';
            }
        }
    
        template <typename F>
        void plot(F f) {
            for (size_t x_tick = 0; x_tick < Columns; ++x_tick) {
                auto x = to_logical({ x_tick, 0 }).x;
                auto y = f(x);
                auto y_ = derivative(f, x, scale.x/2);
    
                size_t y_tick = to_physical({x, y}).y;
                if (y_tick < Rows)
                    operator[]({x_tick, y_tick}) = line_glyph(y_);
            }
        }
    
      private:
        template <typename F>
        auto derivative(F const& f, double x, double dx = 0.01) {
            return (f(x+dx)-f(x-dx))/(2*dx);
        }
    
        char line_glyph(double tangent) {
            auto angle = atan(tangent);
    
            while (angle < 0) 
                angle += 2*M_PI;
    
            int angle_index = 2.0 * angle / atan(1);
    
            return R"(--/||\--)"[angle_index % 8];
        }
    
    };
    
    #include <boost/geometry.hpp>
    #include <boost/geometry/geometries/point_xy.hpp>
    #include <boost/geometry/geometries/polygon.hpp>
    #include <boost/geometry/geometries/box.hpp>
    #include <boost/geometry/io/io.hpp>
    
    namespace bg = boost::geometry;
    
    using Point   = bg::model::d2::point_xy<double, bg::cs::cartesian>;
    using Polygon = bg::model::polygon<Point>;
    using Box     = bg::model::box<Point>;
    
    template <typename Canvas, typename G>
    void fill(Canvas& canvas, G const& geo, char filler = '*') {
        for (size_t x_tick = 0; x_tick < canvas.columns(); ++x_tick) {
            for (size_t y_tick = 0; y_tick < canvas.rows(); ++y_tick) {
                Physical phys { x_tick, y_tick };
                Logical log = canvas.to_logical(phys);
    
                if (bg::within(Point(log.x, log.y), geo))
                    canvas[phys] = filler;
            }
        }
    }
    
    namespace manip {
    
        template <typename Geo, int Columns = 100, int Rows = 50>
        struct geo_manip {
            Geo _geo_or_ref;
            char _filler, _bg;
    
            friend std::ostream& operator<<(std::ostream& os, geo_manip const& gm) {
                Box bounding;
                bg::envelope(gm._geo_or_ref, bounding);
    
                BasicCanvas<Columns, Rows> canvas(Extents {
                        {bounding.max_corner().x(), bounding.max_corner().y()},
                        {bounding.min_corner().x(), bounding.min_corner().y()}});
    
                canvas.clear(gm._bg);
    
                fill(canvas, gm._geo_or_ref, gm._filler);
    
                os << "Canvas extents: " << canvas.extents() << "\n";
                os << "Canvas origin:" << canvas.origin << " scale:" << canvas.scale << "\n";
                return os << canvas;
            }
        };
    
        template <int Columns = 100, int Rows = 50, typename Geo>
        geo_manip<Geo const&, Columns, Rows> as_geo(Geo const& geo, char filler = '*', char background = ' ') {
            return {geo, filler, background};
        }
    
        template <typename Geo = Polygon, int Columns = 100, int Rows = 50>
        geo_manip<Geo, Columns, Rows> as_geo(std::string const& wkt, char filler = '*', char background = ' ') {
            Geo geo;
            bg::read_wkt(wkt, geo);
    
            return {geo, filler, background};
        }
    }
    
    int main() {
        using manip::as_geo;
    
        Polygon poly;
        bg::read_wkt("POLYGON((0 0,0 7,4 2,2 0,0 0))", poly);
    
        std::cout << as_geo(poly);
    
        // a polygon with a hole
        std::cout << as_geo<Polygon>("POLYGON((0 0,0 7,4 2,2 0,0 0) (1.5 2.5, 1.5 3.0, 2.5 3.0, 2.5 2.5, 1.5 2.5))");
    
        // custom canvas size and glyphs
        std::cout << as_geo<60, 30>(poly, '@', '%');
    }
    

    输出也在coliru上。