Help drawing a Superellipse C++ GUI

Pages: 123
Right near the beginning of the thread @integralfx wrote
The parametric equation is much nicer to use, as you don't have to worry about returning two y-ordinates. I'd recommend you use that instead.

That was very perceptive advice and I wholeheartedly agree with him/her. It saves a lot of hassle with two sets of y-coordinates, the points come out in order around the shape, and you can easily loop through the values of theta (using, of course, an integer loop index!)

In this case, with theta ranging from 0 to 2.pi
x = a * (abs(cos theta))2/m * sign(cos theta)
y = b * (abs(sin theta))2/n * sign(sin theta)


I can't help you with the graphics I'm afraid, @dragonosman, but the following code dumps successive coordinates to file shape.txt and you can plot them easily with gnuplot.
a b m n = 1.0 1.0 0.5 0.5 looks quite "astral".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <fstream>
#include <cmath>
#include <vector>
using namespace std;

const double PI = 3.14159265358979323846;

//======================================================================

struct Point{ double x, y; };

//======================================================================

// Return coordinates using PARAMETRIC EQUATION
// Default arguments yield unit circle; first three arguments only give a "normal" ellipse
Point getPoint( double theta, double a = 1.0, double b = 1.0, double m = 2.0, double n = 2.0 )
{
   Point p;
   double c = cos( theta );               
   double s = sin( theta );               
   p.x = a * pow( abs( c ), 2.0 / m );   if ( c < 0 ) p.x = -p.x;
   p.y = b * pow( abs( s ), 2.0 / n );   if ( s < 0 ) p.y = -p.y;
   return p;
}

//======================================================================

int main()
{
   double a, b, m, n;
   int Npoint;
   char filename[] = "shape.txt";

   cout << "Define points for a super-ellipse and write them to file " << filename << "\n\n";
   cout << "The equation for the super-ellipse is\n";
   cout << "   (abs(x)/a)^m + (abs(y)/b)^n = 1\n\n";
   cout << "Input a, b, m, n (all positive): ";
   cin >> a >> b >> m >> n;
   cout << "Input number of points: ";
   cin >> Npoint;
   vector<Point> coords(Npoint+1);               // last point will be same as first

   // Set coordinates
   double dtheta = 2.0 * PI / Npoint;
   for ( int i = 0; i <= Npoint; i++ ) coords[i] = getPoint( i * dtheta, a, b, m, n );

   // Dump points to file for plotting
   ofstream out( filename );
   for ( int i = 0; i <= Npoint; i++ ) out << coords[i].x << "  " << coords[i].y << '\n';
   out.close();
}

//====================================================================== 
Last edited on
Can't I just store the coordinates into variables (or vectors) and then use them to draw the super-ellipse, instead of saving the points to a file?

Anyway, I hope someone will give me pointers/hints on how to actually draw the curves I need to draw.
Can't I just store the coordinates into variables (or vectors) and then use them to draw the super-ellipse, instead of saving the points to a file?


Absolutely!

After line 46 in my code the coordinates are stored in the vector coords and you can do what you like with them.

I only wrote them to file so that I could check them easily by plotting with some other plotting package. (gnuplot has the advantage of being staggeringly quick in this respect: type the line
p "shape.txt" w l

and there you are.)

I admit that I can't actually help you with plotting them, as I don't have this particular book or set of libraries. However, I would strongly advise you to make sure you can just draw a line across the screen before you try mixing it with fancy shapes.
You absolutely don't need a file for anything.

What lastchance said. Get your graphics window initialized and doodle on it with some hard-coded lines a bit. figure out how wide the line needs to be (1 pixel thin is too thin on modern displays..) and test the edges of your drawing area, etc. Then it should not be too hard to write a function that draws a line on the screen, or better yet, on a buffer that will become the screen (then you can draw all the lines at once and drop the object to the screen when done, no visual progressive/iterated drawing effects). From there, making it draw your ellipse should be easy.
Right now, I'm trying to update VS. I'll try this when I'm done.

But anyway, I have a window already and I'm trying to call Line::draw. But I don't know about the thickness of a line or how to control it. Thanks for mentioning it. Do I need to "attach" the lines to the window, or do I just have to draw them and get the thickness right?
closed account (48T7M4Gy)
@Dragonsman

I'm not sure whether you are aware of it but Stroustrups chapters 15 etc demonstrating C++ in relation to graphics/GUI's is based on the use of FLTK which needs to be added to any C++ IDE - VS, XCode, CodeBlocks, QT(?) etc to achieve the same results he gets. QT has it's own systems eg QCustomPlot.

