Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How to position an object in a particular degree around circles?

Status
Not open for further replies.

djjd47130

Programmer
Nov 1, 2010
480
US
I have an round shape object in a form, it can be placed in any position. The center of this object is considered 'Center Of Focus', and I know the X,Y coordinates of the center of this object at any time. Let's say it's in position [150,150] of the form.

Now I have another smaller round shape object just outside that main object. I would like to place this other object in a particular location around the main circle, and tell it to go in a certain distance from the center of focus and a certain degree around that center of focus. In other words, I'd like to (eventually) rotate the second object around the main object.

I basically need the following function filled in for me:

Code:
function PositionAroundPoint(CenterOfFocus: TPoint; DistanceFromFocus: Integer; DegreeAroundFocus: Integer): TPoint;
begin
  Result:= CenterOfFocus; //Default result value

  //I'm presuming that (CenterOfFocus.Y - DistanceFromFocus) is where it will be placed if I specify 0 Degrees, and (CenterOfFocus.Y + DistanceFromFocus) is where it will be placed if I specify 180 degrees.

  //CenterOfFocus = the center static position to base calculation off of
  //DistanceFromFocus = how many pixels away from focus
  //DegreeAroundFocus = how many degrees around the focus

  //Result = Position where second object should be placed

end;

Sounds like a Trigonometry formula, but I'm absolutely horrible at mathematics. Any help would be awesome. Thank you!


JD Solutions
 
My problem with this kind of stuff is memory of the right equations for the algorithm more than it is anything. That given, the internet is great if you remember the right words to search for.

Given any circle, its equation is defined as:

Code:
(x-h)[sup]2[/sup] + (y-k)[sup]2[/sup] = r[sup]2[/sup]

where
h = x coordinate center of circle
k = y coordinate center of circle
r = radius of the circle

Now given that, it can be solved for x and y relative to angle which represent any point along the circle, those are:

Code:
x = h + r cos(a)
y = k + r sin(a)

where
h = x coordinate center of circle
k = y coordinate center of circle
r = radius of the circle
a = number of radians from direct vertical (h, k+r)

Code:
r = d * (pi/180)

where 
r = number of radians
d = number of degrees

Now take that and use it in Delphi, and you'll get the answer to your question. If you are using set degree counts, I would consider making constant tables out of the sine/cosine parts if you use it much.

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Rather than handle this mathematically, another option may be to use the TAnimate object. Of course, you'd have to be able to create an .avi file, with enough 'snapshots' of the outer object rotated to make a smooth animation.

Also, found 'About' has an article on how to rotate a bitmap to any angle.

Link:

Code:
Const PixelMax = 32768;
Type
   pPixelArray = ^TPixelArray;
   TPixelArray = Array[0..PixelMax-1] Of TRGBTriple;

Procedure RotateBitmap_ads(
   SourceBitmap : TBitmap;
   out DestBitmap : TBitmap;
   Center : TPoint;
   Angle : Double) ;
Var
   cosRadians : Double;
   inX : Integer;
   inXOriginal : Integer;
   inXPrime : Integer;
   inXPrimeRotated : Integer;
   inY : Integer;
   inYOriginal : Integer;
   inYPrime : Integer;
   inYPrimeRotated : Integer;
   OriginalRow : pPixelArray;
   Radians : Double;
   RotatedRow : pPixelArray;
   sinRadians : Double;
begin
   DestBitmap.Width := SourceBitmap.Width;
   DestBitmap.Height := SourceBitmap.Height;
   DestBitmap.PixelFormat := pf24bit;
   Radians := -(Angle) * PI / 180;
   sinRadians := Sin(Radians) ;
   cosRadians := Cos(Radians) ;
   For inX := DestBitmap.Height-1 Downto 0 Do
   Begin
     RotatedRow := DestBitmap.Scanline[inX];
     inXPrime := 2*(inX - Center.y) + 1;
     For inY := DestBitmap.Width-1 Downto 0 Do
     Begin
       inYPrime := 2*(inY - Center.x) + 1;
       inYPrimeRotated := Round(inYPrime * CosRadians - inXPrime * sinRadians) ;
       inXPrimeRotated := Round(inYPrime * sinRadians + inXPrime * cosRadians) ;
       inYOriginal := (inYPrimeRotated - 1) Div 2 + Center.x;
       inXOriginal := (inXPrimeRotated - 1) Div 2 + Center.y;
       If
         (inYOriginal >= 0) And
         (inYOriginal <= SourceBitmap.Width-1) And
         (inXOriginal >= 0) And
         (inXOriginal <= SourceBitmap.Height-1)
       Then
       Begin
         OriginalRow := SourceBitmap.Scanline[inXOriginal];
         RotatedRow[inY] := OriginalRow[inYOriginal]
       End
       Else
       Begin
         RotatedRow[inY].rgbtBlue := 255;
         RotatedRow[inY].rgbtGreen := 0;
         RotatedRow[inY].rgbtRed := 0
       End;
     End;
   End;
