Arc drawing routine

The Partridge Family were neither partridges nor a family. Discuss.
Jezierro
Posts: 36
Joined: March 11th, 2018, 6:02 pm

Arc drawing routine

Post by Jezierro » March 17th, 2019, 11:19 am

Hello

I am looking for better code to draw arc in my program (i want to improve circle clipping)
and make another tool able to operate like splitting circle on two parts.

I was looking for mid point algorithm and i have implemented one but it is not what i am looking for (olny for circle)

Code: Select all

void Graphics::DrawArc(double Ox, double Oy, double R , double theta_begin, double theta_end, Color c)
{

	bool theta_range = theta_end - theta_begin > 0.0;


	if (theta_begin == theta_end)
	{
		DrawCircle(Ox, Oy ,R,c);
	}
	else
	{
		for (double theta = theta_begin;
			theta_range ? theta < theta_end : theta > theta_end;
			theta_range ? theta += 0.2 : theta -= 0.2)
		{
			double x = (double)(R * std::cos(PI_F*theta / 180));
			double y = (double)(R * std::sin(PI_F*theta / 180));

				
			//Draw arc
			PutPixel((int)(x + 0.5f + Ox), (int)(y + 0.5f + Oy), c);
		}
	}
}

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Arc drawing routine

Post by albinopapa » March 17th, 2019, 7:00 pm

Don't know if this is any better, but has some advantages:

Code: Select all

void Graphics::DrawCircle( double _ox, double _oy, double _outer_radius, const Rect<double>& _clip, Color _color ) noexcept
	// For outline thickness of 1
	const auto rSq_inner = Square( _outer_radius - 1.0 );
	const auto rSq_outer = Square( _outer_radius );

	const auto outer_double = _outer_radius * 2.0;

	// Calculate the bounding rectangle of the circle
	const auto left = _ox - _outer_radius;
	const auto top = _oy - _outer_radius;
	const auto right = _ox + _outer_radius;
	const auto bottom = _oy + _outer_radius;
	
	// Clip the bounding rectangle to screen boundaries and translate 
	// back to -radius ( left_clip, top_clip ), +radius ( right_clip, bottom_clip )
	const auto left_clip = std::max( 0.0, -left ) - _outer_radius;
	const auto top_clip = std::max( 0.0, -top ) - _outer_radius;
	const auto right_clip = std::min( ScreenWidth - right, outer_double ) - _outer_radius;
	const auto bottom_clip = std::min( ScreenHeight - bottom, outer_double ) - _outer_radius;

	// Loop through clipped bounding rectangle, from top to bottom,
	// left to right skipping any pixels contained in the _clip Rect passed
	// as parameter to the function
	for( double y = top_clip; y < bottom_clip; ++y )
	{
		for( double x = left_clip; x < right_clip; ++x )
		{
			const auto sqDist = Square( x ) + Square( y );
			if( sqDist > rSq_inner && sqDist < rSq_outer )
			{
				const auto px = x + _ox;
				const auto py = y + _oy;

				if( !_clip.Contains( Vec2<double>{ px, py } ) )
				{
					PutPixel( int( std::round( px ) ), int( std::round( py ) ), _color );
				}
			}
		}
	}
}
Advantages:
  • Doesn't require calculating the begin and end angles before calling draw function.
  • Doesn't require calculating sine or cosine.
  • Only deals with per pixel changes, instead of arbitrary angle increments.
  • Doesn't require calculating square root.
  • Allows for easy screen clipping
Disadvantages:
  • Requires iterating over the entire bounding rectangle.
  • Doesn't actually calculate arcs.
Based on your criteria of just needing to clip a circle ( avoid drawing parts of the circle you want hidden ), this algorithm will work both for clipping against bounding rectangles and against drawing outside the screen boundaries preventing crashes.

Another way you could draw arcs is to use bezier curves. I'll have to refresh my memory a bit before I could show code for them or you can look them up yourself. Best thing about them is no angles, just plotting a few points and interpolating between them.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Arc drawing routine

Post by albinopapa » March 18th, 2019, 4:25 am

There are a couple ways to plot a bezier curve, but this was the simplest for me to understand, you'll have to look up the other way(s). This is just a simple 3 point curve. Given points of a right angle triangle, you'll get a 90 degree arc. You can adjust the segments by changing the step variable. If you want 8 segments, the step would be 1.f / 8.f = .125f. The more segments you have the longer it takes to draw, but the smoother the curve will be. Other than testing that every pixel is on screen, I don't know how to do any clipping using this algorithm.

If you want 180 degree arcs, just add another point to the interpolations.

Code: Select all

void Graphics::DrawBezier( const Vec2f & _p0, const Vec2f & _p1, const Vec2f & _p2, Color _color ) noexcept
{
	auto Lerp = []( const Vec2f& a, const Vec2f& b, float t )
	{
		return a + ( ( b - a ) * t );
	};

	constexpr float step = .01f;
	
	Vec2f p0 = _p0;
	for( float i = step; i <= 1.f; i += step )
	{
		// Interpolate between P0 and P1
		const auto seg0 = Lerp( _p0, _p1, i );

		// Interpolate between P1 and P2
		const auto seg1 = Lerp( _p1, _p2, i );

		// Interpolate between the result of the previous to interpolations
		const auto p1 = Lerp( seg0, seg1, i );

		// There will be spaces between points when the distance between them
		// is large, so you can connect the dots using a line drawing method
		DrawLine( p0, p1, _color );
		p0 = p1;
	}
}
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Jezierro
Posts: 36
Joined: March 11th, 2018, 6:02 pm

