/* -*-c++-*- */ /* osgEarth - Geospatial SDK for OpenSceneGraph * Copyright 2020 Pelican Mapping * http://osgearth.org * * osgEarth is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ #ifndef OSGEARTHSYMBOLOGY_GEOMETRY_H #define OSGEARTHSYMBOLOGY_GEOMETRY_H 1 #include #include #include #include #include #include namespace osgEarth { using namespace osgEarth; /** Options for the Geometry::buffer() operation. */ class BufferParameters { public: enum CapStyle { CAP_DEFAULT, CAP_SQUARE, CAP_ROUND, CAP_FLAT }; enum JoinStyle { JOIN_ROUND, JOIN_MITRE, JOIN_BEVEL}; BufferParameters( CapStyle capStyle =CAP_DEFAULT, JoinStyle joinStyle = JOIN_ROUND, int cornerSegs =0, bool singleSided=false, bool leftSide=false ) : _capStyle(capStyle), _joinStyle(joinStyle),_cornerSegs(cornerSegs), _singleSided(singleSided), _leftSide(leftSide) { } CapStyle _capStyle; JoinStyle _joinStyle; int _cornerSegs; // # of line segment making up a rounded corner bool _singleSided; //Whether or not to do a single sided buffer bool _leftSide; //If doing a single sided buffer are we buffering to the left? If false, buffer to the right }; typedef std::vector Vec3dVector; /** * Baseline geometry class. All Geometry objects derive from this * class, even MultiGeometry. */ class OSGEARTH_EXPORT Geometry : public osgEarth::InlineVector { public: enum Type { TYPE_UNKNOWN, TYPE_POINT, TYPE_POINTSET, TYPE_LINESTRING, TYPE_RING, TYPE_POLYGON, TYPE_TRIMESH, TYPE_MULTI }; enum Orientation { ORIENTATION_CCW, ORIENTATION_CW, ORIENTATION_DEGENERATE }; public: Geometry() : Geometry(TYPE_UNKNOWN) { } Geometry(const Geometry& rhs) = default; /** dtor - intentionally public */ virtual ~Geometry(); public: static std::string toString( Type t ) { return t == TYPE_POINT ? "Point" : t == TYPE_POINTSET ? "PointSet" : t == TYPE_LINESTRING ? "LineString" : t == TYPE_RING ? "Ring" : t == TYPE_POLYGON ? "Polygon" : t == TYPE_TRIMESH ? "TriangleMesh" : t == TYPE_MULTI ? "MultiGeometry" : "Unknown"; } /** Creates a geometry from a vector array */ static Geometry* create( Type type, const Vec3dVector* toCopy ); // true if osgEarth is compiled for buffering static bool hasBufferOperation(); public: /** * Gets the total number of points in this geometry. */ virtual int getTotalPointCount() const; /** * Gets the total number of geometry components */ virtual unsigned getNumComponents() const { return 1; } /** * Gets the total number of geometries; it is the total of all parts of all * components. Also can be seen as the number of Geometry objects that would * be returned by a full GeometryIterator. */ virtual unsigned getNumGeometries() const { return 1; } /** * Converts this geometry to another type. This function will return "this" if * the type is the same, and will return NULL if the conversion is impossible. */ virtual Geometry* cloneAs( const Geometry::Type& newType ) const; /** * Creates a new Vec3Array (single-precision), copies the part into it, and * returns the new object. */ osg::Vec3Array* createVec3Array() const; /** * Creates a new Vec3dArray (double-precision), copies the part into it, and * returns the new object. */ osg::Vec3dArray* createVec3dArray() const; /** * Gets the bounds of this geometry */ virtual Bounds getBounds() const; /** * Length of the [outermost] geometry. */ virtual double getLength() const; /** * Whether the geometry is lines */ bool isLinear() const { return getComponentType() == TYPE_LINESTRING || getComponentType() == TYPE_RING; } /** * Runs a buffer (dialate/erode) operation on this geometry and returns the * result in the output parameter. Returns true if the op succeeded. */ bool buffer( double distance, osg::ref_ptr& output, const BufferParameters& bp =BufferParameters() ) const; /** * Crops this geometry to the region represented by the crop polygon, returning * the result in the output parameter. Returns true if the op succeeded. */ bool crop( const class Polygon* cropPolygon, osg::ref_ptr& output ) const; /** * Crops this geometry to the bounds, returning the result in the output parameter. * Returns true if the op succeeded. */ bool crop( const Bounds& bounds, osg::ref_ptr& output) const; /** * Creates the union of this geometry with the other geometry, returning * the result in the output parameter. Returns true if the op succeeded. */ bool geounion( const Geometry* other, osg::ref_ptr& output ) const; /** * Boolean difference - subtracts diffPolygon from this geometry, and put the * result in output. */ bool difference( const class Polygon* diffPolygon, osg::ref_ptr& output ) const; /** * Whether this geometry intersects with another geometry */ bool intersects( const class Geometry* other ) const; /** * Simplifies this geometry, returning the result in the output parameter. */ bool simplify( double tolerance, bool preserveTopology, osg::ref_ptr& output ) const; //! Calculate the signed distance (in the XY plane) from a point //! to this geometry. //! A negative distance indicates that the point is interior //! to a ring or polygon. virtual double getSignedDistance2D( const osg::Vec3d& point) const; /** * Localizes this geometry relative to its centroid, and returns the localization * offset. */ osg::Vec3d localize(); /** * Reverses a call the localize(), given the same offset returned by that method. */ void delocalize( const osg::Vec3d& offset ); /** * Reorders the points in the geometry so that, if the last point was connected * to the first in a ring, they would be would in the specified direction. */ virtual void rewind( Orientation ori ); /** * Makes the last point the same as the first point. Suitable for rings and polygons. */ virtual void close(); virtual void open() { } /** * Removes consecutive duplicates in the geometry to prepare for tessellation. */ virtual void removeDuplicates(); /** * Removes any colinear points, i.e. points that can be safely removed without * affecting the shape/area of the geometry. */ virtual void removeColinearPoints(); /** * Get the winding orientation of the geometry (if you consider the last point * to connect back to the first in a ring.) */ Orientation getOrientation() const; //! Whether a closed geometry contains the 2D point virtual bool contains2D(double x, double y) const { return false; } //! Iterate over all the parts of a geometry (signature = void(Geometry* part)) template inline void forEachPart(bool includePolygonHoles, CALLABLE&& func); template inline void forEachPart(CALLABLE&& func) { forEachPart(true, func); }; //! Iterate over all the parts of a CONST geometry (signature = void(const Geometry* part)) template inline void forEachPart(bool includePolygonHoles, CALLABLE&& func) const; template inline void forEachPart(CALLABLE&& func) const { forEachPart(true, func); }; public: inline Type getType() const { return _type; } virtual Type getComponentType() const { return getType(); } virtual bool isValid() const { return size() >= 1; } virtual Geometry* clone() const { return cloneAs(getType()); } void push_back(const osg::Vec3d& v ) { osgEarth::InlineVector::push_back(v); } void push_back(double x, double y) { osgEarth::InlineVector::push_back(osg::Vec3d(x,y,0.)); } void push_back(double x, double y, double z) { osgEarth::InlineVector::push_back(osg::Vec3d(x,y,z)); } virtual bool isRing() const { return getComponentType() == TYPE_RING || getComponentType() == TYPE_POLYGON; } virtual bool isPolygon() const { return getComponentType() == TYPE_POLYGON; } virtual bool isPointSet() const { return getComponentType()==TYPE_POINT || getComponentType()==TYPE_POINTSET; } virtual bool isLineString() const { return getComponentType() == TYPE_LINESTRING; } virtual bool isOpen() const { return true; } protected: Geometry(Type type, int capacity = 0); Geometry(Type type, const Vec3dVector* toCopy); Type _type = TYPE_UNKNOWN; }; typedef std::vector< osg::ref_ptr > GeometryCollection; /** * An unordered collections of points. */ class OSGEARTH_EXPORT PointSet : public Geometry { public: PointSet(int capacity = 0) : Geometry(TYPE_POINTSET, capacity) { } PointSet(const Vec3dVector* toCopy) : Geometry(TYPE_POINTSET, toCopy) { } PointSet(const PointSet& rhs) = default; // Don't close point sets void close() override { } protected: PointSet(Type type, int capacity = 0) : Geometry(type, capacity) { } PointSet(Type type, const Vec3dVector* toCopy) : Geometry(type, toCopy) { } }; /** * A single point. */ class OSGEARTH_EXPORT Point : public PointSet { public: Point(int capacity = 0) : PointSet(TYPE_POINT, capacity) { } Point(const Vec3dVector* toCopy) : PointSet(TYPE_POINT, toCopy) { } Point(const Point& rhs) = default; void set(const osg::Vec3d& value); // don't close void close() override { } }; /** * An ordered set of points forming a single contiguous line string. */ class OSGEARTH_EXPORT LineString : public Geometry { public: LineString(int capacity = 0) : Geometry(TYPE_LINESTRING, capacity) { } LineString(const Vec3dVector* toCopy) : Geometry(TYPE_LINESTRING, toCopy) { } LineString(const LineString& rhs) = default; bool getSegment(double length, osg::Vec3d& start, osg::Vec3d& end); void close() override; double getSignedDistance2D(const osg::Vec3d& point) const override; public: bool isValid() const override { return size() >= 2; } }; /** * A Ring is a closed region. It is open (the first and last * points are not the same). It has an orientation, i.e. it is either * wound clockwise or counter-clockwise. */ class OSGEARTH_EXPORT Ring : public Geometry { public: Ring(int capacity = 0) : Geometry(TYPE_RING, capacity) { } Ring(const Vec3dVector* toCopy) : Ring(TYPE_RING, toCopy) { } Ring(const Ring& ring) = default; // override virtual Geometry* cloneAs( const Geometry::Type& newType ) const; // tests whether the point falls within the ring bool contains2D( double x, double y ) const override; // gets the signed area of a part that is known to be open. double getSignedArea2D() const; // gets the length of the ring (override) double getLength() const; // ensures that the first and last points are not idential. void open() override; // ensures that the first and last points are identical. void close() override; // whether the ring is open (i.e. first and last points are different) bool isOpen() const override; // opens and winds the ring in the specified direction virtual void rewind(Orientation ori); double getSignedDistance2D(const osg::Vec3d& a) const override; public: virtual Type getType() const { return Geometry::TYPE_RING; } virtual bool isValid() const { return size() >= 3; } protected: Ring(Type type, int capacity = 0) : Geometry(type, capacity) { } Ring(Type type, const Vec3dVector* toCopy); }; typedef std::vector > RingCollection; /** * A Polygon is a geometry that consists of one outer boundary Ring, and * zero or more inner "hole" rings. The boundary ring is would CCW, and the * inner "holes" are wound CW. */ class OSGEARTH_EXPORT Polygon : public Ring { public: Polygon(int capacity = 0) : Ring(TYPE_POLYGON, capacity) { } Polygon(const Vec3dVector* toCopy) : Ring(TYPE_POLYGON, toCopy) { } Polygon(const Polygon& rhs); public: virtual Type getType() const { return Geometry::TYPE_POLYGON; } virtual int getTotalPointCount() const; virtual unsigned getNumGeometries() const { return 1 + _holes.size(); } // tests whether the point falls within the polygon (but not its holes) virtual bool contains2D( double x, double y ) const override; virtual void open(); virtual void close(); virtual void removeDuplicates(); virtual void removeColinearPoints(); virtual double getSignedDistance2D( const osg::Vec3d& a) const override; public: RingCollection& getHoles() { return _holes; } const RingCollection& getHoles() const { return _holes; } protected: RingCollection _holes; }; class OSGEARTH_EXPORT TriMesh : public Geometry { public: TriMesh() : Geometry(TYPE_TRIMESH) { } unsigned getNumGeometries() const override { return 1u; } bool contains2D(double x, double y) const override; public: std::vector _indices; }; /** * A collection of multiple geometries (aka, a "multi-part" geometry). */ class OSGEARTH_EXPORT MultiGeometry : public Geometry { public: MultiGeometry() : Geometry(TYPE_MULTI) { } MultiGeometry(const MultiGeometry& rhs); MultiGeometry(const GeometryCollection& parts); public: Type getComponentType() const override; int getTotalPointCount() const override; unsigned getNumComponents() const override { return _parts.size(); } unsigned getNumGeometries() const override; // gets the combined length of all parts double getLength() const override; // override Geometry* cloneAs( const Geometry::Type& newType ) const override; bool isValid() const override; Bounds getBounds() const override; void rewind( Orientation ori ) override; void removeDuplicates() override; void removeColinearPoints() override; void open() override; void close() override; double getSignedDistance2D(const osg::Vec3d& a) const override; bool contains2D(double x, double y) const override; public: GeometryCollection& getComponents() { return _parts; } const GeometryCollection& getComponents() const { return _parts; } Geometry* add( Geometry* geom ) { _parts.push_back(geom); return geom; } protected: GeometryCollection _parts; }; /** * Iterates over a Geometry object, returning each component Geometry * in turn. The iterator automatically traverses MultiGeometry objects, * returning their components. The iterator NEVER returns an actual * MultiGeometry object. */ class OSGEARTH_EXPORT GeometryIterator { public: //! Constructs a new iterator. //! @param geom Geometry over which to iterator //! @param traversePolyHoles Whether to include polygon holes in the traversal GeometryIterator( Geometry* geom, bool traversePolygonHoles = true); //! Whether next() will return another geometry bool hasMore() const; //! Returns the next geometry part when hasMore() == true Geometry* next(); //! Visits each part and calls a user-defined functor inline void forEach(const std::function& func) { while (hasMore()) func(next()); } private: Geometry* _next; std::queue _stack; bool _traverseMulti; bool _traversePolyHoles; void fetchNext(); }; /** * Iterates over a Geometry object, returning each component Geometry * in turn. The iterator automatically traverses MultiGeometry objects, * returning their components. The iterator NEVER returns an actual * MultiGeometry object. */ class OSGEARTH_EXPORT ConstGeometryIterator { public: //! Null constructor (must call reset before using) ConstGeometryIterator(); //! Constructs a new iterator. //! @param geom Geometry over which to iterator //! @param traversePolyHoles Whether to include polygon holes in the traversal ConstGeometryIterator( const Geometry* geom, bool traversePolygonHoles =true ); //! Sets the iterator to iterate a new geometry void reset(const Geometry* geom, bool traversePolygonHoles = true); //! Whether next() will return another geometry bool hasMore() const; //! Returns the next geometry part when hasMore() == true const Geometry* next(); //! Visits each part and calls a user-defined functor //! Signature = void(const Geometry* part) template inline void forEach(CALLABLE&& func) { //const std::function& func) { while (hasMore()) func(next()); } private: const Geometry* _next = nullptr; std::vector _stack; //std::stack _stack; bool _traverseMulti = true; bool _traversePolyHoles = true; void fetchNext(); }; typedef std::pair Segment; /** * Iterates over geometry, returning each consecutive pair of points * as a line segment. */ class OSGEARTH_EXPORT ConstSegmentIterator { public: //! Construct an iterator. //! @param verts Geometry over which to iterate segments //! @param forceClosedLoop connect the last and first point even if the //! geometry is not a ring. ConstSegmentIterator(const Geometry* verts, bool forceClosedLoop = false) { _verts = &verts->asVector(); _iter = verts->begin(); _done = verts->size() < 2; _closeLoop = forceClosedLoop ? true : verts->getType() == verts->TYPE_RING || verts->getType() == verts->TYPE_POLYGON; } //! Whether next() will return a valid segment inline bool hasMore() const { return !_done; } //! Next segment when hasMore() == true const Segment& next() { _current.first = *_iter++; if (_iter == _verts->end()) { _iter = _verts->begin(); _done = true; } else if (_iter + 1 == _verts->end() && !_closeLoop) { _done = true; } _current.second = *_iter; return _current; } private: const Vec3dVector* _verts; Vec3dVector::const_iterator _iter; bool _done; bool _closeLoop; Segment _current; }; //! Iterate over all the parts of a geometry template inline void Geometry::forEachPart(bool includePolygonHoles, CALLABLE&& func) { GeometryIterator i(this, includePolygonHoles); i.forEach(func); } //! Iterate over all the parts of a geometry template inline void Geometry::forEachPart(bool includePolygonHoles, CALLABLE&& func) const { ConstGeometryIterator i(this, includePolygonHoles); i.forEach(func); } } // namespace osgEarth #endif // OSGEARTHSYMBOLOGY_GEOMETRY_H