There are other ways to draw lines but FLTX is the way to go if you want to be in sync with the book. His chapters show how to draw lines. If you haven't got FLTK on your system then the free download is at http://www.fltk.org/index.php

Also http://www.stroustrup.com/Programming/PPP2code/Graph.h
I already downloaded it. And I also have the code. But FYI, I'm not on Chapter 15 yet. I'm on Chapter 12 Exercises. The one I'm trying to do right now right now Chapter 12 Exercise 12. Please pay more attention to the opening post (if you look at the comments at the top of my code, you'll notice that it clearly says, "Chapter 12 Exercise 12"). Thank you.
Last edited on
closed account (48T7M4Gy)
LOL, that's the way ...
I built FLTK and am able to compile and link with it, it's just that I don't have full debugging information for some reason. On x86 it says "Partial PDB" and on x64 it has to link it as though there's no debug info at all.

Anyway, yeah, what function do I use to draw the lines and display them? Reference Stroustrup's code that you can get from the book's support site, please.

Anyway, yeah, what function do I use to draw the lines and display them? Reference Stroustrup's code that you can get from the book's support site, please.

You'd use an Open_polyline object.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Point superellipse( double deg, double a, double b, double n )
{
    deg *= M_PI / 180.0;
    return Point(
        a * pow( sin( deg ), 2 / n ),
        b * pow( cos( deg ), 2 / n )
    );
}

constexpr int a{ 100 }, b{ 200 }, n{ 2 }, revolution{ 360 };
Open_polyline fx{};
for( int d{}; d < revolution; d++ )
    fx.add( superellipse( d, a, b, n ) += centre ); // shift superellipse over to centre of window
fx.set_color( Color::blue ); 
fx.set_style( { Line_style::solid, 2 } );
win.attach( fx );


edit: Result: https://puu.sh/v6bjw/12abec9738.png
Last edited on
But that's not a superellipse, that's just an ellipse (different things). The definition for a superellipse is there in comments at the top of my code if you want to look. The equations are posted in this thread by others. I need to update my VS2017 right now, so I don't want to open it yet since it could cause problems if I don't update it. So I can't post my code. But just know that I just want to know how to draw the curves.
closed account (48T7M4Gy)
So I can't post my code.
What's new @DragonOsman, I can't recall you ever posting your code for any of the numerous exercises you get everybody else to write for you. How's your book going by the way?
What are you talking about, there's code in the opening post and other places on this thread isn't there? Don't tell me you just jumped over to this page without reading the opening post? I did the same thing on every other exercise, too. You probably only saw the posts where I said I wouldn't post any code.

I'm on Chapter 12 Exercise 12 and I can't see how to draw the lines. What else?

I'll post my code again, with two comments added in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// Osman Zakir
// 3 / 29 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 12 Exercise 12
// Exercise Specifications:
/**
 * A superellipse is a two-dimensional shape defined by the equation
 *		pow(abs(x/a), m) + pow(abs(y/b), n) = 1, m,n > 0

 * Look up superellipse on the web to get a better idea of what such shapes
 * look like. Write a program that draws “starlike” patterns by connecting
 * points on a superellipse. Take a , b , m , n , and N as arguments. Select N
 * points on the superellipse defined by a , b , m , and n . Make the points
 * equally spaced for some definition of “equal.” Connect each of those N
 * points to one or more other points (if you like you can make the number
 * of points to which to connect a point another argument or just use N – 1 ,
 * i.e., all the other points).
 */

#include "../../Simple_window.h"
#include "../../Graph.h"
#include <cmath>

std::vector<double> superellipse_calc(const double x, const double a,
	const double b, const double n);

