Add path rendering.
This commit is contained in:
parent
f2ab532005
commit
97afa782a0
39 changed files with 1148 additions and 131 deletions
|
@ -14,6 +14,7 @@ list(APPEND HEADERS
|
|||
path/Line.h
|
||||
path/LineSegment.h
|
||||
path/Path.h
|
||||
path/PathPostScriptConverter.h
|
||||
path/PathElement.h
|
||||
points/Point.h
|
||||
points/PointCollection.h
|
||||
|
@ -34,6 +35,7 @@ list(APPEND SOURCES
|
|||
path/Line.cpp
|
||||
path/LineSegment.cpp
|
||||
path/Path.cpp
|
||||
path/PathPostScriptConverter.cpp
|
||||
path/PathElement.cpp
|
||||
points/Point.cpp
|
||||
points/PointCollection.cpp
|
||||
|
|
|
@ -7,6 +7,81 @@ Line::Line(const Point& start, const PointCollection& points)
|
|||
|
||||
}
|
||||
|
||||
Line::Line(const Point& start, InputBufferType bufferType, const std::vector<double>& buffer)
|
||||
: mStartPoint(start)
|
||||
{
|
||||
if (bufferType == InputBufferType::XY_REL)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < buffer.size(); idx += 2)
|
||||
{
|
||||
const auto x = buffer[idx];
|
||||
const auto y = buffer[idx + 1];
|
||||
mPoints.addPoint(Point(mStartPoint.getX() + x, mStartPoint.getY() + y));
|
||||
}
|
||||
}
|
||||
else if (bufferType == InputBufferType::XY_ABS)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < buffer.size(); idx += 2)
|
||||
{
|
||||
const auto x = buffer[idx];
|
||||
const auto y = buffer[idx + 1];
|
||||
mPoints.addPoint(Point(x, y));
|
||||
}
|
||||
}
|
||||
else if (bufferType == InputBufferType::HORIZONTAL_REL)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < buffer.size(); idx ++)
|
||||
{
|
||||
const auto x = buffer[idx];
|
||||
mPoints.addPoint(Point(mStartPoint.getX() + x, mStartPoint.getY()));
|
||||
}
|
||||
}
|
||||
else if (bufferType == InputBufferType::HORIZONTAL_ABS)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < buffer.size(); idx++)
|
||||
{
|
||||
const auto x = buffer[idx];
|
||||
mPoints.addPoint(Point(x, mStartPoint.getY()));
|
||||
}
|
||||
}
|
||||
else if (bufferType == InputBufferType::VERTICAL_REL)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < buffer.size(); idx++)
|
||||
{
|
||||
const auto y = buffer[idx];
|
||||
mPoints.addPoint(Point(mStartPoint.getX(), mStartPoint.getY() + y));
|
||||
}
|
||||
}
|
||||
else if (bufferType == InputBufferType::VERTICAL_ABS)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < buffer.size(); idx++)
|
||||
{
|
||||
const auto y = buffer[idx];
|
||||
mPoints.addPoint(Point(mStartPoint.getX(), y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Line::toPostScriptString() const
|
||||
{
|
||||
std::string path = "L ";
|
||||
for (const auto& point : mPoints.getPoints())
|
||||
{
|
||||
path += std::to_string(point.getX()) + " " + std::to_string(point.getY()) + " ";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
Point Line::getFirstPoint() const
|
||||
{
|
||||
return getLocation();
|
||||
}
|
||||
|
||||
Point Line::getEndPoint() const
|
||||
{
|
||||
return mPoints.getEndPoint();
|
||||
}
|
||||
|
||||
const PointCollection& Line::getPoints() const
|
||||
{
|
||||
return mPoints;
|
||||
|
|
|
@ -4,12 +4,25 @@
|
|||
#include "PointCollection.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class Line : public PathElement
|
||||
{
|
||||
public:
|
||||
enum class InputBufferType
|
||||
{
|
||||
HORIZONTAL_REL,
|
||||
HORIZONTAL_ABS,
|
||||
VERTICAL_REL,
|
||||
VERTICAL_ABS,
|
||||
XY_REL,
|
||||
XY_ABS
|
||||
};
|
||||
|
||||
Line(const Point& start, const PointCollection& points);
|
||||
|
||||
Line(const Point& start, InputBufferType bufferType, const std::vector<double>& buffer);
|
||||
|
||||
const PointCollection& getPoints() const;
|
||||
|
||||
Line::Type getType() const override;
|
||||
|
@ -18,8 +31,14 @@ public:
|
|||
|
||||
Bounds getBounds() const override;
|
||||
|
||||
Point getFirstPoint() const override;
|
||||
|
||||
Point getEndPoint() const override;
|
||||
|
||||
void sample(SparseGrid<bool>* grid) const override {};
|
||||
|
||||
std::string toPostScriptString() const override;
|
||||
|
||||
private:
|
||||
Point mStartPoint;
|
||||
PointCollection mPoints;
|
||||
|
|
|
@ -32,6 +32,11 @@ void LineSegment::sample(SparseGrid<bool>* grid) const
|
|||
|
||||
}
|
||||
|
||||
std::string LineSegment::toPostScriptString() const
|
||||
{
|
||||
return "L " + std::to_string(mP1.getX()) + " " + std::to_string(mP1.getY());
|
||||
}
|
||||
|
||||
Bounds LineSegment::getBounds() const
|
||||
{
|
||||
const auto minX = std::min(mP0.getX(), mP1.getX());
|
||||
|
@ -47,3 +52,18 @@ const Point& LineSegment::getLocation() const
|
|||
{
|
||||
return mP0;
|
||||
}
|
||||
|
||||
Point LineSegment::getFirstPoint() const
|
||||
{
|
||||
return getPoint0();
|
||||
}
|
||||
|
||||
Point LineSegment::getEndPoint() const
|
||||
{
|
||||
return getPoint1();
|
||||
}
|
||||
|
||||
LineSegment::Type LineSegment::getType() const
|
||||
{
|
||||
return AbstractGeometricItem::Type::LINE_SEGMENT;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "PathElement.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class LineSegment : public PathElement
|
||||
{
|
||||
public:
|
||||
|
@ -21,6 +23,14 @@ public:
|
|||
|
||||
const Point& getLocation() const override;
|
||||
|
||||
Point getFirstPoint() const override;
|
||||
|
||||
Point getEndPoint() const override;
|
||||
|
||||
std::string toPostScriptString() const override;
|
||||
|
||||
Type getType() const override;
|
||||
|
||||
private:
|
||||
Point mP0;
|
||||
Point mP1;
|
||||
|
|
|
@ -1,11 +1,55 @@
|
|||
#include "Path.h"
|
||||
|
||||
Path::~Path()
|
||||
#include "StringUtils.h"
|
||||
#include "FileLogger.h"
|
||||
|
||||
#include "Line.h"
|
||||
#include "LineSegment.h"
|
||||
#include "PathPostScriptConverter.h"
|
||||
|
||||
GeometryPath::~GeometryPath()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
const std::vector<PathElementPtr>& Path::getElements() const
|
||||
const std::vector<GeometryPathFeaturePtr>& GeometryPath::getFeatures() const
|
||||
{
|
||||
return mElements;
|
||||
return mFeatures;
|
||||
}
|
||||
|
||||
void GeometryPath::buildFromPostscript(const std::string& psString)
|
||||
{
|
||||
PathPostScriptConverter converter;
|
||||
converter.fromPostScript(this, psString);
|
||||
}
|
||||
|
||||
void GeometryPath::addFeature(GeometryPathFeaturePtr feature)
|
||||
{
|
||||
mFeatures.push_back(std::move(feature));
|
||||
}
|
||||
|
||||
const Point& GeometryPath::getLocation() const
|
||||
{
|
||||
return mLocation;
|
||||
}
|
||||
|
||||
Bounds GeometryPath::getBounds() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
GeometryPath::Type GeometryPath::getType() const
|
||||
{
|
||||
return GeometryPath::Type::PATH;
|
||||
}
|
||||
|
||||
void GeometryPath::sample(SparseGrid<bool>* grid) const
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::string GeometryPath::getAsPostScript() const
|
||||
{
|
||||
PathPostScriptConverter converter;
|
||||
return converter.toPostScript(this);
|
||||
}
|
|
@ -4,15 +4,60 @@
|
|||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using PathElementPtr = std::unique_ptr<PathElement>;
|
||||
|
||||
class Path : public AbstractGeometricItem
|
||||
class GeometryPathFeature
|
||||
{
|
||||
public:
|
||||
~Path();
|
||||
const std::vector<PathElementPtr>& getElements() const;
|
||||
void addElement(PathElementPtr element)
|
||||
{
|
||||
if (mElements.empty())
|
||||
{
|
||||
mLocation = element->getFirstPoint();
|
||||
}
|
||||
mElements.push_back(std::move(element));
|
||||
}
|
||||
|
||||
const Point& getLocation() const
|
||||
{
|
||||
return mLocation;
|
||||
}
|
||||
|
||||
const std::vector<PathElementPtr>& getElements() const
|
||||
{
|
||||
return mElements;
|
||||
}
|
||||
|
||||
private:
|
||||
Point mLocation;
|
||||
std::vector<PathElementPtr> mElements;
|
||||
};
|
||||
using GeometryPathFeaturePtr = std::unique_ptr<GeometryPathFeature>;
|
||||
|
||||
class GeometryPath : public AbstractGeometricItem
|
||||
{
|
||||
public:
|
||||
~GeometryPath();
|
||||
|
||||
void addFeature(GeometryPathFeaturePtr feature);
|
||||
|
||||
void buildFromPostscript(const std::string& psString);
|
||||
|
||||
std::string getAsPostScript() const;
|
||||
|
||||
const Point& getLocation() const override;
|
||||
|
||||
Bounds getBounds() const override;
|
||||
|
||||
Type getType() const override;
|
||||
|
||||
const std::vector<GeometryPathFeaturePtr>& getFeatures() const;
|
||||
|
||||
void sample(SparseGrid<bool>* grid) const override;
|
||||
|
||||
private:
|
||||
Point mLocation;
|
||||
std::vector<GeometryPathFeaturePtr> mFeatures;
|
||||
};
|
|
@ -2,8 +2,16 @@
|
|||
|
||||
#include "AbstractGeometricItem.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class PathElement : public AbstractGeometricItem
|
||||
{
|
||||
public:
|
||||
~PathElement();
|
||||
|
||||
virtual Point getFirstPoint() const = 0;
|
||||
|
||||
virtual Point getEndPoint() const = 0;
|
||||
|
||||
virtual std::string toPostScriptString() const = 0;
|
||||
};
|
235
src/base/geometry/path/PathPostScriptConverter.cpp
Normal file
235
src/base/geometry/path/PathPostScriptConverter.cpp
Normal file
|
@ -0,0 +1,235 @@
|
|||
#include "PathPostScriptConverter.h"
|
||||
|
||||
#include "Path.h"
|
||||
|
||||
#include "StringUtils.h"
|
||||
#include "FileLogger.h"
|
||||
|
||||
#include "Line.h"
|
||||
#include "LineSegment.h"
|
||||
|
||||
void PathPostScriptConverter::fromPostScript(GeometryPath* targetPath, const std::string& postScriptPath)
|
||||
{
|
||||
mCurrentPoint = Point();
|
||||
|
||||
for (auto c : postScriptPath)
|
||||
{
|
||||
if (c == 'M' || c == 'm')
|
||||
{
|
||||
mLineState = LineState::IN_FIRST_POINT;
|
||||
mPositionState = getPositionState(c);
|
||||
|
||||
mWorkingFeature = std::make_unique<GeometryPathFeature>();
|
||||
}
|
||||
else if (c == 'H' || c == 'h')
|
||||
{
|
||||
onNewElement(c);
|
||||
mLineState = LineState::IN_HORIZONTAL;
|
||||
}
|
||||
else if (c == 'V' || c == 'v')
|
||||
{
|
||||
onNewElement(c);
|
||||
mLineState = LineState::IN_VERTICAL;
|
||||
}
|
||||
else if (c == 'L' || c == 'l')
|
||||
{
|
||||
onNewElement(c);
|
||||
mLineState = LineState::IN_LINE;
|
||||
}
|
||||
else if (c == 'C' || c == 'c')
|
||||
{
|
||||
onNewElement(c);
|
||||
mLineState = LineState::IN_CUBIC_BEZIER;
|
||||
}
|
||||
else if (c == 'Q' || c == 'q')
|
||||
{
|
||||
onNewElement(c);
|
||||
mLineState = LineState::IN_QUADRATIC_BEZIER;
|
||||
}
|
||||
else if (c == 'A' || c == 'a')
|
||||
{
|
||||
onNewElement(c);
|
||||
mLineState = LineState::IN_ARC;
|
||||
}
|
||||
else if (c == 'Z' || c == 'z')
|
||||
{
|
||||
onNewElement(c);
|
||||
|
||||
mLineState = LineState::START;
|
||||
targetPath->addFeature(std::move(mWorkingFeature));
|
||||
mWorkingFeature = nullptr;
|
||||
}
|
||||
else if (std::isblank(c))
|
||||
{
|
||||
onNonNumeric();
|
||||
}
|
||||
else if (std::isalpha(c))
|
||||
{
|
||||
onNewElement(c);
|
||||
mLineState = LineState::IN_UNSUPPORTED;
|
||||
}
|
||||
else
|
||||
{
|
||||
mBuffer.push_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PathPostScriptConverter::onNewElement(char c)
|
||||
{
|
||||
onNonNumeric();
|
||||
|
||||
onElementEnd();
|
||||
|
||||
mPositionState = getPositionState(c);
|
||||
}
|
||||
|
||||
void PathPostScriptConverter::onNonNumeric()
|
||||
{
|
||||
if (!mBuffer.empty())
|
||||
{
|
||||
mPointBuffer.push_back(std::stod(mBuffer));
|
||||
mBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
PathPostScriptConverter::PositionState PathPostScriptConverter::getPositionState(char c) const
|
||||
{
|
||||
return std::isupper(c) ? PositionState::ABSOLUTE : PositionState::RELATIVE;
|
||||
}
|
||||
|
||||
void PathPostScriptConverter::onElementEnd()
|
||||
{
|
||||
if (mPointBuffer.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PathElementPtr element;
|
||||
if (mLineState == LineState::IN_HORIZONTAL)
|
||||
{
|
||||
element = onHorizontalLineTo();
|
||||
}
|
||||
else if (mLineState == LineState::IN_VERTICAL)
|
||||
{
|
||||
element = onVerticalLineTo();
|
||||
}
|
||||
else if (mLineState == LineState::IN_LINE)
|
||||
{
|
||||
element = onLineTo();
|
||||
}
|
||||
else if (mLineState == LineState::IN_FIRST_POINT)
|
||||
{
|
||||
onMoveTo();
|
||||
}
|
||||
|
||||
if (element)
|
||||
{
|
||||
mCurrentPoint = element->getEndPoint();
|
||||
mWorkingFeature->addElement(std::move(element));
|
||||
}
|
||||
|
||||
mPointBuffer.clear();
|
||||
}
|
||||
|
||||
void PathPostScriptConverter::onMoveTo()
|
||||
{
|
||||
if (mPointBuffer.size() != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (mPositionState == PositionState::RELATIVE)
|
||||
{
|
||||
mCurrentPoint = Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY() + mPointBuffer[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentPoint = Point(mPointBuffer[0], mPointBuffer[1]);
|
||||
}
|
||||
}
|
||||
|
||||
PathElementPtr PathPostScriptConverter::onHorizontalLineTo()
|
||||
{
|
||||
PathElementPtr element;
|
||||
if (mPointBuffer.size() == 1)
|
||||
{
|
||||
if (mPositionState == PositionState::RELATIVE)
|
||||
{
|
||||
element = std::make_unique<LineSegment>(mCurrentPoint, Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY()));
|
||||
}
|
||||
else
|
||||
{
|
||||
element = std::make_unique<LineSegment>(mCurrentPoint, Point(mPointBuffer[0], mCurrentPoint.getY()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::HORIZONTAL_REL : Line::InputBufferType::HORIZONTAL_ABS;
|
||||
element = std::make_unique<Line>(mCurrentPoint, buffer_type, mPointBuffer);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
PathElementPtr PathPostScriptConverter::onVerticalLineTo()
|
||||
{
|
||||
PathElementPtr element;
|
||||
if (mPointBuffer.size() == 1)
|
||||
{
|
||||
if (mPositionState == PositionState::RELATIVE)
|
||||
{
|
||||
element = std::make_unique<LineSegment>(mCurrentPoint, Point(mCurrentPoint.getX(), mCurrentPoint.getY() + mPointBuffer[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
element = std::make_unique<LineSegment>(mCurrentPoint, Point(mCurrentPoint.getX(), mPointBuffer[0]));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::VERTICAL_REL : Line::InputBufferType::VERTICAL_ABS;
|
||||
element = std::make_unique<Line>(mCurrentPoint, buffer_type, mPointBuffer);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
PathElementPtr PathPostScriptConverter::onLineTo()
|
||||
{
|
||||
PathElementPtr element;
|
||||
if (mPointBuffer.size() == 2)
|
||||
{
|
||||
if (mPositionState == PositionState::RELATIVE)
|
||||
{
|
||||
const auto next_point = Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY() + mPointBuffer[1]);
|
||||
element = std::make_unique<LineSegment>(mCurrentPoint, next_point);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto next_point = Point(mPointBuffer[0], mPointBuffer[1]);
|
||||
element = std::make_unique<LineSegment>(mCurrentPoint, next_point);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::XY_REL : Line::InputBufferType::XY_ABS;
|
||||
element = std::make_unique<Line>(mCurrentPoint, buffer_type, mPointBuffer);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
std::string PathPostScriptConverter::toPostScript(const GeometryPath* targetPath)
|
||||
{
|
||||
std::string path;
|
||||
for (const auto& feature : targetPath->getFeatures())
|
||||
{
|
||||
auto start_loc = feature->getLocation();
|
||||
path += "M " + std::to_string(start_loc.getX()) + " " + std::to_string(start_loc.getY());
|
||||
|
||||
for (const auto& path_element : feature->getElements())
|
||||
{
|
||||
path += " " + path_element->toPostScriptString();
|
||||
}
|
||||
path += "Z ";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
61
src/base/geometry/path/PathPostScriptConverter.h
Normal file
61
src/base/geometry/path/PathPostScriptConverter.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include "PathElement.h"
|
||||
#include "Point.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class GeometryPath;
|
||||
using PathElementPtr = std::unique_ptr<PathElement>;
|
||||
|
||||
class GeometryPathFeature;
|
||||
using GeometryPathFeaturePtr = std::unique_ptr<GeometryPathFeature>;
|
||||
|
||||
class PathPostScriptConverter
|
||||
{
|
||||
public:
|
||||
void fromPostScript(GeometryPath* targetPath, const std::string& postScriptPath);
|
||||
|
||||
std::string toPostScript(const GeometryPath* targetPath);
|
||||
|
||||
private:
|
||||
enum class LineState
|
||||
{
|
||||
START,
|
||||
IN_FIRST_POINT,
|
||||
IN_HORIZONTAL,
|
||||
IN_VERTICAL,
|
||||
IN_LINE,
|
||||
IN_CUBIC_BEZIER,
|
||||
IN_QUADRATIC_BEZIER,
|
||||
IN_ARC,
|
||||
IN_UNSUPPORTED
|
||||
};
|
||||
|
||||
enum class PositionState
|
||||
{
|
||||
ABSOLUTE,
|
||||
RELATIVE
|
||||
};
|
||||
|
||||
PositionState getPositionState(char c) const;
|
||||
|
||||
void onNewElement(char c);
|
||||
void onNonNumeric();
|
||||
void onElementEnd();
|
||||
|
||||
void onMoveTo();
|
||||
PathElementPtr onHorizontalLineTo();
|
||||
PathElementPtr onVerticalLineTo();
|
||||
PathElementPtr onLineTo();
|
||||
|
||||
LineState mLineState{ LineState::START };
|
||||
PositionState mPositionState{ PositionState::ABSOLUTE };
|
||||
std::string mBuffer;
|
||||
std::vector<double> mPointBuffer;
|
||||
|
||||
GeometryPathFeaturePtr mWorkingFeature;
|
||||
Point mCurrentPoint;
|
||||
};
|
|
@ -14,6 +14,11 @@ void PointCollection::apply(const Transform& transform)
|
|||
}
|
||||
}
|
||||
|
||||
void PointCollection::addPoint(const Point& point)
|
||||
{
|
||||
mPoints.push_back(point);
|
||||
}
|
||||
|
||||
Bounds PointCollection::getBounds() const
|
||||
{
|
||||
Bounds bounds{0.0, 0.0, 0.0, 0.0};
|
||||
|
@ -29,4 +34,21 @@ Bounds PointCollection::getBounds() const
|
|||
bounds.includePoint(point.getX(), point.getY(), point.getZ());
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
Point PointCollection::getEndPoint() const
|
||||
{
|
||||
if (mPoints.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
else
|
||||
{
|
||||
return mPoints[mPoints.size() - 1];
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Point>& PointCollection::getPoints() const
|
||||
{
|
||||
return mPoints;
|
||||
}
|
|
@ -9,10 +9,16 @@ class PointCollection
|
|||
public:
|
||||
PointCollection(const std::vector<Point> points = {});
|
||||
|
||||
void addPoint(const Point& point);
|
||||
|
||||
void apply(const Transform& transform);
|
||||
|
||||
Bounds getBounds() const;
|
||||
|
||||
Point getEndPoint() const;
|
||||
|
||||
const std::vector<Point>& getPoints() const;
|
||||
|
||||
private:
|
||||
std::vector<Point> mPoints;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue