## Drawing circles on a line

### Drawing circles on a line

I haven't actually written the code for this yet, but I'm hoping someone
could help me out here by taking a look at the general pseudocode I've got
so far. I'm basically trying to get my feet wet with the WinAPI GDI, and I
figure I can look up the hard stuff in a reference book later; the important
part is understanding the basic process first.

Basically, I'm writing an ActiveX control which has two properties, Angle
horizontal that a line of length Radius should be drawn on the control. When
the user MouseDowns within, say, 20 pixels of the line, a circle of radius 8
should be drawn, centered on the line. Moving the mouse while it's down
moves the circle along the line. Releasing the mouse leaves the circle drawn
in its last position just before the mouse was released. Thus, clicking,
dragging, and releasing n times should produce n distinct circles centered
on the line (unless they happen to be released at the same point, in which
case the circles would overlap).

Anyway, without further ado here's what I've come up with.. if you have any
hints/suggestions/code about a particular part I'd love to hear it...
thanks!

pseudocode:
' helper function

Quote:> DeterminePosition (mX As Long, mY As Long) As POINT_API

+ sX = start x-coordinate of the line
+ sY = start y-coordinate of the line
+ mX = mouse x-coord
+ mY = mouse y-coord
+ rX = (mX-sX)*Cos( Angle )
+ rY = (mY-sY)*Sin( Angle )
+ return value is a POINT_API with coords (rX, rY)

' mousedown event handler

Quote:> UserControl_MouseDown

+ get coordinates (cX,cY) of click
+ is click within 20 pixels of line?
+ yes: draw circle on the line with DeterminePosition(cX, cY)
+ no: do nothing

' mousemove event handler

Quote:> UserControl_MouseMove

+ delete previous circle by xor? (can you do this?)
+ draw circle with DeterminePosition and mouse coords

' mouseup event handler

Quote:> UserControl_MouseUp

+ don't do anything. last circle should still be there from MouseMove (or
MouseDown if user didn't move)

### Drawing circles on a line

Quote:> I haven't actually written the code for this yet, but I'm hoping someone
> could help me out here by taking a look at the general pseudocode I've got
> so far. I'm basically trying to get my feet wet with the WinAPI GDI, and I
> figure I can look up the hard stuff in a reference book later; the
important
> part is understanding the basic process first.

> Basically, I'm writing an ActiveX control which has two properties, Angle
> and Radius. This determines the counterclockwise angle in radians from the
> horizontal that a line of length Radius should be drawn on the control.
When
> the user MouseDowns within, say, 20 pixels of the line, a circle of radius
8
> should be drawn, centered on the line. Moving the mouse while it's down
> moves the circle along the line. Releasing the mouse leaves the circle
drawn
> in its last position just before the mouse was released. Thus, clicking,
> dragging, and releasing n times should produce n distinct circles centered
> on the line (unless they happen to be released at the same point, in which
> case the circles would overlap).

> Anyway, without further ado here's what I've come up with.. if you have
any
> hints/suggestions/code about a particular part I'd love to hear it...
> thanks!

> pseudocode:
> ' helper function
> > DeterminePosition (mX As Long, mY As Long) As POINT_API
>  + sX = start x-coordinate of the line
>  + sY = start y-coordinate of the line
>  + mX = mouse x-coord
>  + mY = mouse y-coord
>  + rX = (mX-sX)*Cos( Angle )
>  + rY = (mY-sY)*Sin( Angle )
>  + return value is a POINT_API with coords (rX, rY)

What you'd need to do, is project a line from the mouse coordinates at right
angles to the line of your control to get an intersection point, then take
that distance.  Unfortunately my maths/trigonometry fails me here but I'm
sure it shouldn't be too difficult.  You just need to work out how far down
the line the circle should be mapped

Quote:> ' mousedown event handler
> > UserControl_MouseDown
>  + get coordinates (cX,cY) of click
>  + is click within 20 pixels of line?
>      + yes: draw circle on the line with DeterminePosition(cX, cY)
>      + no: do nothing

To get a point on your line, you'd use something like this:

'***
' Adust this for the positioning, 0 = centre, 0.5 = middle, 1 = outside
Pos = 0.5

MidX = UserControl.ScaleWidth \ 2
MidY = UserControl.ScaleHeight \ 2
PtX = (Cos(Angle) * (Radius * Pos)) + MidX
PtY = (Sin(Angle) * (Radius * Pos)) + MidY
'***

