【问题标题】:C++ polygon drawing on console Linux [closed]在控制台 Linux 上绘制 C++ 多边形 [关闭]
【发布时间】:2017-12-13 05:18:27
【问题描述】:

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

【问题讨论】:

标签: c++ boost graphics


【解决方案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, '@', '%');
}

将第一个多边形渲染为

第二个多边形有一个矩形内环,显示为

第三个选项使用自定义画布大小和绘图字形:

实施

实用程序 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 几何

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

为了这个演示的目的,我只实现了一个操作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;
        }
    }
}

注意:暂时没有考虑性能因素

流 IO 操纵器

为了像我们开始的示例一样将它们粘合在一起,我们创建了一个 IO manipulator 来处理

  1. 计算边界框
  2. 实例化具有足够范围以适合边界框的画布
  3. 绘制背景
  4. 填充几何体
  5. 可选择直接从 WKT 解析几何,在这种情况下,调用者必须指定要反序列化的类型
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 上。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2017-04-20
  • 1970-01-01
  • 2010-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多