int main()
{
	using namespace Graph_lib;
	using namespace std;

	Point tl{ 100, 100 };
	Simple_window win{ tl, 800, 600, "Super-ellipse" };

	try
	{
		constexpr double a = 3.0;
		constexpr double b = 2.0;
		constexpr double n = 2.0;
		constexpr double precision = 1e-2;

		// commented out so it wouldn't give out a warning about an unused variable
		//constexpr int N = 100;

		// -a <= x <= a
		double x = -a;
		while (x < a)
		{
			vector<double> y0 = superellipse_calc(x, a, b, n);
			vector<double> y1 = superellipse_calc(x + precision, a, b, n);

			// negative y
			// bottom half of superellipse
			Line l0
			{
				{int(x), int(y0[0])},
				{int(x + precision), int(y1[1])}
			};

			// positive y
			// top half of superellipse
			Line l1
			{
				{int(x), int(y0[1])},
				{int(x + precision), int(y1[1])}
			};
			x += precision;
			l0.draw();
			l1.draw();
			l0.set_color(Color::black);
			l1.set_color(Color::black);
			win.attach(l0);
			win.attach(l1);
		}
		win.wait_for_button();
	}
	catch (const runtime_error &e)
	{
		Text err_msg_start{ Point{300, 600}, "Runtime_error: " };
		Text err_msg{ Point{400, 600}, e.what() };
		err_msg_start.set_color(Color::black);
		err_msg.set_color(Color::black);

		win.attach(err_msg_start);
		win.attach(err_msg);
		win.wait_for_button();
	}
}

std::vector<double> superellipse_calc(const double x, const double a,
	const double b, const double n)
{
	using namespace std;

	// two y-coordinates for every x-coordinate
	vector<double> y{ 0, 0 };

	// y[0] is negative value, y[1] is positive value
	y[0] = -b * pow(1 - std::pow(std::abs(x / a), n), 1 / n);
	y[1] = b * pow(1 - std::pow(std::abs(x / a), n), 1 / n);

	return y;
}


What should I do with the value for m? Also, the equation shown in the book should be good for this, too, right? I could just get the x and y values with that and check if the result is 1 or not. If it's 1, I return the points from the function and try to draw the superellipse. Otherwise, the function will throw an exception. How about that?
But that's not a superellipse, that's just an ellipse (different things).
Wikipedia wrote:
A superellipse, also known as a Lamé curve after Gabriel Lamé, is a closed curve resembling the ellipse,

The only reason it looks like an ellipse is because I set n to 2. You can make also make m a parameter as well, for it to be "more of a superellipse".
1
2
3
4
5
6
7
8
Point superellipse( double deg, double a, double b, double m, double n )
{
    deg *= M_PI / 180.0;
    return Point(
        a * pow( sin( deg ), 2 / m ),
        b * pow( cos( deg ), 2 / n )
    );
}
Just be careful with the pow() function of potentially negative quantities if m or n is, say 4.0.

@phoenixeyes tried this problem a couple of months back; see:
http://www.cplusplus.com/forum/beginner/208817/#msg983207

His/her method of drawing lines seemed to accord very muchexactly with what @integralfx has shown you.

Last edited on
Okay, I'll think about that.

So um, how about the equation shown in the book?
pow(abs(x/a), m) + pow(abs(y/b), n) = 1, m,n > 0
Maybe I could check if doing pow(abs(x/a), m) + pow(abs(y/b), n) == 1 and return a Point object from that function only if the check evaluates to true. For good measure, a check for whether or not m and n are greater than 0, too.

If I use the same method as @integralfx, where's the N? And for the code I have right now, if I change the equation to resemble the on in the book, would it be good to make a Vector_ref of Points returned from that function if the equation evaluates to 1?

I'm sorry, I'm just trying to wrap my head around all of it and understand the way to do it from the book and also the one being shown here. I want to try it with the equation in the book as well.

Edit:
Note: I'm thinking of trying it both ways, but first I have to fix whatever problem it is that's making my VS2017 not be able to update.
Last edited on

So um, how about the equation shown in the book?
pow(abs(x/a), m) + pow(abs(y/b), n) = 1, m,n > 0

That's the Cartesian equation of a superellipse. What I've shown you is the parametric equation, which is just the Cartesian equation rearranged in such a way that x and y are independent of each other.
For example, the Cartesian equation of a circle is
 
x2 + y2 = r2

while the parametric equation of a circle is
1
2
x = r*cosθ
y = r*sinθ



If I use the same method as @integralfx, where's the N?

We'll deal with N after you can actually draw a superellipse. Basically, you have to select N points on the superellipse and connect those points by lines. This is where the parametric equation comes in; select a random angle between [0, 360], and you'll have a point that lies on the superellipse.
Last edited on
@integralfx: I can't really understand the equation shown on page two on that link. But it says on page 1 that the parametric representation of a super-ellipse is presented by the equation
pow(x, n) / pow(a, n) + pow(y, n) / pow(b, n) = 1, 0 <= x <= a, 0 <= y <= b