Clamp Pos in the range 0 to 1 to keep it on the line
Then draw your circle at those coordinates, remember though that the
Ellipse() API call (Which is presumably what you're going to be using)
doesn't draw in the bottom right pixel of it's bounding box, so add an extra
one to the right and bottom coordinates.

Quote:> ' mousemove event handler
> > UserControl_MouseMove
>  + delete previous circle by xor? (can you do this?)
>  + draw circle with DeterminePosition and mouse coords

You can only use the XOr method if the circle you're drawing is opposite of
the background colour which in most cases doesn't look too good.  To
accomplish it though you'd set a binary raster operation of R2_NOTMASKPEN on
the DC and draw in solid white.  If you want to draw in any colour, then
you'd have to use the FillRect() API to clear the area where the circle used
to cover, then re-draw the line through that section, and any other circles
that are within that rectangle.  If that sounds like too much work then you
can also just clear and re-draw the entire thing, sans the circle you're
moving - As long as the control isn't too huge then you should be able to
get away with this easily.
Another option is the use of a back buffer and only draw the 'locked'
circles and base line to it, then each repaint you draw that to your
control's surface (Or just the region that was covered by the previous
circle) and then the new circle over that.
Hope this helps,

Mike

- Microsoft Visual Basic MVP -

WWW: Http://www.mvps.org/EDais/

### Drawing circles on a line

Mike:

Quote:> Another option is the use of a back buffer and only draw the 'locked'
> circles and base line to it, then each repaint you draw that to your
> control's surface (Or just the region that was covered by the previous
> circle) and then the new circle over that.

You threw a lot of stuff at me at once. Can you elaborate more on the back
buffer strategy?

- Marc

> > I haven't actually written the code for this yet, but I'm hoping someone
> > could help me out here by taking a look at the general pseudocode I've
got
> > so far. I'm basically trying to get my feet wet with the WinAPI GDI, and
I
> > figure I can look up the hard stuff in a reference book later; the
> important
> > part is understanding the basic process first.

> > Basically, I'm writing an ActiveX control which has two properties,
Angle
> > and Radius. This determines the counterclockwise angle in radians from
the
> > horizontal that a line of length Radius should be drawn on the control.
> When
> > the user MouseDowns within, say, 20 pixels of the line, a circle of
> 8
> > should be drawn, centered on the line. Moving the mouse while it's down
> > moves the circle along the line. Releasing the mouse leaves the circle
> drawn
> > in its last position just before the mouse was released. Thus, clicking,
> > dragging, and releasing n times should produce n distinct circles
centered
> > on the line (unless they happen to be released at the same point, in
which
> > case the circles would overlap).

> > Anyway, without further ado here's what I've come up with.. if you have
> any
> > hints/suggestions/code about a particular part I'd love to hear it...
> > thanks!

> > pseudocode:
> > ' helper function
> > > DeterminePosition (mX As Long, mY As Long) As POINT_API
> >  + sX = start x-coordinate of the line
> >  + sY = start y-coordinate of the line
> >  + mX = mouse x-coord
> >  + mY = mouse y-coord
> >  + rX = (mX-sX)*Cos( Angle )
> >  + rY = (mY-sY)*Sin( Angle )
> >  + return value is a POINT_API with coords (rX, rY)

> What you'd need to do, is project a line from the mouse coordinates at
right
> angles to the line of your control to get an intersection point, then take
> that distance.  Unfortunately my maths/trigonometry fails me here but I'm
> sure it shouldn't be too difficult.  You just need to work out how far
down
> the line the circle should be mapped

> > ' mousedown event handler
> > > UserControl_MouseDown
> >  + get coordinates (cX,cY) of click
> >  + is click within 20 pixels of line?
> >      + yes: draw circle on the line with DeterminePosition(cX, cY)
> >      + no: do nothing

> To get a point on your line, you'd use something like this:

> '***
> ' Adust this for the positioning, 0 = centre, 0.5 = middle, 1 = outside
> Pos = 0.5

> MidX = UserControl.ScaleWidth \ 2
> MidY = UserControl.ScaleHeight \ 2
> PtX = (Cos(Angle) * (Radius * Pos)) + MidX
> PtY = (Sin(Angle) * (Radius * Pos)) + MidY
> '***

