-
Moving a series of points to be centered on a given point
I have the 5 points of a star calculated as follows:
Code:
// Calculate the outer points of the star
for(int i = 0; i < m_nPoints; i++)
{
m_dAngle += m_dTheta;
int X = (int)(m_dRadius * cos(m_dAngle));
int Y = (int)(m_dRadius * sin(m_dAngle));
m_ptsOuter[i].x = X;
m_ptsOuter[i].y = Y;
}
Now I want this star to be centered at a point given by m_ptCenter.
How can I move the points defined in m_ptsOuter to be centered on m_ptCenter?
Mike B
-
Re: Moving a series of points to be centered on a given point
Code:
for(int i = 0; i < m_nPoints; i++)
{
m_ptsOuter[i] += m_ptCenter;
}
-
Re: Moving a series of points to be centered on a given point
You need to determine the current center, then update all x,y pairs by delta values of new and old centers:
dx = new_center.x - old_center.x;
dy = new_center.y - old_center.y;
for each point in points do
point.x = point.x + dx;
point.y = point.y + dy;
-
1 Attachment(s)
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by AvDav
You need to determine the current center, then update all x,y pairs by delta values of new and old centers:
dx = new_center.x - old_center.x;
dy = new_center.y - old_center.y;
for each point in points do
point.x = point.x + dx;
point.y = point.y + dy;
Hmmm, well, I should have known this actually. Anyway, I used this method, but the polygon (not drawing star yet) isn't centering on the point.
I have attached an application that draws this polygon in a dialog.
In the dialogs OnPaint() I get the center of the dialog, set the radius of the star, set the center point, call InitStar() and then draw it. It isn't centering on the dialog.
Any ideas?
Mike Bujak
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by MikeB
In the dialogs OnPaint() I get the center of the dialog, set the radius of the star, set the center point, call InitStar() and then draw it. It isn't centering on the dialog.
The flaw is in your CStar::GetContainingRect() method:
Code:
CRect CStar::GetContainingRect()
{
CPoint ptCur = m_ptsOuter[0];
CRect rc (ptCur.x,ptCur.y,ptCur.x,ptCur.y);
for(int i = 1; i < m_nPoints; i++)
{
CPoint ptTemp = m_ptsOuter[i];
if(ptTemp.x < rc.left)
c.left = ptTemp.x;
else if(ptTemp.x > rc.right)
rc.right = ptTemp.x;
else if(ptTemp.y < rc.top)
rc.top = ptTemp.y;
else if(ptTemp.y > rc.bottom)
rc.bottom = ptTemp.y;
}
return rc;
}
Problem is that you are using else-if for those four conditions - however, they are not mutually exclusive: For example, when ptTemp.x happens to be < rc.left, the y coordinates won't be tested any more, resulting in wrong values of your resulting rectangle - and since you are using it for determining the drawing center later on, your shape gets offset.
Besides that: I wonder why you need that step (calculating the containing rect, and then getting its center point) at all? The way you currently create your polygon, the center point is always (0,0) - and if you fix GetContainingRect(), you will see that the center point you get back is always (obviously) (0,0).
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by gstercken
The flaw is in your CStar::GetContainingRect() method:
Code:
CRect CStar::GetContainingRect()
{
CPoint ptCur = m_ptsOuter[0];
CRect rc (ptCur.x,ptCur.y,ptCur.x,ptCur.y);
for(int i = 1; i < m_nPoints; i++)
{
CPoint ptTemp = m_ptsOuter[i];
if(ptTemp.x < rc.left)
c.left = ptTemp.x;
else if(ptTemp.x > rc.right)
rc.right = ptTemp.x;
else if(ptTemp.y < rc.top)
rc.top = ptTemp.y;
else if(ptTemp.y > rc.bottom)
rc.bottom = ptTemp.y;
}
return rc;
}
Problem is that you are using else-if for those four conditions - however, they are not mutually exclusive: For example, when ptTemp.x happens to be < rc.left, the y coordinates won't be tested any more, resulting in wrong values of your resulting rectangle - and since you are using it for determining the drawing center later on, your shape gets offset.
Besides that: I wonder why you need that step (calculating the containing rect, and then getting its center point) at all? The way you currently create your polygon, the center point is always (0,0) - and if you fix GetContainingRect(), you will see that the center point you get back is always (obviously) (0,0).
You are absolutely correct, but I want to ensure that if I ever use this class for anything other then my current project, that a star can in fact be moved from its current position. I know I create the orginal polygon at 0,0, but lets say I move it, then move it again?
Actually come to think of it, you are right, I should not need this step at all. If the user was to move it, all I would have to do is re-create the polygon and reposition its center.
Good point. 5 Stars for you!
Mike B
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by MikeB
Good point. 5 Stars for you!
Thanks - correctly centered ones, I hope? ;)
-
1 Attachment(s)
Re: Moving a series of points to be centered on a given point
Now I am trying to draw the star by the points defining the perimeter of the star shape. To do this, I create 2 polygons, one for the inside points of the star and one for the outside of the star. EG, for a 5 point star, there are a total of 10 points along the perimeter.
I am getting all the points correctly, but when I try to combine the points into one array and call the CDC::Polygon(...) function, the star shape is not correct.. All the points are, but the shape seems inverted or something.
See attached program for all the code:
here is my InitStar() and DrawStar(...) functions for those that do not
want to download project:
Code:
void CStar::InitStar()
{
m_dTheta = (PI*2)/m_nPoints; // Angle to each point, outer circle
if(m_ptsOuter)
delete [] m_ptsOuter;
m_ptsOuter = new CPoint[m_nPoints];
if(m_ptsInner)
delete [] m_ptsInner;
m_ptsInner = new CPoint[m_nPoints];
// Calculate the outer points of the star
for(int i = 0; i < m_nPoints; i++)
{
m_dAngle += m_dTheta;
int X = (int)(m_dRadius * cos(m_dAngle));
int Y = (int)(m_dRadius * sin(m_dAngle));
m_ptsOuter[i].x = X;
m_ptsOuter[i].y = Y;
}
// Calculate the inner points of the star
double dRad = m_dRadius / 2;
double dAngle = m_dAngle + (m_dTheta / 2);
for(i = 0; i < m_nPoints; i++)
{
int X = (int)(dRad * cos(dAngle));
int Y = (int)(dRad * sin(dAngle));
m_ptsInner[i].x = X;
m_ptsInner[i].y = Y;
dAngle += m_dTheta;
}
// Center star points
for(i = 0; i < m_nPoints; i++)
{
m_ptsOuter[i].x += m_ptCenter.x;
m_ptsOuter[i].y += m_ptCenter.y;
m_ptsInner[i].x += m_ptCenter.x;
m_ptsInner[i].y += m_ptCenter.y;
}
}
void CStar::DrawStar(CDC* pDC)
{
CPoint* pts = new CPoint[m_nPoints*2];
int nPoint = 0;
for(int i = 0; i < m_nPoints * 2; i++)
{
if(i % 2 == 0)
{
pts[i] = m_ptsOuter[nPoint];
}
else
{
pts[i] = m_ptsInner[nPoint];
++nPoint;
}
}
for(i = 0; i < m_nPoints * 2; i++)
{
CPoint pt = pts[i];
}
pDC->Polygon(pts, m_nPoints * 2);
delete [] pts;
}
Thanks
Mike B
-
Re: Moving a series of points to be centered on a given point
Update:
I find if I reverse the for loop in the DrawStar(...) function, it is almost correct, but the top of the star goes bad somehow.
Code:
void CStar::DrawStar(CDC* pDC)
{
CPoint* pts = new CPoint[m_nPoints*2];
int nPoint = 4;
for(int i = m_nPoints * 2 - 1; i >= 0; --i)
{
if(i % 2 == 0)
{
pts[i] = m_ptsOuter[nPoint];
}
else
{
pts[i] = m_ptsInner[nPoint];
--nPoint;
}
}
pDC->Polygon(pts, m_nPoints * 2);
delete [] pts;
}
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by MikeB
I find if I reverse the for loop in the DrawStar(...) function, it is almost correct, but the top of the star goes bad somehow.
Looks like trial-and-error, if you ask me... ;)
Again, your code is unnecessarily complicated (and hence error-prone). Why these two separate arrays for inner and outer points, the two separate loops, and later merging both arrays into one? It would be far easier to have one single array of (m_nPoints * 2) elements, and loop from 0 to < (m_nPoints * 2) - the only thing you need to do is to use the inner and outer radius in an alternating way for calculating the point, depending on the value of i % 2, and incrementing the angle by (m_dTheta / 2). This automatically gets you the polygon points in the right order, without having to deal with three arrays.
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by gstercken
Looks like trial-and-error, if you ask me... ;)
Again, your code is unnecessarily complicated (and hence error-prone). Why these two separate arrays for inner and outer points, the two separate loops, and later merging both arrays into one? It would be far easier to have one single array of (m_nPoints * 2) elements, and loop from 0 to < (m_nPoints * 2) - the only thing you need to do is to use the inner and outer radius in an alternating way for calculating the point, depending on the value of i % 2, and incrementing the angle by (m_dTheta / 2). This automatically gets you the polygon points in the right order, without having to deal with three arrays.
LOL, easy enough, you are the man. 5 More stars for you, correctly centered and drawn!!!
:thumb:
Code:
void CStar::InitStar()
{
m_dTheta = (PI*2)/m_nPoints; // Angle to each point, outer circle
if(m_ptsOuter)
delete [] m_ptsOuter;
m_ptsOuter = new CPoint[m_nPoints*2];
// Calculate the outer points of the star
for(int i = 0; i < m_nPoints * 2; i++)
{
double dRad = i%2==0 ? m_dRadius : m_dRadius/2;
int X = (int)(dRad * cos(m_dAngle));
int Y = (int)(dRad * sin(m_dAngle));
m_ptsOuter[i].x = X;
m_ptsOuter[i].y = Y;
m_dAngle += m_dTheta/2;
}
// Center star points
for(i = 0; i < m_nPoints*2; i++)
{
m_ptsOuter[i].x += m_ptCenter.x;
m_ptsOuter[i].y += m_ptCenter.y;
}
}
void CStar::DrawStar(CDC* pDC)
{
pDC->Polygon(m_ptsOuter, m_nPoints * 2);
}
Mike B
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by MikeB
5 More stars for you, correctly centered and drawn!!!
Thanks... :) :D
Btw, one more suggestion for further optimizing (and simplifying) your code: Instead of deleting and reallocating your point array each time you draw the shape, you could move this to the ctor (where the number of points is already known). The only other place where you would then need to delete and reallocate the array is in your setter for the number of points (SetPoints()).
-
1 Attachment(s)
Re: Moving a series of points to be centered on a given point
Here is the working app so far. I am only posting this in the case
that someone else is interested ever in doing the same type of thing.
The purpose of this "star" class is to be able to display customer ratings
based on a 5 star systems. I know I could have just displayed this numerically but my application is already lacking "fun" so I thought I would
implement a graphical display of the star system.
Anyway, now I need to come up with a way to paint these stars either in whole or partial. I know I can find the intersecting points of a of a dividing line, say at 25 percent to indicate a 1/4 star, and draw two polygons (the left and right) of the star, but does anyone have an easier method. As you can see by the previous posts, I am not one to take the easy route out.
Any ideas?
Mike B
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by gstercken
Thanks... :) :D
Btw, one more suggestion for further optimizing (and simplifying) your code: Instead of deleting and reallocating your point array each time you draw the shape, you could move this to the ctor (where the number of points is already known). The only other place where you would then need to delete and reallocate the array is in your setter for the number of points (SetPoints()).
I have moved the allocation to set points, which makes sense. But the points were not calculated each time it was drawn. Only when InitStar() was called which builds the star.
Should I call InitStar from inside all my properties set functions? Or let it be that it is called at the time of creation?
Mike B
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by MikeB
The purpose of this "star" class is to be able to display customer ratings
based on a 5 star systems. I know I could have just displayed this numerically but my application is already lacking "fun" so I thought I would
implement a graphical display of the star system.
That's fine - but honestly, in a real-world app, I would do this with pre-drawn bitmaps for the stars... :) I assume that you need partially filled stars for displaying fractional ratings, right? And having only 4 differently filled star bitmaps (0%, 25%, 50% and 100%) already gives you 20 different levels with 5 stars - pretty much for any rating system.
But for the heck of it: The geometrical approach you mentioned (finding the intersecting line and drawing two separate filled polygons seems indeed to be the easiest - especially since you're already bothered with the math to dynamically calculate the polygon points. Any other approach would either use offscreen DCs and clipping regions, or temporary offscreen bitmaps and blitting with ternary ROP codes to combine certain bits of say, the polygon bitmap and a rectangle that determines the fill state. While they involve less geometry math, I estimate that the boilerplate code you need to make them work would be more, in the end, than the approach you mentioned yourself. In any case: There is no simple GDI approach for filling a polygon only partially.
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by MikeB
Should I call InitStar from inside all my properties set functions? Or let it be that it is called at the time of creation?
No, consequently, you would need to call InitStar() from any setter method that would require to recalculate the points. But this is still far better than calling it every time a WM_PAINT message is sent to your window. :)
-
Re: Moving a series of points to be centered on a given point
I haven't tried this, but for drawing your partially filled stars, I would consider using a clipping region.
Set up the clipping region to cover the entire star from top to bottom, but only the first 25% from left to right. Then draw the "complete" star (no need to change your drawing code) with a given brush.
Next, move the left and right limits of the clipping region to conver the last 75% from left to right. Draw the "complete" star again with a different brush.
I think that might be quicker for you.
Hope that at least gives you an idea.
Good luck.
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by krmed
I haven't tried this, but for drawing your partially filled stars, I would consider using a clipping region.
Good point - you're right, using a clipping region would work even without offscreen DCs. You just need to be careful to properly combine it with the current clipping region when drawing to the DC - this can be quite a mess...
But another idea, Mike: If switching to GDI+ is an option for you, then you could use linear gradient fills - and let the gradient ramp determine the horizontal fill degree of the stars.
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by krmed
I haven't tried this, but for drawing your partially filled stars, I would consider using a clipping region.
Set up the clipping region to cover the entire star from top to bottom, but only the first 25% from left to right. Then draw the "complete" star (no need to change your drawing code) with a given brush.
Next, move the left and right limits of the clipping region to conver the last 75% from left to right. Draw the "complete" star again with a different brush.
I think that might be quicker for you.
Hope that at least gives you an idea.
Good luck.
Yeah, this is pretty much what I was thinking of, not in the same terms, but basically the same. It is a good idea, and would be better then trying to find intersection points, then creating idividual polygons.
Thanks
Mike B
P.S., if you try the attached app, take it from me, do not try to creat a star
with 100,000 points. It crashed my laptop.
-
Re: Moving a series of points to be centered on a given point
Thanks.
What I'd probably do is in my drawing function, I would call another function designed to draw stars. In that DrawStars, I would set up the clipping region for only the stars and draw them (save the previous clipping region when you SelectObject for your star clipping region).
Then, as the last thing in DrawStars, restore the original clipping region and return to the main drawing routine.
Just a thought.
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by gstercken
Good point - you're right, using a clipping region would work even without offscreen DCs. You just need to be careful to properly combine it with the current clipping region when drawing to the DC - this can be quite a mess...
But another idea, Mike: If switching to GDI+ is an option for you, then you could use linear gradient fills - and let the gradient ramp determine the horizontal fill degree of the stars.
Thanks, I will have to look into this as well. I have never attempted to use GDI+. Looks like I have some reading an experimenting to do! :)
Mike B
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by krmed
Thanks.
What I'd probably do is in my drawing function, I would call another function designed to draw stars. In that DrawStars, I would set up the clipping region for only the stars and draw them (save the previous clipping region when you SelectObject for your star clipping region).
Then, as the last thing in DrawStars, restore the original clipping region and return to the main drawing routine.
Just a thought.
Thanks alot for all the help hrmed and gstercken. I do need a little more if you can spare the thought and time though.
I am not sure how to use regions. I have tried to use ExcludeClipRgn and this works for partial, but I am not able to understand how to get it to work totally.
I create to rectangles, one for each the left and right portions of the star.
I call these rects, rcFillRgn and rcNoFillRgn.
I call pDC->ExcludeClipRgn(&rcNoFillRgn) to draw the yellow portion of the star. This should draw the star leaving out the right side.
I then call pDC->ExcludeClipRgn(&rcFillRgn) to draw the right side, excluding the left.
The left portion gets drawn but the right side does not. I think it has something to do with restoring to the original clip region prior to the second call to ExcludeClipRgn() but I am not sure how to do this.
Code:
void CStar::DrawStar(CDC* pDC)
{
CRect rcFillRgn(GetContainingRect());
CRect rcNoFillRgn(rcFillRgn);
rcFillRgn.right = rcFillRgn.left + (int)(rcFillRgn.Width() * 0.75);
rcNoFillRgn.left = rcFillRgn.right;
pDC->ExcludeClipRect(&rcNoFillRgn);
CBrush brYellow(RGB(255,255,0));
CBrush brWhite(RGB(255,255,255));
CBrush* pOldBrush = pDC->SelectObject(&brYellow);
pDC->Polygon(m_ptsOuter, m_nPoints * 2);
// I probably have to restore to the original clip region prior to
this call, but I am unsure how to do this.
// pDC->ExcludeClipRect(&rcFillRgn);
// pDC->SelectObject(&brWhite);
// pDC->Polygon(m_ptsOuter, m_nPoints * 2);
pDC->SelectObject(pOldBrush);
}
Mike
-
Re: Moving a series of points to be centered on a given point
I think what you've done is excluded the rcNoFillRgn from the DC, so you won't draw on it. Then when you exclude the rcFillRgn, it ADDITIONALLY excludes that area (the noFillRgn is not added back in).
Instead I believe your should use a CRgn to create a region for your nofill and your fill areas (only).
Then use something like this: (You'll need to create your rects properly of course)
Code:
temp_rect = data_box;
CRgn oldRgn;
rcNoFillRgn.right -= m_iPpiHorz / 16;
rcNoFillRgn.bottom = data_text.y + text_string_size.cy;
clip_rgn.CreateRectRgnIndirect(&rcNoFillRgn);
pDc->GetClipRgn(hDC, &oldRgn);
pDc->SelectClipRgn(&clip_rgn);
// Draw your fill area
pDc->SelectClipRgn(&rcFillRgn);
// Draw your no fill area
pDc->SelectClipRgn(&oldRgn);
Hope that helps.
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by krmed
I think what you've done is excluded the rcNoFillRgn from the DC, so you won't draw on it. Then when you exclude the rcFillRgn, it ADDITIONALLY excludes that area (the noFillRgn is not added back in).
That's basically what I meant when I said:
Quote:
Originally Posted by gstercken
You just need to be careful to properly combine it with the current clipping region when drawing to the DC - this can be quite a mess...
;)
-
Re: Moving a series of points to be centered on a given point
gstercken:
I couldn't agree more!
I knew that, but I think MikeB missed it. That's why I gave him the "easier???" way to do the same basic thing.
-
Re: Moving a series of points to be centered on a given point
Quote:
Originally Posted by krmed
I think what you've done is excluded the rcNoFillRgn from the DC, so you won't draw on it. Then when you exclude the rcFillRgn, it ADDITIONALLY excludes that area (the noFillRgn is not added back in).
Instead I believe your should use a CRgn to create a region for your nofill and your fill areas (only).
Then use something like this: (You'll need to create your rects properly of course)
Code:
temp_rect = data_box;
CRgn oldRgn;
rcNoFillRgn.right -= m_iPpiHorz / 16;
rcNoFillRgn.bottom = data_text.y + text_string_size.cy;
clip_rgn.CreateRectRgnIndirect(&rcNoFillRgn);
pDc->GetClipRgn(hDC, &oldRgn);
pDc->SelectClipRgn(&clip_rgn);
// Draw your fill area
pDc->SelectClipRgn(&rcFillRgn);
// Draw your no fill area
pDc->SelectClipRgn(&oldRgn);
Hope that helps.
Well, this seems to draw it nicely. I am not sure if the code will affect anything else. It shouldn't since I am restoring the original clip region?
Thanks
Mike
Code:
void CStar::DrawStar(CDC* pDC)
{
CRect rcFill(GetContainingRect());
CRect rcNoFill(rcFill);
CBrush brYellow(RGB(255,255,0));
CBrush brWhite(RGB(255,255,255));
rcFill.right = rcFill.left + (int)(rcFill.Width() * 0.75);
rcNoFill.left = rcFill.right;
CRgn rgOld, rgFill, rgNoFill;
::GetClipRgn(pDC->GetSafeHdc(), rgOld);
rgNoFill.CreateRectRgnIndirect(&rcNoFill);
pDC->SelectClipRgn(&rgNoFill);
CBrush* pOld = pDC->SelectObject(&brWhite);
pDC->Polygon(m_ptsOuter, m_nPoints * 2);
rgFill.CreateRectRgnIndirect(&rcFill);
pDC->SelectClipRgn(&rgFill);
pDC->SelectObject(&brYellow);
pDC->Polygon(m_ptsOuter, m_nPoints*2);
pDC->SelectClipRgn(&rgOld);
pDC->SelectObject(pOld);
}
-
Re: Moving a series of points to be centered on a given point
Since you restore the original clipping region, you should be fine.
However, you also need to delete the region objects after you select a new region object:
Code:
void CStar::DrawStar(CDC* pDC)
{
CRect rcFill(GetContainingRect());
CRect rcNoFill(rcFill);
CBrush brYellow(RGB(255,255,0));
CBrush brWhite(RGB(255,255,255));
rcFill.right = rcFill.left + (int)(rcFill.Width() * 0.75);
rcNoFill.left = rcFill.right;
CRgn rgOld, rgFill, rgNoFill;
::GetClipRgn(pDC->GetSafeHdc(), rgOld);
rgNoFill.CreateRectRgnIndirect(&rcNoFill);
pDC->SelectClipRgn(&rgNoFill);
CBrush* pOld = pDC->SelectObject(&brWhite);
pDC->Polygon(m_ptsOuter, m_nPoints * 2);
rgFill.CreateRectRgnIndirect(&rcFill);
rgNoFill.DeleteObject();
pDC->SelectClipRgn(&rgFill);
pDC->SelectObject(&brYellow);
pDC->Polygon(m_ptsOuter, m_nPoints*2);
pDC->SelectClipRgn(&rgOld);
pDC->SelectObject(pOld);
rgFill.DeleteObject();
}
If you don't do that, you'll have GDI resource leaks, and eventually other GDI objects cannot be created, so drawing on ANY window will be affected.
Glad I could help.