End;

Usage:

procedure TForm1.Button1Click(Sender: TObject) ;
Var
   Center : TPoint;
   Bitmap : TBitmap;
begin
   Bitmap := TBitmap.Create;
   Try
     Center.y := (Image.Height div 2)+20;
     Center.x := (Image.Width div 2)+0;
     RotateBitmap_ads(
       Image.Picture.Bitmap,
       Bitmap,
       Center,
       Angle) ;
     Angle := Angle + 15;
     Image2.Picture.Bitmap.Assign(Bitmap) ;
   Finally
     Bitmap.Free;
   End;
end;
 
Thanks for your input. I did find something online which did (in a way) answer my question, with a little more Google on about page 30. The sample I found was actually making a square rotate, where the 4 points of the square are positioned from the center of the square. I stripped it down and got just one point of one square. Here's what I have of it so far. Can someone check it for problems?

Code:
function NewPosition(Center: TPoint; Distance: Integer;
  Degrees: Integer): TPoint;
var
  Radians: Real;
  Final: TPoint;
begin
  //Convert angle from degrees to radians
  //Subtract 135 to bring position to 0 Degrees
  Radians:= ((Degrees - 135) * Pi / 180.0);
  //Rotate 'Final' by 'Radians' around 'Center'
  Final.X:= Trunc(Distance*Cos(Radians)-Distance*Sin(Radians))+Center.X;  //Calculate X Position
  Final.Y:= Trunc(Distance*Sin(Radians)+Distance*Cos(Radians))+Center.Y;  //Calculate Y Position
  Result:= Final;
end;

My project is an animated floating menu with X number of items rotating around a central point in the screen. Each menu item is placed in a particular position around that central point. Each menu item may be of any color, shape, size, font, caption, border, icon, etc.

I can also set a 'StartDegree' property (degrees from 0) and a 'SpanDegree' property (How far around from StartDegree) where all the menu items are placed within that given range. The center of the menu is a round object. When you click it, the menu items slide and expand out to their 'Distance' property (how far away from center).

The menu items are inherited from TCustomPanel, which means you can drop controls into them. There's a main default ItemWidth and ItemHeight property, but you can override it with ParentItemWidth and ParentItemHeight.

This project is way over my head, but I think I got it down. It's not too difficult, now that I have the formula to calculate the position of the items. My main weakness is in efficiency and quality. I don't know enough about Delphi quite yet to make anything too immaculate, but I'm slowly getting there.

I will write an FAQ on how to rotate an object around a central point once I get the practice down solid.

Happy Holidays, and Happy Coding!


JD Solutions
 
Can someone check it for problems?

The formula is wonky that you have in your last post and isn't outputting correct results (*). My test on radius = 200 returns points of length 282 with the code you posted. Using the formula I posted (much simpler!) will achieve generally accurate results.

I notice now the formula I posted treats 0 degrees as along the positive-x axis, but if you want it adjusted to the positive-y axis, simply subtract the number of degrees by 90.