> Clamp Pos in the range 0 to 1 to keep it on the line
> Then draw your circle at those coordinates, remember though that the
> Ellipse() API call (Which is presumably what you're going to be using)
> doesn't draw in the bottom right pixel of it's bounding box, so add an
extra
> one to the right and bottom coordinates.

> > ' mousemove event handler
> > > UserControl_MouseMove
> >  + delete previous circle by xor? (can you do this?)
> >  + draw circle with DeterminePosition and mouse coords

> You can only use the XOr method if the circle you're drawing is opposite
of
> the background colour which in most cases doesn't look too good.  To
> accomplish it though you'd set a binary raster operation of R2_NOTMASKPEN
on
> the DC and draw in solid white.  If you want to draw in any colour, then
> you'd have to use the FillRect() API to clear the area where the circle
used
> to cover, then re-draw the line through that section, and any other
circles
> that are within that rectangle.  If that sounds like too much work then
you
> can also just clear and re-draw the entire thing, sans the circle you're
> moving - As long as the control isn't too huge then you should be able to
> get away with this easily.
> Another option is the use of a back buffer and only draw the 'locked'
> circles and base line to it, then each repaint you draw that to your
> control's surface (Or just the region that was covered by the previous
> circle) and then the new circle over that.
> Hope this helps,

>     Mike

>  - Microsoft Visual Basic MVP -

> WWW: Http://www.mvps.org/EDais/

### Drawing circles on a line

Quote:> You threw a lot of stuff at me at once. Can you elaborate more on the back
> buffer strategy?

It' probably easiest to visualise this with two picture box's, then drop in
this code:

'***
Const CircRad As Long = 20

With Picture1 ' This is your 'front buffer'
.ScaleMode = vbPixels
Call .Move(0, 0, 4000, 4000)
End With

With Picture2 ' This is your 'back buffer'
.ScaleMode = vbPixels
.AutoRedraw = True
Call .Move(Picture1.Width, 0, Picture1.Width, Picture1.Height)
.BackColor = vbWhite ' You could load an image here too

' Comment this out to hide the back-buffer
'.Visible = False
End With
End Sub

Private Sub Picture1_MouseDown(ByRef Button As Integer, _
ByRef Shift As Integer, ByRef X As Single, ByRef Y As Single)
If (Button = vbLeftButton) Then Call ReDraw(X, Y)
End Sub

Private Sub Picture1_MouseMove(ByRef Button As Integer, _
ByRef Shift As Integer, ByRef X As Single, ByRef Y As Single)
If (Button = vbLeftButton) Then Call ReDraw(X, Y)
End Sub

Private Sub ReDraw(ByVal inX As Single, ByVal inY As Single)
Call Picture1.Refresh

' Draw the circle
Picture1.DrawWidth = 4
Picture1.DrawWidth = 2
End Sub

Private Sub Picture1_MouseUp(ByRef Button As Integer, _
ByRef Shift As Integer, ByRef X As Single, ByRef Y As Single)
If (Button <> vbLeftButton) Then Exit Sub

Picture1.AutoRedraw = True ' Paint the new back-buffer
Call Picture1.PaintPicture(Picture2.Image, 0, 0)
Call ReDraw(X, Y)
Call Picture2.PaintPicture(Picture1.Image, 0, 0)
Picture1.AutoRedraw = False

' Clear front buffer
Call Picture1.Refresh
End Sub

Private Sub Picture1_Paint()
' Paint front buffer with back-buffer
Call Picture1.PaintPicture(Picture2.Image, 0, 0)
End Sub
'***

This isn't a particularly great example of back-buffering, but it's about as
simple as I could make it.
You can accomplish the same thing with a single picture box set to
AutoRedraw, since it creates a back buffer (Well, 2..) behind the scenes.
Calling .Cls() will refresh to the back buffer, setting the .Picture
property as the .Image property will lock the current drawing in place (Lock
front buffer to back buffer.)
If you're doing this properly though, you'd create the back buffer in memory
and simply blit a section from it with each re-draw, but that's beyond the
scope of a simple newsgroup post.
Hope this helps,

Mike

- Microsoft Visual Basic MVP -

WWW: Http://www.mvps.org/EDais/

I derived this once by first solving for the circle radius by
adding the same amount to each circle equation and setting them
equal to one another, then solving for the coordinates by more
manipulation of these equations (I don't remember EXACTLY, but this
is the crux of the solution). I also remember that it took up
quite a bit of calculation for something that seems fairly straight
forward. Is there another approach to solving this problem in
particular, (or even in general) that doesn't lead to gigantic amounts
of wasted led?