Re: Arc drawing routine

Post by Jezierro » March 20th, 2019, 8:21 pm

I have implemented your algorithm but it has to be connected to my points transformation

[attachment=0]Bez tytułu.png[/attachment] the circle is only partly drawn
Attachments
Bez tytułu.png
(19.07 KiB) Not downloaded yet

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Arc drawing routine

Post by albinopapa » March 21st, 2019, 3:53 am

Which algorithm did you end up going with?
...it has to be connected to my points transformation.
What do you mean? Are the end points of the arc suppose to end on the line you drew?

Can you share a snippet of code that might give me an idea of how you implemented the code?
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Jezierro
Posts: 36
Joined: March 11th, 2018, 6:02 pm

Re: Arc drawing routine

Post by Jezierro » March 21st, 2019, 3:58 am

I am using a simple pipeline to transform points from screen coordinates to mathematical coordinates

I have used first one

https://github.com/oBornToCreateo/chili ... sformerh.h

https://github.com/oBornToCreateo/chili ... e/Camera.h

And a most important way to input circles

Code: Select all

	case MainWindow::MWShapeState::TwoPointCircle:
	{
		while (!wnd.mouse.IsEmpty())
		{
			const auto e = wnd.mouse.Read();

			if (e.GetType() == Mouse::Event::Type::LPress)
			{
				if (input == 0)
				{
					
					first_point_engagement = true;
					P = cam.TrasformPoint(wnd.mouse.GetPos());
				}
				if (input == 1)
				{
					Q = wnd.mouse.GetPos();
					Q = cam.TrasformPoint(Q);
					Shapes.push_back(std::make_unique<JC_Circle>(P, Q));
					//Shapes.push_back(std::make_unique<JC_Line>(P, Q));
				}

				input++;

				if (input >= 2)
				{
					input = 0;
					first_point_engagement = false;
				}
			}
			if (e.GetType() == Mouse::Event::Type::RPress)
			{
				input = 0;
				first_point_engagement = false;
			}
		}
		if (first_point_engagement)
		{
			
			Q = cam.TrasformPoint(wnd.mouse.GetPos());
			cam.DrawCircle(P, GetDistanceTo(P, Q), 2, Colors::Red);
			cam.DrawLine(P, Q, Colors::Red);
		}
		break;
}

Code: Select all

	template<typename T2>
	void DrawCircle(JC_Point2<T2> vO, T2 R, int t , Color c)
	{
		RectD Bound{ (double)ScreenWidth ,0,(double)ScreenHeight,0 };
		DrawCircle((double)vO.x, (double)vO.y, (double)R, Bound, t, c);
		
		
		

}

Code: Select all

void DrawCircle(double _ox, double _oy, double _outer_radius, const CRectangle<double>& _clip, int t, Color _color) noexcept;

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Arc drawing routine

Post by albinopapa » March 21st, 2019, 6:16 am

I guess I'll try downloading your project and see what results I get.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Arc drawing routine

Post by albinopapa » March 21st, 2019, 8:15 pm

Ok, found the issue with the messed up circle, the problem was the right_clip and bottom_clip should be:

Code: Select all

	const auto left_clip = std::max(0.0, -left) - _outer_radius;
	const auto top_clip = std::max(0.0, -top) - _outer_radius;
	const auto right_clip = std::min(ScreenWidth - left, outer_double) - _outer_radius;
	const auto bottom_clip = std::min(ScreenHeight - top, outer_double) - _outer_radius;
Seems every time I write this algorithm from scratch, I always mess those last two up.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Arc drawing routine

Post by albinopapa » March 22nd, 2019, 9:04 am

Played around with the order of things for the 3 point bezier curve algorithm and came up with something a little sexier.

Code: Select all

void Graphics::DrawBezier( const JC_Point2d & p0, const JC_Point2d & p1, const JC_Point2d & p2, Color color ) noexcept
{
	const auto range0 = ( p1 - p0 );
	const auto range1 = ( p2 - p1 );
	const auto range2 = ( range1 - range0 );
	const auto doubleRange0 = ( range0 * 2.0 );

	constexpr auto step = .1;
	auto prev = p0;
	for( double t = step; t <= 1.0; t += step )
	{
		const auto p3 = p0 + ( doubleRange0 + ( range2 * t ) ) * t;

		DrawLine( prev, p3, color );
		prev = p3;
	}
}
p0 + ( 2( p1 - p0 ) + ( ( p2 - p1 ) - ( p1 - p0 ) )t )t
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Jezierro
Posts: 36
Joined: March 11th, 2018, 6:02 pm

Re: Arc drawing routine

Post by Jezierro » March 23rd, 2019, 8:33 pm

Thanx mate!

Bezier looks great!

How about skype session to work together?

Post Reply