There are one or two algebraic simplifications one can make to this for performance reasons (which I'm sure someone who remembers more mathematics will point out), but it's simple enough and should help you test your code.

Code:
function LineLength(p1, p2: TPoint): Extended;
// returns the line length between p1 and p2
var
  x1, y1: integer;
begin
  x1 := p1.x - p2.x;
  y1 := p1.y - p2.y;
  Result := sqrt(sqr(x1) + sqr(y1));
end;

(*) - generally with any floating point values, you don't get exact values, but values with a certain degree of accuracy acceptable in scientific and mathematical applications. This is referred to as significant figures. This means if you use 200 as a radius and go back and check the final points, you won't necessarily get 200 but you might get something like 199.323857076869 or 199.822421164393, which is acceptable for applications such as yours.

My main weakness is in efficiency and quality.

Keep it as simple as possible. For example, if all you're doing is rotating a drawn square (or a menu item), all that is needed is to calculate the end-points and redraw - doing a TBitmap rotate would be overkill. And if you use the code much against fixed angles, pre-calculating the sine and cosine parts will net a performance advantage.

That given, the TBitmap rotate code posted above might be interesting to play around with as something different to do other than my big project. I might see what I can do with it.

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
I actually found some other code for rotating a bitmap...

Code:
function RotateImage(srcbit: TBitmap; Angle: Extended; FPoint: TPoint;
  Background: TColor): TBitmap;
var
  highest, lowest, mostleft, mostright: TPoint;
  topoverh, leftoverh: integer;
  x, y, newx, newy: integer;
begin
  Result := TBitmap.Create;

  //Identify specific degree to turn by
  while Angle >= (2 * pi) do
  begin
    Angle := Angle - (2 * pi);
  end;

  //Identify region
  if (angle <= (pi / 2)) then
  begin
    highest := Point(0,0);                        //OL
    Lowest := Point(Srcbit.Width, Srcbit.Height); //UR
    mostleft := Point(0,Srcbit.Height);           //UL
    mostright := Point(Srcbit.Width, 0);          //OR
  end else if (angle <= pi) then
  begin
    highest := Point(0,Srcbit.Height);
    Lowest := Point(Srcbit.Width, 0);
    mostleft := Point(Srcbit.Width, Srcbit.Height);
    mostright := Point(0,0);
  end else if (Angle <= (pi * 3 / 2)) then
  begin
    highest := Point(Srcbit.Width, Srcbit.Height);
    Lowest := Point(0,0);
    mostleft := Point(Srcbit.Width, 0);
    mostright := Point(0,Srcbit.Height);
  end else
  begin
    highest := Point(Srcbit.Width, 0);
    Lowest := Point(0,Srcbit.Height);
    mostleft := Point(0,0);
    mostright := Point(Srcbit.Width, Srcbit.Height);
  end;

  topoverh := yComp(Vektor(FPoint, highest), Angle);
  leftoverh := xComp(Vektor(FPoint, mostleft), Angle);
  Result.Height := Abs(yComp(Vektor(FPoint, lowest), Angle)) + Abs(topOverh);
  Result.Width  := Abs(xComp(Vektor(FPoint, mostright), Angle)) + Abs(leftoverh);

  Topoverh := TopOverh + FPoint.y;
  Leftoverh := LeftOverh + FPoint.x;

  // Fill in background color
  Result.Canvas.Brush.Color := Background;
  Result.Canvas.Pen.Color   := Background;
  Result.Canvas.Fillrect(Rect(0,0,Result.Width, Result.Height));

  //Loop through pixels
  for y := 0 to srcbit.Height - 1 do
  begin
    for x := 0 to srcbit.Width - 1 do
    begin
      newX := xComp(Vektor(FPoint, Point(x, y)), Angle);
      newY := yComp(Vektor(FPoint, Point(x, y)), Angle);
      newX := FPoint.x + newx - leftoverh;
      newy := FPoint.y + newy - topoverh;
      // Move beacause of new size
      Result.Canvas.Pixels[newx, newy] := srcbit.Canvas.Pixels[x, y];
      // Also fill the pixel beside to prevent empty pixels
      if ((angle < (pi / 2)) or
        ((angle > pi) and
        (angle < (pi * 3 / 2)))) then
      begin
        Result.Canvas.Pixels[newx, newy + 1] := srcbit.Canvas.Pixels[x, y];
      end else begin
        Result.Canvas.Pixels[newx + 1,newy] := srcbit.Canvas.Pixels[x, y];
      end;
    end;
  end;
end;

I have another unit somewhere I found written in asm which works beautifully fast, but I can't seem to find it at the moment.



JD Solutions
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top