I'll stick to the equation you showed me before. But do I replace the superellipse_calc function I have right now with the code you have here:
1
2
3
4
5
6
7
8
Point superellipse( double deg, double a, double b, double m, double n )
{
    deg *= M_PI / 180.0;
    return Point(
        a * pow( sin( deg ), 2 / m ),
        b * pow( cos( deg ), 2 / n )
    );
}


Or do I keep both? And will if I replace my current main function's code with this code?
1
2
3
4
5
6
7
constexpr int a{ 100 }, b{ 200 }, n{ 2 }, revolution{ 360 };
Open_polyline fx{};
for( int d{}; d < revolution; d++ )
    fx.add( superellipse( d, a, b, n ) += centre ); // shift superellipse over to centre of window
fx.set_color( Color::blue ); 
fx.set_style( { Line_style::solid, 2 } );
win.attach( fx );


I'll have to replace "centre" with the correct value, but yeah. Anyway, without those changes, my current code is like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Osman Zakir
// 3 / 29 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 12 Exercise 12
// Exercise Specifications:
/**
 * A superellipse is a two-dimensional shape defined by the equation
 *		pow(abs(x/a), m) + pow(abs(y/b), n) = 1, m,n > 0

 * Look up superellipse on the web to get a better idea of what such shapes
 * look like. Write a program that draws “starlike” patterns by connecting
 * points on a superellipse. Take a , b , m , n , and N as arguments. Select N
 * points on the superellipse defined by a , b , m , and n . Make the points
 * equally spaced for some definition of “equal.” Connect each of those N
 * points to one or more other points (if you like you can make the number
 * of points to which to connect a point another argument or just use N – 1 ,
 * i.e., all the other points).
 */

#include "../../Simple_window.h"
#include "../../Graph.h"
#include <cmath>

std::vector<double> superellipse_calc(const double x, const double a,
	const double b, const double n);

int main()
{
	using namespace Graph_lib;
	using namespace std;

	Point tl{ 100, 100 };
	Simple_window win{ tl, 800, 600, "Super-ellipse" };

	try
	{
		constexpr double a = 3.0;
		constexpr double b = 2.0;
		constexpr double m = 4.0;
		constexpr double n = 9.9;
		constexpr double precision = 1e-2;

		// commented out so it wouldn't give out a warning about an unused variable
		// this is meant to be the constant for determining the number of points on
		// the superellipse
		//constexpr int N = 100;

		// -a <= x <= a
		double x = -a;
		while (x < a)
		{
			vector<double> y0 = superellipse_calc(x, a, b, n);
			vector<double> y1 = superellipse_calc(x + precision, a, b, n);

			// negative y
			// bottom half of superellipse
			Line l0
			{
				{int(x), int(y0[0])},
				{int(x + precision), int(y1[1])}
			};

			// positive y
			// top half of superellipse
			Line l1
			{
				{int(x), int(y0[1])},
				{int(x + precision), int(y1[1])}
			};
			x += precision;
			l0.draw();
			l1.draw();
			l0.set_color(Color::black);
			l1.set_color(Color::black);
		}
		win.wait_for_button();
	}
	catch (const runtime_error &e)
	{
		Text err_msg_start{ Point{300, 600}, "Runtime_error: " };
		Text err_msg{ Point{400, 600}, e.what() };
		err_msg_start.set_color(Color::black);
		err_msg.set_color(Color::black);

		win.attach(err_msg_start);
		win.attach(err_msg);
		win.wait_for_button();
	}
}

std::vector<double> superellipse_calc(const double x, const double a,
	const double b, const double n)
{
	using namespace std;

	// two y-coordinates for every x-coordinate
	vector<double> y{ 0, 0 };

	// y[0] is negative value, y[1] is positive value
	y[0] = -b * pow(1 - std::pow(std::abs(x / a), n), 1 / n);
	y[1] = b * pow(1 - std::pow(std::abs(x / a), n), 1 / n);

	return y;
}


Just let me know what to change here. Thanks.

Edit: Can't add int value to Point value. Flags compiler error.

