(Full question is listed at the bottom)
I have an assignment that requires me to write text to a postscript file that allows me to draw "Gosper" curves using recursion. However, the test driver (GosperDriver.cpp) my professor has given us resembles the following:
#include "Gosper.h"
#include <iostream>
using namespace std;
int main( )
{
// test right hexagonal Gosper curve at level 4
Gosper gosper1( 100, 100, 0 );
gosper1.rightCurve( 4, 4 );
// test left hexagonal Gosper curver at level 4
Gosper gosper2( 500, 100, 0 );
gosper2.leftCurve( 4, 4 );
// test right hexagonal Gosper curve at level 3
Gosper gosper3( 100, 400, 0 );
gosper3.rightCurve( 3, 6 );
// test left hexagonal Gosper curver at level 3
Gosper gosper4( 500, 400, 0 );
gosper4.leftCurve( 3, 6 );
// test right hexagonal Gosper curve at level 2
Gosper gosper5( 100, 600, 0 );
gosper5.rightCurve( 2, 8 );
// test left hexagonal Gosper curver at level 2
Gosper gosper6( 500, 600, 0 );
gosper6.leftCurve( 2, 8 );
// test right hexagonal Gosper curve at level 1
Gosper gosper7( 100, 700, 0 );
gosper7.rightCurve( 1, 10 );
// test left hexagonal Gosper curver at level 1
Gosper gosper8( 500, 700, 0 );
gosper8.leftCurve( 1, 10 );
}
Gosper.h includes Turtle.h, which contains the "draw" functions which are vital to the project.
Here are my Gosper.h, Gosper.cpp, Turtle.h, and Turtle.cpp files, in that order (I'll cut out the unnecessary code, which controls drawing):
Gosper.h:
// Sierpinski Class
#ifndef GOSPER_H
#define GOSPER_H
#include "Turtle.h"
#include <iostream>
#include <fstream>
using namespace std;
class Gosper : Turtle
{
public:
Gosper(float initX=0.0, float initY=0.0, float initA=0.0);
void leftCurve( int l, float d ); // Draw level l left curve with dist d
void rightCurve( int l, float d ); // Draw level l right curve with dist d
};
#endif
Gosper.cpp:
#include <iostream>
#include <string>
#include "Gosper.h"
// Initialization and such.
Gosper::Gosper(float initX, float initY, float initA)
{
}
void Gosper::leftCurve(int level, float d)
{
// Code that uses draw() function of Turtle.h and Turtle.cpp
}
void Gosper::rightCurve(int level, float d)
{
// Same as above
}
Turtle.h:
#ifndef TURTLE_H
#define TURTLE_H
#include <iostream>
#include <fstream>
#include <math.h>
using namespace std;
const float PI = 3.1459265;
class Turtle {
public:
Turtle(float initX = 0.0, float initY = 0.0, float initA = 0.0);
~Turtle();
void draw( float d ); // draw line by distance d
void move( float d ); // simply move by distance d
void turn( float a ); // turn by angle a
private:
ofstream out;
float angle; // current angle
};
Turtle.cpp:
#include "Turtle.h"
#include <iostream>
#include <fstream>
Turtle::Turtle(float initX, float initY, float initA)
{
out.open("output.ps");
out << "%!PS-Adobe-2.0" << endl;
out << initX << "\t" << initY << "\tmoveto" << endl;
angle = initA;
}
Turtle::~Turtle()
{
out << "stroke" << endl;
out << "showpage" << endl;
}
void Turtle::turn(float a)
{
angle += a;
}
void Turtle::draw(float d)
{
float dX, dY;
dX = d * cos(PI * angle / 180);
dY = d * sin(PI * angle / 180);
out << dX << "\t" << dY << "\trlineto" << endl;
}
void Turtle::move(float d)
{
float dX, dY;
dX = d * cos(PI * angle / 180);
dY = d * sin(PI * angle / 180);
out << dX << "\t" << dY << "\trmoveto" << endl;
}
#endif
Okay, so now that you've seen my code, here's my problem:
I want to write the text for every Gosper class object in GosperDriver.cpp into one postscript file. As it is right now, any attempt to do that will result in the previous block of text in the designated output.ps to be overwritten. At the moment, I can only write the text necessary for ONE Gosper class object. I have had to comment out every Gosper object declaration in Gosperdriver.cpp but one, in order to test if my program is working correctly.
In short, I need to write the text necessary to output.ps for every Gosper object in GosperDriver.cpp, but it isn't working because it will only let me write for one at a time. What do I do?
Bonus question about inheritance: right now, my "starting point" for each Gosper drawing keeps being set at x = 0 and y = 0. As seen by the Gosper object declarations, none of the parameters contain 0 for x or y. Something's gone wonky. What's happening?
Thanks in advance to anyone who can answer one or both of these questions! :)
You can use
out.open("output.ps", std::fstream::in | std::fstream::out | std::fstream::app);
to open the file in append mode. Meaning old content will not be overwritten.
You will however need to add something to detect if the header
out << "%!PS-Adobe-2.0" << endl; has already been written. (I assume you need that exactly once per file.)
To avoid opening and closing the file all the time you could also create a separate class that will open the file, initialize it (write the header) and then use this class to write all your contents and close the file afterwards.
For bonus points use RAII to make the class automatically take care of the file.
Related
Sorry for the massive block of code ;w;
I'm learning C++, and this is an assignment I'm stuck on- I'm supposed to be making a brick wall with alternating rows offset by 1/2 bricks, and I'm supposed to be using a nested loop to do so.
I've got the functions to draw the full brick and half bricks, and I've successfully been able to create a full line of 9 bricks(9 being how many bricks can span the window) and I'm stuck on the next step. I need to make the program draw the next line after the first one is finished, but "\n" and "cout << endl;" only affect the main window and not the "TurtleWindow" that OpenCV opens. All I have in that regard is that it has something to do with the "changePosition" command, but I'm not sure how to add that in to my loop. If I set position to some specific (x, y) coords, then wouldn't it just keep setting the "bricks" in that position every loop?
Any help would be appreciated, I feel like I'm so close to the solution but this is stumping me...
// BrickWall.cpp : This file contains the 'main' function. Program execution begins
//and ends there.
//
#include <iostream>
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <stdio.h>
#include <math.h>
using namespace cv;
using namespace std;
#define M_PI 3.14159265358979323846264338327950288
#define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0)
char wndname[] = "TurtleWindow";
Mat image = Mat::zeros(500, 500, CV_8UC3);
Scalar WHITE(255, 255, 255);
const int DELAY = 1;
Point _curPosition(250, 250);
int _direction = 0;
void init()
{
imshow(wndname, image);
waitKey(DELAY);
}
//
// Move the pen to the given coordinates without leaving a mark
//
// Note (0,0) refers to the upper left corner
// (500,500) refers to the bottom right corner
//
void changePosition(int x, int y)
{
_curPosition.x = x;
_curPosition.y = y;
}
//
// point in the direction given in degrees
// 0 ==> point right
// 90 ==> point down
// 180 ==> point left
// 270 ==> point up
//
void changeDirection(int direction)
{
_direction = direction;
}
//
// Moves the pen forward the given number of pixels
// Note leaves a mark creating a line from the previous point
// to the new point
//
void moveForward(int nPixels)
{
int x = static_cast<int>(round(nPixels * cos(degToRad(_direction))));
int y = static_cast<int>(round(nPixels * sin(degToRad(_direction))));
Point newPoint = Point(x + _curPosition.x, y + _curPosition.y);
line(image, _curPosition, newPoint, WHITE);
_curPosition = newPoint;
// cout << "moved to " << newPoint.x << "," << newPoint.y << endl;
imshow(wndname, image);
waitKey(DELAY);
}
void fullBrick()
// changePosition(25, 25);
{
changeDirection(0);
moveForward(50);
changeDirection(90);
moveForward(20);
changeDirection(180);
moveForward(50);
changeDirection(270);
moveForward(20);
changeDirection(0);
moveForward(50);
}
void halfBrick()
//changePosition(25, 45);
{
changeDirection(0);
moveForward(25);
changeDirection(90);
moveForward(20);
changeDirection(180);
moveForward(25);
changeDirection(270);
moveForward(20);
}
int main()
{
int counter;
int counterWall;
counterWall = 0;
counter = 0;
init();
changePosition(25, 25);
while (counter < 20)
{
do
{
changeDirection(0);
moveForward(50);
changeDirection(90);
moveForward(20);
changeDirection(180);
moveForward(50);
changeDirection(270);
moveForward(20);
changeDirection(0);
moveForward(50);
counterWall++;
} while (counterWall < 9);
counter++;
}
waitKey();
}
edit: thanks everyone for the advice! I was fully intending to use a nested loop in the end(and did use one, so no lost points there!) and was able to realize there was nothing stopping me from looping the bricks like a snake. I had to turn the assignment in before I could try any of the changes you guys offered, but I'll be sure to keep them in mind for future assignments!
and here's a link to the picture of the final product, I've never been happier to see a brick wall in my life
Brick Wall Picture
My task is to find all sub-matrices inside a matrix such that each sub-matrix counted satisfies a certain condition and also is not a part of another sub-matrix that works.
My first thought was to write a recursive procedure so that we could simply return from the current sub-matrix whenever we find that it works (to prevent any sub-matrices of that sub-matrix from being tested). Here is my code that attempts to do this:
void find(int xmin, int xmax, int ymin, int ymax){
if(xmin > xmax || ymin > ymax){return;}
else if(works(xmin, xmax, ymin, ymax)){++ANS; return;}
find(xmin + 1, xmax, ymin, ymax);
find(xmin, xmax - 1, ymin, ymax);
find(xmin, xmax, ymin + 1, ymax);
find(xmin, xmax, ymin, ymax - 1);
}
The problem with my current method seems to be the fact that it allows sub-matrices to be visited more than once, meaning that the return statements are ineffective and don't actually prevent sub-matrices of working sub-matrices from being counted, because they are visited from other matrices. I think I have the right idea with writing a recursive procedure, though. Can someone please point me in the right direction?
Obviously, you need a way to check if a submatrix was evaluated before or is contained in a larger solution. Also, you need to account that after a solution is found, a larger solution may be found later which covers the currently found solution.
One way of doing this is to utilize a structure called R*-tree, which allows to query spatial (or geometric) data efficiently. For this purpose, you could use R-tree implementation from boost. By using a box (rectangle) type to represent a submatrix, you can then use the R-tree with queries boost::geometry::index::contains (to find previously found solutions which include the submatrix considered) and boost::geometry::index::within (to find previously found solutions which are contained within a newly found solution).
Here is a working example in C++11, which is based on your idea:
#include <vector>
#include <numeric>
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/register/point.hpp>
#include <boost/geometry/geometries/register/box.hpp>
#include <boost/geometry/index/rtree.hpp>
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
struct Element
{
int x, y;
};
struct Box
{
Element lt, rb;
};
BOOST_GEOMETRY_REGISTER_POINT_2D(Element, long, cs::cartesian, x, y)
BOOST_GEOMETRY_REGISTER_BOX(Box, Element, lt, rb)
template<class M>
bool works(M&& matrix, Box box) {
// Dummy function to check if sum of a submatrix is divisible by 7
long s = 0;
for (int y=box.lt.y; y<box.rb.y; y++)
for (int x=box.lt.x; x<box.rb.x; x++)
s += matrix[y][x];
return s % 7 == 0;
}
template <class T, class M>
void find(T& tree, M&& matrix, Box box, T& result) {
if (box.lt.x >= box.rb.x || box.lt.y >= box.rb.y) return;
for (auto it = tree.qbegin(bgi::contains(box)); it != tree.qend(); ++it) return;
if (works(matrix, box)) {
// Found a new solution
// Remove any working submatrices which are within the new solution
std::vector<Box> temp;
for (auto it = result.qbegin(bgi::within(box)); it != result.qend(); ++it)
temp.push_back(*it);
result.remove(std::begin(temp), std::end(temp));
// Remember the new solution
result.insert(box);
tree.insert(box);
return;
}
// Recursion
find(tree, matrix, Box{{box.lt.x+1,box.lt.y},{box.rb.x,box.rb.y}}, result);
find(tree, matrix, Box{{box.lt.x,box.lt.y+1},{box.rb.x,box.rb.y}}, result);
find(tree, matrix, Box{{box.lt.x,box.lt.y},{box.rb.x-1,box.rb.y}}, result);
find(tree, matrix, Box{{box.lt.x,box.lt.y},{box.rb.x,box.rb.y-1}}, result);
tree.insert(box);
}
template <class T>
void show(const T& vec) {
for (auto box : vec) {
std::cout << "Start at (" << box.lt.x << ", " << box.lt.y << "), width="
<< box.rb.x-box.lt.x << ", height=" << box.rb.y-box.lt.y << "\n";
}
}
int main()
{
// Initialize R-tree
const size_t rtree_max_size = 20000;
bgi::rtree<Box, bgi::rstar<rtree_max_size> > tree, result;
// Initialize sample matrix
const int width = 4;
const int height = 3;
int matrix[height][width];
std::iota((int*)matrix, (int*)matrix + height*width, 1);
// Calculate result
find(tree, matrix, Box{{0,0},{width,height}}, result);
// Output
std::cout << "Resulting submatrices:\n";
show(result);
return 0;
}
In this example the following matrix is considered:
1 2 3 4
5 6 7 8
9 10 11 12
And the program will find all submatrices for which the sum of their elements is divisible by 7. Output:
Resulting submatrices:
Start at (0, 2), width=4, height=1
Start at (1, 0), width=3, height=3
Start at (0, 0), width=2, height=2
Start at (0, 1), width=1, height=2
What I liked about your recursive approach is that it works very fast even for large matrices of 1000×1000 elements.
I am using SFML and coding in C++. The program I am writing must be a recursive implementation.
My goal is to create a function that recursively draws a square to the screen in different positions and rotations dependent upon the previously drawn square.
Each subsequent square should be smaller than the previous function call and rotated 45 degrees to the left( from the left corner of the previous square ) or 45 to the right of the previous square.
Each new square spawns two more squares etc..
My idea is to pass the upper left point and the upper right point of a square to two different recursive function calls and use these points as starting points for the subsequent squares.
While the squares generated will also pass upper left and right corners to recursive function calls etc..
The code I have developed is not displaying both squares that should have been generated from the recursive function calls. Only one side is being shown.
I have developed the following code (Please forgive my code.. I haven't been coding in C++ for too long..)
DRIVER of PROGRAM ( main.cpp )
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include "PTree.hpp"
using namespace std;
using namespace sf;
int main( int argc, char* argv[ ] )
{
double L = 0.0; // Length of square sides
int N = 0; // Number of times to call recursive function
L = atol( argv[ 1 ] );
N = atoi( argv[ 2 ] );
Vector2f vPoint;
vPoint.x = 0;
vPoint.y = 0;
// Create and Display Window
PTree tree( L, N );
return 0;
}
( PTree.hpp )
#ifndef PTREE_H
#define PTREE_H
using namespace std;
using namespace sf;
class PTree /*:public sf::Drawable, public sf::Transformable*/{
public:
// Constructor
PTree( double L, int N );
// Destructor
~PTree();
// Recursive function to draw Pythagorias Tree
void pTree( double L, int N, Vector2f vPoint, Vector2f vOrigin, float rotation );
private:
float width = 0;
float height = 0;
int originX = 0;
int originY = 0;
float rotation = 0;
RenderWindow window;
int angle1 = 0;
int angle2 = 0;
};
#endif // PTREE_H included
( PTree.cpp )
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <math.h>
#include "PTree.hpp"
#include <iostream>
using namespace std;
using namespace sf;
// Constructor
PTree::PTree( double L, int N )
{
width = ( 6 * L );
height = ( 4 * L );
Vector2f vPoint = { width/2, height - 1 };
Vector2f vOrigin;
vOrigin.x = L/2;
vOrigin.y = L;
/* vPoint.x = width/2;
vPoint.y = height - 1;
*/
window.create( VideoMode( width, height ), "Pythagoras Fractal Tree" );
pTree( L, N, vPoint, vOrigin, 0 );
}
// Destructor
PTree::~PTree(){}
/*###########################################################################*/
// Recursive function to draw Pythagorias Tree
void PTree::pTree( double L, int N, Vector2f vPoint, Vector2f vOrigin, float rotation )
{
Vector2f vPointR;
if( N < 1 )
{
return;
}
// Define a convex shape called convexSquare
ConvexShape convexSquare( 4 );
convexSquare.setPoint( 0, Vector2f( 0, 0 ));
convexSquare.setPoint( 1, Vector2f( 0, L ));
convexSquare.setPoint( 2, Vector2f( L, L ));
convexSquare.setPoint( 3, Vector2f( L, 0 ));
convexSquare.setOutlineThickness( 1.f );
convexSquare.setFillColor( Color::Black );
convexSquare.setOutlineColor( Color::White );
convexSquare.setPosition( vPoint );
convexSquare.setOrigin( vOrigin );
convexSquare.setRotation( rotation );
while( window.isOpen( ))
{
Event event;
while( window.pollEvent( event ))
{
if( event.type == Event::Closed )
{
window.close( );
}
}
if( N >= 0 )
{
window.draw( convexSquare );
window.display( );
L = ( L * ( sqrt(2)/2 ));
N = N - 1;
rotation = rotation - 135;
cout << "LOOPS:" << N << endl;
//left
vPoint = convexSquare.getTransform( ).transformPoint( convexSquare.getPoint( 0 ));
vOrigin = convexSquare.getPoint( (angle1) );
pTree( L, N, vPoint, vOrigin, rotation );
angle1 = (( angle1 + 1 ) % 4 );
//right
vPointR = convexSquare.getTransform( ).transformPoint( convexSquare.getPoint( 3 ));
vOrigin = convexSquare.getPoint( 2 );
pTree( L, N, vPointR, vOrigin, rotation-90 );
}
}
cout << "X value =" << vPoint.x << " Y value = " << vPoint.y << endl;
So far I have tried to return various points of the convex shapes for the second recursive call to the function pTree. This did not display anything either.
Initially I was only using Vector2f vPoint and modifying it prior to each recursive call but after exhausting my knowledge base for a solution I created a new variable specifically for the right side squares called Vector2f vPointR.
The SFML documentation does not provide sufficient examples for noobs like myself. The API is essentially a list of options with minimal examples if any for each function. Ive searched the internet to the best of my ability to see if I am passing the wrong points but could not find an answer.
The one thing that did work ( although not entirely correct ) was when I switched the recursive calls... meaning I moved the call for the right side squares before the call for the left side squares but the problem with this is that the left side s
quares were not displaying.
At this point I am also trying to work out the proper rotation for each square but this is the least of my problems.
Is there an issue with the way I am trying to display these squares recursively?
I am not sure where to go from here other than Stack Overflow for help.
Thanks for your time and expertise.
Don't recursively call the entire while loop. Only recurively call the drawing part
// Initialize window...
while (window.isOpen())
{
sf::Event event;
// Handle events...
window.clear();
// call the recursive function here
window.display();
}
Also you may want to use sf::RectangleShape to draw instead of sf::ConvexShape
Here's a working "example":
#include <SFML/Graphics.hpp>
#include <cmath>
void drawPythagoreanTree(sf::RenderTarget&, const float, const int);
int main()
{
const float L = 150;
const int N = 14;
const unsigned width = static_cast<unsigned>(6 * L);
const unsigned height = static_cast<unsigned>(4 * L);
sf::RenderWindow window{{width, height}, "Pythagorean Tree"};
while (window.isOpen())
{
for (sf::Event event; window.pollEvent(event);)
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear(sf::Color::White);
drawPythagoreanTree(window, L, N);
window.display();
}
}
void drawPythagoreanTree(sf::RenderTarget& target, const int N,
const sf::RectangleShape& parent)
{
static const float halfSqrt2 = sqrt(2.f) / 2;
if (N < 1) return;
target.draw(parent);
auto const& sz = parent.getSize();
auto const& tf = parent.getTransform();
auto childL = parent; // copy parent's color and rotation
childL.setSize(sz * halfSqrt2); // resize
childL.setOrigin(0, childL.getSize().y); // bottom left corner
childL.setPosition(tf.transformPoint({0, 0})); // reposition
childL.rotate(-45);
drawPythagoreanTree(target, N - 1, childL);
auto childR = parent; // copy parent's color and rotation
childR.setSize(sz * halfSqrt2); // resize
childR.setOrigin(childR.getSize()); // bottom right corner
childR.setPosition(tf.transformPoint({sz.x, 0})); // reposition
childR.rotate(45);
drawPythagoreanTree(target, N - 1, childR);
}
void drawPythagoreanTree(sf::RenderTarget& target, const float L, const int N)
{
sf::RectangleShape rect{{L, L}};
// set origin to center of the rect, easier to center position on screen
rect.setOrigin(rect.getSize() / 2.f);
rect.setPosition(target.getSize().x / 2.f, target.getSize().y - L / 2.f);
rect.setFillColor(sf::Color::Black);
drawPythagoreanTree(target, N, rect);
}
I have been trying to make a checkered image.
I can print out everything just fine but I want to find a way to duplicate the shapes of different colors so that I won't have to wast time creating 64 rectangles. I really don't know where to go from here.
#include "Window.h"
#include "Graph.h"
#include "std_lib_facilities_5.h"
#include "Simple_window.h"
#include "FL/Fl_JPEG_Image.H"
int main()
try {
Here is the window being create using the Simple_window struct.
Point tl{ 100,100 };
Simple_window win{ tl,440,440,"Canvas" };
Find the maximum distances than can be given on a window and set the values up as integers (both going in x and y dimensions). x_grid and y_grid are dimensions of a single grid square.
int x_size = win.x_max();
int y_size = win.y_max();
int x_grid = 55;
int y_grid = 55;
Use a Lines class to build the grid. For loops are made to determine where the lines will be placed to form a grid.
Lines grid;
for (int x = x_grid; x < x_size; x += x_grid) {
grid.add(Point{ x,0 }, Point{ x,y_size });
}
for (int y = y_grid; y < y_size; y += y_grid) {
grid.add(Point{ 0,y }, Point{ x_size,y });
}
win.attach(grid);
So here I created two different colors but of the same size using the rectangle class.
So right now I have placed the squared at only one point. However like I said before, I need them to be all over the image. Is there any other way besides making 64 shapes where I can have them copies of the squares? I assume there are some loops involved.
Rectangle rx1(Point(55, 0), 55, 55);
Color moss_green(fl_rgb_color(173, 223, 173));
rx1.set_color(Color::invisible);
rx1.set_fill_color(moss_green);
win.attach(rx1);
Rectangle ry1(Point(0, 0), 55, 55);
Color cherry_blossom_pink(fl_rgb_color(255, 183, 197));
ry1.set_color(Color::invisible);
ry1.set_fill_color(cherry_blossom_pink);
win.attach(ry1);
win.wait_for_button();
}
catch (exception& e) {
cerr << "exception: " << e.what() << endl;
keep_window_open();
}
catch (...) {
cerr << "exception\n";
keep_window_open();
}
I am visualising different sorts of geometries using Qt 5.
There I have a QRect that is either visualised as filled or not.
Now I want to calculate the distance of a QPoint to that rectangle using boost::geometry.
A point within the rectangle should have a distance of 0 when filled, and the distance to the next line when not filled.
Since the documentation of Box does not mention that it is a shape I thought I could use it for this case and adapted the Box concept to QRect.
The following example does not work though, since a Box is treated as shape and therefore always "filled".
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/geometries/register/point.hpp>
#include <QtCore/QPoint>
#include <QtCore/QRect>
BOOST_GEOMETRY_REGISTER_POINT_2D_GET_SET(QPoint, int, boost::geometry::cs::cartesian, x, y, setX, setY);
namespace boost { namespace geometry {
namespace traits
{
template <> struct tag<QRect> { typedef box_tag type; };
template <> struct point_type<QRect> { typedef QPoint type; };
template <std::size_t Index, std::size_t Dimension>
struct indexed_access<QRect, Index, Dimension>
{
typedef typename geometry::coordinate_type<QRect>::type coordinate_type;
static inline coordinate_type get(const QRect &r)
{
if (Index == boost::geometry::min_corner)
return geometry::get<Dimension>(r.topLeft());
else
return geometry::get<Dimension>(r.bottomRight());
}
};
}
}}
double distance(const QPoint &p, const QRect &r, const bool filled)
{
if (filled && r.contains(p))
return 0.0;
else
return boost::geometry::distance(p, r);
}
int main()
{
QRect r(QPoint(0, 0), QPoint(20, 10));
QPoint p(5, 5); // whithin rect
// 0, instead of 5
std::cout << "not filled: " << distance(p, r, false) << '\n';
// 0, as expected
std::cout << "filled: " << distance(p, r, true) << '\n';
}
Run g++ -Wall -O2 -fPIC main.cpp -I/usr/include/qt -lQtCore to build this on Linux.
I could of course use the LineString for the not filled case, though then there would be dynamic allocations.
Unless I create a manualy adaption which uses an underlying QRect, which would be quite some work.
How do I best tackle this issue?
Indeed you're right line-string is required because Box implies a filled shape. Same thing for polygons, actually, in my quick test.
You could of course create a fake "holey" polygon that has an edge of some small width. But that's cheating and certainly less efficient
Indeed, you can use linestring here:
Live On Coliru
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/linestring.hpp>
using namespace boost::geometry;
int main()
{
using Point = model::d2::point_xy<double>;
using Rect = model::linestring<Point>;
Rect rect;
rect.insert(rect.end(), {
Point { 0, 0 },
Point { 10, 0 },
Point { 10, 20 },
Point { 0, 20 },
Point { 0, 0 },
});
std::cout << "distance point within: " << distance(rect, Point(5, 5)) << '\n'; // 0
std::cout << "distance point not within: " << distance(rect, Point(15, 5)) << '\n'; // 5
}
Which prints
distance point within: 5
distance point not within: 5
I don't see any reason to believe that the linestring is less efficient than the polygon (it's basically the same as just the outer ring of a polygon).
However, indeed box testing might be faster. I suggest you profile it. If it's faster, just use the box in case the shape is known to be "filled" and a linestring otherwise.
A relative easy way to support non-filled QRect is to use the LineString concept.
To avoid overhead of a allocations std::array could be used.
Based on the intial code, the following parts need to be added:
#include <array>
using RectLineString = std::array<QPoint, 5>;
BOOST_GEOMETRY_REGISTER_LINESTRING(RectLineString)
double distance(const QPoint &p, const QRect &r, const bool filled)
{
if (filled && r.contains(p))
return 0.0;
else
{
RectLineString rls;
fillRectLineString(rls, rect);
return boost::geometry::distance(p, rls);
}
}
What fillrectLineString should look like depends on how you want to handle the issue that QRect::bottomRight() returns QPoint(rect.x() + rect.width() - 1, rect.y() + rect.height() - 1).
So I provide two versions here:
// bottomRight() is QPoint(rect.x() + rect.width() - 1, rect.y() + rect.height() - 1)
void fillRectLineString1(RectLineString &rls, const QRect &rect)
{
rls[0] = rect.topLeft();
rls[1] = rect.topRight();
rls[2] = rect.bottomRight();
rls[3] = rect.bottomLeft();
rls[4] = rect.topLeft();
}
// bottomRight() is QPoint(rect.x() + rect.width(), rect.y() + rect.height())
void fillRectLineString2(RectLineString &rls, const QRect &rect)
{
rls[0] = QPoint(rect.x(), rect.y());
rls[1] = QPoint(rect.x() + rect.width(), rect.y());
rls[2] = QPoint(rect.x() + rect.width(), rect.y() + rect.height());
rls[3] = QPoint(rect.x(), rect.y() + rect.height());
rls[4] = QPoint(rect.x(), rect.y());
}