Edit2: Never mind that. Just help me center the super-ellipse in my window. I'm having trouble with that right now. This is my current main function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int main()
{
	using namespace Graph_lib;
	using namespace std;

	Point tl{ 100, 100 };
	constexpr int win_width = 800;
	constexpr int win_height = 600;
	Simple_window win{ tl, win_width, win_height, "Super-ellipse" };

	try
	{
		constexpr double a = 100.0;
		constexpr double b = 200.0;
		constexpr double m = 100.0;
		constexpr double n = 2.0;
		constexpr int revolution = 360;
		
		Open_polyline fx;

		for (int d = 0; d < revolution; d++)
		{
			fx.add(superellipse(d, a, b, m, n));
		}
		fx.set_color(Color::blue);
		fx.set_style({ Line_style::solid, 2 });
		win.attach(fx);
		win.wait_for_button();
	}
	catch (const runtime_error &e)
	{
		Text err_msg_start{ Point{300, 600}, "Runtime_error: " };
		Text err_msg{ Point{400, 600}, e.what() };
		err_msg_start.set_color(Color::black);
		err_msg.set_color(Color::black);

		win.attach(err_msg_start);
		win.attach(err_msg);
		win.wait_for_button();
	}
}


Please help me out here. I tried to define "center", but I need x-coordinate + win_width and y-coordinate - win_height (right?), which I can't do with superellipse() because it keeps saying that there's no suitable constructor for Graph_lib::Point that takes the arguments I'm trying to give it.
Last edited on

Please help me out here. I tried to define "center", but I need x-coordinate + win_width and y-coordinate - win_height (right?)

The centre of your window would be the point (win_width / 2, win_height / 2).


which I can't do with superellipse() because it keeps saying that there's no suitable constructor for Graph_lib::Point that takes the arguments I'm trying to give it.

Could you provide an example?
Thanks for the correction. It should work now. But anyway, what I tried was this:
 
superellipse(d, a, b, m, n).x + win_width / 2, superellipse(d, a, b, m, n).y - win_height / 2


It kept giving me an error about there being no suitable conversion between an int and a Graph_lib::Point, and when I tried to explicitly cast, it said that no suitable constructor for Graph_lib::Point::Point (that's really what it said) exists for what I'm doing.

Edit: Alright, I finally got something to show up on the window. It's not a superellipse, though. Here's the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// Osman Zakir
// 3 / 29 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 12 Exercise 12
// Exercise Specifications:
/**
 * A superellipse is a two-dimensional shape defined by the equation
 *		pow(abs(x/a), m) + pow(abs(y/b), n) = 1, m,n > 0

 * Look up superellipse on the web to get a better idea of what such shapes
 * look like. Write a program that draws “starlike” patterns by connecting
 * points on a superellipse. Take a , b , m , n , and N as arguments. Select N
 * points on the superellipse defined by a , b , m , and n . Make the points
 * equally spaced for some definition of “equal.” Connect each of those N
 * points to one or more other points (if you like you can make the number
 * of points to which to connect a point another argument or just use N – 1 ,
 * i.e., all the other points).
 */

#include "../../Simple_window.h"
#include "../../Graph.h"
#include <cmath>

Graph_lib::Point superellipse(double deg, double a, double b, double m, double n);

int main()
{
	using namespace Graph_lib;
	using namespace std;

	Point tl{ 100, 100 };
	constexpr int win_width = 800;
	constexpr int win_height = 600;
	Simple_window win{ tl, win_width, win_height, "Super-ellipse" };

	try
	{
		constexpr double a = 100.0;
		constexpr double b = 200.0;
		constexpr double m = 100.0;
		constexpr double n = 2.0;
		constexpr int revolution = 360;
		Graph_lib::Point center{ win_width / 2, win_height / 2 };
		
		Open_polyline fx;

		for (int d = 0; d < revolution; d++)
		{
			fx.add(superellipse(d, a, b, m, n) += center);
		}
		fx.set_color(Color::blue);
		fx.set_style({ Line_style::solid, 2 });
		win.attach(fx);
		win.wait_for_button();
	}
	catch (const runtime_error &e)
	{
		Text err_msg_start{ Point{300, 600}, "Runtime_error: " };
		Text err_msg{ Point{400, 600}, e.what() };
		err_msg_start.set_color(Color::black);
		err_msg.set_color(Color::black);

		win.attach(err_msg_start);
		win.attach(err_msg);
		win.wait_for_button();
	}
}

Graph_lib::Point superellipse(double deg, double a, double b, double m, double n)
{
	constexpr double pi = 3.14159265359;
	deg *= pi / 180.0;
	return Graph_lib::Point{
		int(a * pow(sin(deg), 2 / m)),
		int(b * pow(cos(deg), 2 / n))
	};
}


Screenshot for output: https://1drv.ms/i/s!As6LkLqTe7Ps73FKsoyxGa9JsEO6

What should m be assigned to?
Last edited on
Pages: 123