Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Für allgemeine Fragen zur Programmierung, welche nicht! direkt mit Lazarus zu tun haben.
Antworten
fpGUIcoder
Beiträge: 199
Registriert: Di 20. Okt 2015, 23:13

Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Beitrag von fpGUIcoder »

Hallo,

Ich kämpfe gerade mit einer Winkelberechnungsfunktion, die mir einen Winkel zwischen 0 und 360 Grad Quadrantenrichtig berechnen soll, wobei ich davon ausghe, daß wegen des Verlaufes der X,Y Achsen mit Punkt 0,0 in der linken oberen Bildschirmecke und für beide Achsen steigenden Werten nach rechts bzw unten ich einen Koordinatenursprung festlegen muss, sagen wir in Bilschirmmitte oder bei Rechtecken in dessen Mitte, wo sich halt die Diagonalen des Rechteckes schneiden.

Der erste Quadrant liegt dann im Gegensatz zur Mathematik da, wo X > XMitte und Y > YMitte

der zweite wo X < XMitte und Y > YMitte

der dritte wo X < XMitte und Y < YMitte

der vierte wo X > XMitte und Y < YMitte

Für diese Vorgabe habe ich nun folgende Funktion geschrieben, die aber den Winkel zwischen Abszisse und einer Geraden von XMitte,YMitte zu irgendeinem Punkt X,Y noch NICHT korrekt berechnet.

Code: Alles auswählen

 
function QuadrantAngle(X,Y,xm,ym: Extended): Extended;
var angle,delta: Extended;
begin
  { I. Quadrant, wegen xm < X, ym < y --> rechts unterhalb ym }
  if (X > xm) and (Y > ym) then                   //X ist die X Koordinate, Y die Y Koordinate des Punktes,
  begin                                                      //der mit xm,ym verbunden den Wikel ergeben soll
    Result := arctan(Y-ym/X-xm) * pi / 180;  //X,Y Koordinaten des Endpunktes
  end                                                         //xm,ym Koordinaten Ursprung oder Mittelpunkt (Im Rechteck)
  else                                                         //Der Winkel ist auf den Koordinatenursprung bezogen
  { II. Quadrant, wegen xm > X, ym < Y --> links unterhalb ym }
  if (X < xm) and (Y > ym) then
  begin
    angle := arctan(Y-ym/X-xm) * pi / 180;
    delta := (90 - angle) * 2;
    angle := angle + delta;
    Result := angle;
  end
  else
  { III. Quadrant, wegen xm > X, ym > Y --> links oberhalb ym }
  if (X < xm) and (Y < ym) then
  begin
    angle := arctan(Y-ym/X-xm) * pi / 180;
    delta := (90 - angle) * 2;
    angle := angle + 180;
    Result := angle;
  end
  else
  { IV. Quadrant, wegen xm < X, ym > Y --> rechts oberhalb ym }
  if (X > xm) and (Y < ym) then
  begin
    angle := arctan(Y-ym/X-xm) * pi / 180;
    delta := (90 - angle) * 2;
    angle := angle + delta;
    Result := angle + 180;
  end;
  //Result := arctan2(Y-ym,X-xm) * pi / 180;
end;
 


Wer kann mir weiter helfen?

Ich kann mir momentan nicht erklären, warum dieses Funktion den Winkel NULL ausgibt. Wenn ich eine Ellipsenfunktion her nehme, die Anfangs und Endwinkel als Eingabe verlangt, wird kein Ellipsenbogen gezeichnet. Jedoch wird korrekt gezeichnet, wenn ich derselben Funktion konstante Winkelwerte übergebe, der Fehler muß also in dieser Funktion hier liegen. Ich bin aber völlig ratlos bezühlich des Fehkers in dieser Funktion hier.

Weiß jemand Rat?


.
Zuletzt geändert von fpGUIcoder am Do 12. Nov 2015, 21:48, insgesamt 1-mal geändert.

wp_xyz
Beiträge: 4885
Registriert: Fr 8. Apr 2011, 09:01

Re: Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Beitrag von wp_xyz »

Warum multiplizierst du das Ergebnis der Funktion arctan mit pi/180? arctan rechnet im Bogenmaß, also musst du, um Grad zu erhalten, mit 180/pi multiplizieren.

fpGUIcoder
Beiträge: 199
Registriert: Di 20. Okt 2015, 23:13

Re: Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Beitrag von fpGUIcoder »

wp_xyz hat geschrieben:Warum multiplizierst du das Ergebnis der Funktion arctan mit pi/180? arctan rechnet im Bogenmaß, also musst du, um Grad zu erhalten, mit 180/pi multiplizieren.


Weil ich geglaubt habe dasß dies der richtige Weg sei um den Winkel, der von der ArcTan Funktion im Bogenmaß zurück gegeben wird, im Gradmaß zu erhalten.

Da ist es also genau umgekehrt (180/pi) * rad-winkel. Ok, danke, wieder was dazu gelernt! :)

Eine Anderung in 180/pi bringt mir aber nix, hab das gerade geändert in meinem Quellcode, der Winkel stimmt nach wie vor nicht, hier muß noch ein anderer kritscher Fehler drin sein. Der Ellipsenbogen wird noch immer nicht gezeichnet.

Code: Alles auswählen

 
  DrawEllipseSeg(dc, xm, ym, {130*}Round(QuadrantAngle(xStart,yStart,xm,ym)), {230*}Round(QuadrantAngle(xEnd,yEnd,xm,ym)), a, b, GetDCPenColor(dc));
 
//Die Konstanten 130 und 230 habe ich probeweise eingesetzt, da wird ein Ellipsenbogen im 2. und 3. Quadranten des Bildschiermkoordinatensystems
//gezeichnet. Mit der Berechnungsfunktion klappt es nicht, weder mit Winkeln im Gradmaß noch mit Winkeln im Bogenmaß. Der Fehler muß also in der Winkelberechnung liegen, mit den Konstanten sehe ich einen Ellipsenbogen, mit der Berechnungsfunktion nicht.
 
//Hier die Funktion:
 
 
procedure DrawEllipseSeg(Handle: HDC; X, Y, startgrd, endgrd, Xradius, Yradius: Integer; Color: COLORREF);
var angle: Integer;
begin
  for angle := startGrd to endGrd do
  begin
    SetPixel(Handle,
      Round(Xradius * cos(angle * pi / 180)) + x,
      Round(Yradius * sin(angle * pi / 180)) + y,
      Color
      );
  end;
end;
 


Muss doch möglich sein, so eine Funktion nachzubauen, Andere bauen ganze Spiellandschaften auf. Die machen das doch auch mit Matheregeln. Warum krieg ich da hier nicht mal so einen simplen Winkel berechnet?


.

wp_xyz
Beiträge: 4885
Registriert: Fr 8. Apr 2011, 09:01

Re: Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Beitrag von wp_xyz »

fpGUIcoder hat geschrieben:Warum krieg ich da hier nicht mal so einen simplen Winkel berechnet?

Nicht aufgeben, sondern nachdenken. Schau dir mal deine arctan-Funktion an: angle := arctan(Y-ym/X-xm). Da willst du die Längen der beiden Katheten des rechtwinkligen Dreiecks durcheinander dividieren, also die y-Länge (Y-ym) durch die x-Länge (X-xm), ja? Aber was dividierst du?

Und dann schaue dir nochmals die Vorzeichen in den 4 Quadranten an. Im 2.Quadranten, z.B. ist X<xm und Y>ym, der Wert des ArcTan (angle) also negativ. delta berechnest du als 90 - angle. Weil aber angle negativ ist, wird delta nicht kleiner als 90° sondern größer! Willst du das? (Übrigens: diese Rechnung mit delta ist unnötig, es geht wesentlich einfacher...)

Du berechnest die Winkel durch eine Division. Da müssen immer die Glocken läuten: was passiert, wenn der Nenner null wird? Ja, Absturz! Du musst also in deiner Funktion noch den Fall X = xm separat behandeln.

In der Ellipsen-Zeichenroutine schließlich setzt du voraus, dass "startGrd" kleiner ist als "endGrd". Ist das sichergestellt? Wenn nicht, wird nämlich die Schleife überhaupt nicht durchlaufen.

Die Reihenfolge der Endwerte führt noch zu einem weiteren Problem, wenn man die Ellipse mit der Maus zeichnet: Angenommen, der Startpunkt liegt bei 340° und die Maus wird so gezogen wird, dass der Winkel zunimmt, z.B. auf 370°. Aber beim Überschreiten der 360° springt deine Winkelroutine wieder auf 0 zurück, und die endgrd sind plötzlich nur noch 10. Wenn nun der Bogen konsequent vom kleinen zum großen Winkel gezeichnet wird, wird nun plötzlich der andere, lange Teil des Bogens gemalt, also von 10 bis 340, nicht der kleine Bogen von 340 bis 370.

Leider sind die Computer so blöd, dass man ihnen jede Kleinigkeit aufs genauste sagen muss...

fpGUIcoder
Beiträge: 199
Registriert: Di 20. Okt 2015, 23:13

Re: Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Beitrag von fpGUIcoder »

wp_xyz hat geschrieben:
fpGUIcoder hat geschrieben:Warum krieg ich da hier nicht mal so einen simplen Winkel berechnet?

Nicht aufgeben, sondern nachdenken. Schau dir mal deine arctan-Funktion an: angle := arctan(Y-ym/X-xm). Da willst du die Längen der beiden Katheten des rechtwinkligen Dreiecks durcheinander dividieren, also die y-Länge (Y-ym) durch die x-Länge (X-xm), ja? Aber was dividierst du?

Und dann schaue dir nochmals die Vorzeichen in den 4 Quadranten an. Im 2.Quadranten, z.B. ist X<xm und Y>ym, der Wert des ArcTan (angle) also negativ. delta berechnest du als 90 - angle. Weil aber angle negativ ist, wird delta nicht kleiner als 90° sondern größer! Willst du das? (Übrigens: diese Rechnung mit delta ist unnötig, es geht wesentlich einfacher...)

Du berechnest die Winkel durch eine Division. Da müssen immer die Glocken läuten: was passiert, wenn der Nenner null wird? Ja, Absturz! Du musst also in deiner Funktion noch den Fall X = xm separat behandeln.

In der Ellipsen-Zeichenroutine schließlich setzt du voraus, dass "startGrd" kleiner ist als "endGrd". Ist das sichergestellt? Wenn nicht, wird nämlich die Schleife überhaupt nicht durchlaufen.

Die Reihenfolge der Endwerte führt noch zu einem weiteren Problem, wenn man die Ellipse mit der Maus zeichnet: Angenommen, der Startpunkt liegt bei 340° und die Maus wird so gezogen wird, dass der Winkel zunimmt, z.B. auf 370°. Aber beim Überschreiten der 360° springt deine Winkelroutine wieder auf 0 zurück, und die endgrd sind plötzlich nur noch 10. Wenn nun der Bogen konsequent vom kleinen zum großen Winkel gezeichnet wird, wird nun plötzlich der andere, lange Teil des Bogens gemalt, also von 10 bis 340, nicht der kleine Bogen von 340 bis 370.

Leider sind die Computer so blöd, dass man ihnen jede Kleinigkeit aufs genauste sagen muss...



Danke erst mal für die Hinweise. Hilfe zur Selbsthilfe ist auf jeden Fall besser, als alles gleich zu verraten und ich dann biem nächsten Problem wieder fragen muss. Besser ich lerne, es selber zu tun. Klar. :)

Aber ich stehe imme rnoch auf dem Schlauch.

In allen mathematischen Erklärungen zur Ellipsengleichung bezieht man sich auf die Brennpunkte der Ellipse. Man denkt sich dann eine Schnur um die Brennpunte F1/F2 und zeichnet mit einem Stift, der sich innerhalb der Schnur befindet, wobei die Schnur straff gezogen ist, den sich ergebenden Umriss, die Ellipse. Ich aber habe ein Rechteck, das die Ellipse umschließt. Und wenn ich nur einen Abschnitt der Ellipse haben will, gebe ich die XY Koordinaten der Schnittpunkte mit der Ellipse an, wobei die beiden Geraden nicht durch die Bernnpunkte der Ellipse sindern zu deren Mittelpunkt laufen. Das macht es für mich scheierig, eine passende Gleichung zu finden.

Ich brauche in Polarkooridinaten die Länge der Geraden vom Mittelpunkt der Ellipse zum übergebenen Schnittpunkt der Geraden mit der Ellipse. Dies natürlich für beide Geraden.

Und genau hier komme ich nicht weiter. :(

Ich brauche ein Gleichungssystem, bestehend aus Ellipsengleichung und Geradengleichung, deren Lösung der Schnittpunkt Gerade-Ellipse ist. Dies für beide Geraden, also je ein solches Gleichungssystem. Der Ellipsenbogen soll dann zwischen beiden Schnittpungkten gezeichnet werden. Daehalb brauche ich dann für Anfang und Ende des Bogens die Gerade mit korrektem Winkel. In meiner bisherigen Zeichenfunktion für den Ellipsenbogen übergebe ich dann Start und Endwinkel. Gibt es da eventuell eine elgantere Lösung.

[quote=wp_xyz]
Nicht aufgeben, sondern nachdenken. Schau dir mal deine arctan-Funktion an: angle := arctan(Y-ym/X-xm). Da willst du die Längen der beiden Katheten des rechtwinkligen Dreiecks durcheinander dividieren, also die y-Länge (Y-ym) durch die x-Länge (X-xm), ja? Aber was dividierst du?[/quote]

Ok, habe jetzt diese formeln:

stangle := arctan((yStart - ym + Top) / (xStart - xm + Left)) * 180/pi;

endangle := arctan((yEnd - ym + Top) / (xEnd - xm + Left)) * 180/pi;

komme aber immer noch nicht wirklich weiter.

Hier meine aktuelle Funktion:

Code: Alles auswählen

 
function Pie(dc: HDC; Left, Top, Right, Bottom, xStart, yStart, xEnd, yEnd: Integer): BOOL;
var xm, ym, a, b: Integer; stangle, endangle, delta: Extended;
begin
  xm := Left + Abs(Right - Left) div 2;
  ym := Top + Abs(Bottom - Top) div 2;
 
  a := Right - xm;
  b := Bottom - ym;
 
  SetArcDirection(dc, AD_COUNTERCLOCKWISE);                                   //--> wenn ich die weglasse, wird gar nichts gezeichnet.
  if Arc(dc, Left, Top, Right, Bottom, xStart, yStart, xEnd, YEnd) then   //--> sonst nur die Geraden und diese zu lang
  begin                                                                                                //--> über den Ellipsenschnittpunkt hinaus
    MoveToEx(dc, xStart, yStart, nil);                                                    //--> Ich will aber die Gerade vom Mittelpunkt der
    LineTo(dc, xm, ym);                                                                        //--> Ellipse bis zum Schnittpunkt mit dieser
    MoveToEx(dc, xEnd, yEnd, nil);
    LineTo(dc, xm, ym);
  end;
  stangle := arctan((yStart - ym + Top) / (xStart - xm + Left)) * 180/pi;
 
  if ((XStart-xm+Left<xm) and (YStart-ym+Top>ym)) or ((XStart-xm+Left<xm) and (YStart-ym+Top<ym)) then
  begin
    stangle := stangle + 180;
  end
  else
  if (XStart-xm+Left>xm) and (YStart-ym+Top<ym) then
  begin
    stangle := stangle + 360;
  end;
 
  endangle := arctan((yEnd - ym + Top) / (xEnd - xm + Left)) * pi / 180;
 
  if ((XStart-xm+Left<xm) and (YStart-ym+Top>ym)) or ((XStart-xm+Left<xm) and (YStart-ym+Top<ym)) then
  begin
    endangle := endangle + 180;
  end
  else
  if (XStart-xm+Left>xm) and (YStart-ym+Top<ym) then
  begin
    endangle := endangle + 360;
  end;
 
  //DrawEllipseSeg(dc, xm, ym, {130}Round(QuadAngle(xStart-Left,yStart-Top,xm,ym)),{230}Round(QuadAngle(xEnd-Left,yEnd-Top,xm,ym)), a, b, GetDCPenColor(dc));
  DrawEllipseSeg(dc, xm, ym, Round(StAngle), Round(EndAngle), a, b, GetDCPenColor(dc));
end;
[code]
 
Bin bissl verzweifelt. Warum funktioniert das nicht?
 
Hier mal die Anwendung:
 
[code]
program tut1;
 
{$APPTYPE CONSOLE}
 
uses
  SysUtils,
  Windows,
  Messages,
  arcs;
 
 
const
  ClassName = 'DrawClass';
  AppName = 'Zeichnen';
 
  WindowWidth = 500;
  WindowHeight = 400;
 
 
function WndProc(hWnd: HWND; uMsg: UINT; wParam: wParam; lParam: LParam):
  lresult; stdcall;
var
  x, y : integer;
  WndDC: HDC;
  ps: TPaintStruct;
  RedBrush, RedBrushOld: HBRUSH;
  GreenHatchBrush, GreenHatchBrushOld: HBRUSH;
  Pen, PenOld: HPEN;
begin
  Result := 0;
  case uMsg of
    WM_CREATE:
    begin
      { Fenster zentrieren }
      x := GetSystemMetrics(SM_CXSCREEN);
      y := GetSystemMetrics(SM_CYSCREEN);
      MoveWindow(hWnd, (x div 2) - (WindowWidth div 2),
        (y div 2) - (WindowHeight div 2),
        WindowWidth, WindowHeight, true);
 
    end;
    WM_PAINT:
    begin
      WndDC := BeginPaint(hWnd, ps);
         { eine einfache Linie }
         MoveToEx(WndDC, 20, 10, nil);
        LineTo(WndDC, 20, 90);
 
        { ein Rechteck }
        Rectangle(WndDC, 40, 10, 60, 90);
 
        { und das ganze gefüllt }
        RedBrush := CreateSolidBrush(RGB(255, 0, 0));
        RedBrushOld := SelectObject(WndDC, RedBrush);
        Rectangle(WndDC, 80, 10, 100, 90);
        { alten Brush wieder herstellen und benutzten löschen }
        SelectObject(WndDc, RedBrushOld);
        DeleteObject(RedBrush);
 
        { und das ganze noch mal mit Muster }
        GreenHatchBrush := CreateHatchBrush(HS_BDIAGONAL, RGB(0, 255, 0));
        GreenHatchBrushOld := SelectObject(WndDC, GreenHatchBrush);
        RectAngle(WndDC, 120, 10, 140, 90);
        { alten Brush wieder herstellen und benutzten löschen }
        SelectObject(WndDC, GreenHatchBrushOld);
        DeleteObject(GreenHatchBrush);
 
        { und nochmal mit einem anderen Stift }
        Pen := CreatePen(PS_DOT, 1, RGB(0, 0, 255));
        SetBkMode(WndDC, TRANSPARENT);
        PenOld := SelectObject(WndDC, Pen);
        RectAngle(WndDC, 160, 10, 180, 90);
        SelectObject(WndDC, PenOld);
        DeleteObject(Pen);
        SetArcDirection(WndDC, AD_CounterCLOCKWISE);
        { Ellipse }
        arcs{Windows}.Ellipse(WndDC, 30, 110, 180, 200);  //eigene Ellipsenfunktion anstelle
                                                                                    //Original
        { Kreis }
        arcs{Windows}.Ellipse(WndDC, 80, 130, 130, 180); //eigene Ellipsenfunktion anstelle
                                                                                   //Original
 
        { Chord }
        Windows.Chord(WndDC, 220, 110, 300, 200, 280, 120, 190, 230);
 
        { Pie }
        //Eigene Pie Funktion anstelle des Originals. Diese Funktion macht Probleme
        arcs{Windows}.Pie(WndDC, 300, 110, 500, 200, 310, 110, 200, 260);
 
        { RoundRect }
        RoundRect(WndDC, 30, 220, 180, 350, 35, 35);
      EndPaint(hWnd, ps);
    end;
    WM_DESTROY: PostQuitMessage(0);
  else
    Result := DefWindowProc(hWnd, uMsg, wParam, lParam);
  end;
end;
 
 
var
  wc: TWndClassEx = (
    cbSize          : SizeOf(TWndClassEx);
    Style           : CS_HREDRAW or CS_VREDRAW;
    lpfnWndProc     : @WndProc;
    cbClsExtra      : 0;
    cbWndExtra      : 0;
    hbrBackground   : COLOR_APPWORKSPACE;
    lpszMenuName    : nil;
    lpszClassName   : ClassName;
    hIconSm         : 0;
  );
  msg: TMsg;
 
begin
  { TODO -oUser -cConsole Main : Insert code here }
  wc.hInstance := hInstance;
  wc.hIcon := LoadIcon(hInstance, IDI_WINLOGO);
  wc.hCursor := LoadCursor(0, IDC_ARROW);
 
  RegisterClassEx(wc);
  CreateWindowEx(0, ClassName, AppName, WS_CAPTION or WS_VISIBLE or WS_SYSMENU,
     CW_USEDEFAULT, CW_USEDEFAULT, WindowWidth, WindowHeight, 0, 0, hInstance, nil);
 
  while GetMessage(msg, 0,0,0) do
  begin
    TranslateMessage(msg);
    DispatchMessage(msg);
  end;
end.
 



Leider kann ich hier nichts mehr anhängen, sonst würde ich einen Screenshot anhängen, was ich haben will und was aktuell gezeichnet wird.

Ich hoffe, ich konnte dennoch mein Problem verständlich erklären.


.
Zuletzt geändert von fpGUIcoder am Mi 11. Nov 2015, 12:51, insgesamt 1-mal geändert.

wp_xyz
Beiträge: 4885
Registriert: Fr 8. Apr 2011, 09:01

Re: Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Beitrag von wp_xyz »

Zur Ellipsengleichung: Da hast du sicher nicht "alle Erklärungen" gefunden, denn Google spuckt folgenden Artikel ziemlich weit oben aus: http://www.mathe-online.at/materialien/ ... llipse.pdf

Was ist eigentlich genau die Aufgabenstellung? Ein Tortenstück ("pie") zu zeichnen? Was sind die gegebenen Voraussetzungen? (Rechteck, das die Ellipse umschreibt; sowie zwei Punkte für die Definition der Start- und Endwinkel?)

Zu deinem neuen Code: Warum nimmst du plötzlich Windows-Funktionen? Der alte Code funktioniert doch auch, wenn die Fehler mit der Winkelberechnung gelöst sind. Und da bist du jetzt ein gutes Stück weiter. Ich frage mich nur, warum du Left und Top in der Kathetenberechnung verwendest. Und der Fall wegen Division durch 0 fehlt noch. Die Winkelberechnung müsste so richtig sein (nicht getestet):

Code: Alles auswählen

 
function QuadrantAngle(X,Y,xm,ym: Double): Double;  // Könnte noch etwas vereinfacht werden...
begin
  if (X = xm) then
  begin
    if (Y > ym) then
      Result := 90
    else
    if (Y < ym) then
      Result := 270
    else
       raise Exception.Create('0/0 ist undefiniert');
  end else
  begin
    Result := arctan(abs((Y - ym) / (X - xm))) * 180 / pi;
    // Das ist der Winkel im 1. Quadranten, ist immer >0!
 
    // 1. Quadrant  --> keine Korrektur nötig
    if (X > xm) and (Y >= ym) then
      exit;
 
    // 2. Quadrant  --> den Betrag des Winkels von 180° subtrahieren
    if (X < xm) and (Y >= ym) then 
    begin
      Result := 180 - Result;
      exit;
    end;
 
    // 3.Quadrant --> entgegengesetzt um 1.Quadrant, also 180 addieren
    if (X < xm) and (Y <ym) then
    begin
      Result := 180 + Result;
      exit;
    end;
 
    // 4. Quadrant --> den Betrag des Winkels von 360° subtrahieren
    if (X > xm) and (Y < ym) then
    begin
      Result := 360 - Result;
      exit;
    end;
  end;
end;
 

fpGUIcoder
Beiträge: 199
Registriert: Di 20. Okt 2015, 23:13

Re: Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Beitrag von fpGUIcoder »

Hallo wp_xyz,

Danke erst mal! Scheint zu funktionieren, :) obwohl ich gegenüber der Originalfunktion aus Windows eine Abweichung habe, die auch auf Rundungsfehlern beruhen kann. Der Winkel der von den beiden Strahlen aufgespannt wird ist in meiner Funktion etwas klaeiner, aber sichtbar kleiner, gefühlte 6 Grad gesamt, 2-3 Grad für jeden der beiden Strahlen.

Hier die Originalfunktion mit den Testparametern:

Windows.Pie(WndDC, 300, 110, 500, 200, 310, 110, 200, 260);

Die Abweichung beträgt gefühlte 2-3 Grad am Startwinkel und am Endwinkel.

Hier jetzt mein neuer Code. Winkel mit Deiner Funktion berechnet.

Code: Alles auswählen

 
 
//Ein anderer Weg zum Ellipsenradius in einem konkreten Punkt
//Funktionirt leider gar nicht --> Absturz!!!
function EllipseRadius(X,Y,a,b, grad: Extended; out p: TPoint): Extended;
var
  sqrsin,sqrcos,radius: Extended;
  sqra,sqrb: Extended;
begin
  sqrsin := Round(sin(grad * pi/180)) mod 360;
  sqrsin := sqr(sqrsin);
  sqrcos := Round(cos(grad * pi/180)) mod 360;
  sqrcos := sqr(sqrcos);
  radius := sqrt(sqrcos+sqrsin);
 
  sqra := sqr(a) * sqr(radius);
  sqrb := sqr(a) * sqr(a)*sqrsin;
 
  p.X  := Round(abs(sqrt(sqra - sqrb))+X);
 
  sqra := sqr(b) * sqr(radius);
  sqrb := sqr(b) * sqr(sqrcos);
 
  p.Y  := Round(abs(sqrt(sqra - sqrb))+Y);
 
  Result := radius;
end;
 
//Meine EllipsenbogenFunktion:
function DrawEllipseSeg(Handle: HDC; X, Y, startgrd, endgrd, Xradius, Yradius: Integer; Color: COLORREF): TRect;
var angle,delta: Integer; ptStart,ptEnd: TPoint;
begin
  if startgrd < endgrd then
  begin
    for angle := startGrd to endGrd do
    begin
      SetPixel(Handle,
        Round(Xradius * cos(angle * pi / 180)) + x,
        Round(Yradius * sin(angle * pi / 180)) + y,
        Color
        );
      if Round(angle) = Round(startgrd)  then
      begin
        ptStart.X := Round(Xradius * cos(angle * pi / 180)) + x;
        ptStart.Y := Round(Yradius * sin(angle * pi / 180)) + y;
      end
      else
      if Round(angle) = Round(endgrd) then
      begin
        ptEnd.X := Round(Xradius * cos(angle * pi / 180)) + x;
        ptEnd.Y := Round(Yradius * sin(angle * pi / 180)) + y;
      end;
    end;
  end
  else
  if startgrd > endgrd then
  begin
    delta := startgrd - endgrd;
    startgrd := StartGrd - delta;
    endgrd := endgrd + delta;
    for angle := startGrd to endgrd do
    begin
      SetPixel(Handle,
        Round(Xradius * cos(angle * pi / 180)) + x,
        Round(Yradius * sin(angle * pi / 180)) + y,
        Color
        );
      if Round(angle) = Round(startgrd)  then
      begin
        ptStart.X := Round(Xradius * cos(angle * pi / 180)) + x;
        ptStart.Y := Round(Yradius * sin(angle * pi / 180)) + y;
      end
      else
      if Round(angle) = Round(endgrd) then
      begin
        ptEnd.X := Round(Xradius * cos(angle * pi / 180)) + x;
        ptEnd.Y := Round(Yradius * sin(angle * pi / 180)) + y;
      end;
    end;
  end;
  Result.Left  := ptStart.X// ist für die Begrenzungslinien des Tortentüxkes gedacht
  Result.Top   := ptStart.Y;
  Result.Right := ptEnd.X;
  Result.Bottom:= ptEnd.Y
end;
 
//Meine aktuelle Pie Funktion --> Prameter wie Original
function Pie(dc: HDC; Left, Top, Right, Bottom, xStart, yStart, xEnd, yEnd: Integer): BOOL;
var
  xm, ym, a, b: Integer;
  stangle, endangle, delta, radius: Extended;
  r: TRect;
begin
  xm := Left + Abs(Right - Left) div 2;
  ym := Top + Abs(Bottom - Top) div 2;
 
  a := Right - xm;
  b := Bottom - ym;
 
  r := DrawEllipseSeg(dc, xm, ym, Round(QuadrantAngle(xStart-Left,yStart-Top,xm-Left,ym-Top)),Round(QuadrantAngle(xEnd-Left,yEnd-Top,xm-Left,ym-Top)), a, b, GetDCPenColor(dc));
 
//Hier werden die Begrenzungen des Tortenstückes gezeichnet
//Zuerst vom x,y Punkt der Ellipse am Startwinkel
  MoveToEx(dc, r.Left, r.Top, nil);
  LineTo(dc, xm, ym);
//Danach vom x,y Punkt der Ellipse am Endwinkel
  MoveToEx(dc, r.Right, r.Bottom, nil);
  LineTo(dc, xm, ym);
//Das funktioniert, unabhängig vom Quadranten
 
//Ein Floodfill ALgorithmus --> funktioniert noch nicht, kann aber mit falscher ANwendung von Stift und Pinsel zusammenhängen. Teste ich morgen.
  fill(dc, xm+20,ym, COLOR_APPWORKSPACE, GetDCBrushColor(dc));
end;
 
//Hier noch der Floodfill Algo:
von hier direkt nach Pascal portiert:
https://de.wikipedia.org/wiki/Floodfill
 
//Hier der Fill4 Algorithmus:
function fill(dc: HDC; X,Y: Integer; OldColor, NewColor: Colorref): Boolean;
begin
  Result := false;
  if GetPixel(dc, X,Y) = OldColor then
  begin
    SetPixel(dc, X,Y, NewColor);
    fill(dc, x, y + 1, OldColor, NewColor); // unten
    fill(dc, x, y - 1, OldColor, NewColor); // oben
    fill(dc, x - 1, y, OldColor, NewColor); // links
    fill(dc, x + 1, y, OldColor, NewColor); // rechts  end;
  end;
end;
 


Die Original Pie() Funktion ist mit der aktuellen Pinselfarbe gefüllt.

wp_xyz hat geschrieben:Zur Ellipsengleichung: Da hast du sicher nicht "alle Erklärungen" gefunden, denn Google spuckt folgenden Artikel ziemlich weit oben aus: http://www.mathe-online.at/materialien/ ... llipse.pdf


Danke, diese Abhandlung kenne ich, die Konstruktion der Ellipse erfolgt da aber mittels der Brennpunkte der Ellipse.

Leider habe ich die folgende Erklärung erst heute gefunden:
und zwar hier:

https://en.wikipedia.org/wiki/Ellipse
General parametric form[edit]

An ellipse in general position can be expressed parametrically as the path of a point , where



as the parameter t varies from 0 to 2π. Here is the center of the ellipse, and is the angle between the -axis and the major axis of the ellipse.
Parametric form in canonical position[edit]

Parametric equation for the ellipse (red) in canonical position. The eccentric anomaly t is the angle of the blue line with the X-axis.

For an ellipse in canonical position (center at origin, major axis along the X-axis), the equation simplifies to



Note that the parameter t (called the eccentric anomaly in astronomy) is not the angle of with the X-axis.

For a given point on an ellipse, formulae connecting the tangential angle , the polar angle from the ellipse center , and the parametric angle t[26] are:[27][28][29][30]


Da geht man von 2 Kreisen aus, der erste im Radius a, der zweite im Radius b.

Ein Strahl läuft um den Mittelpunkt, wenn der bei 90 oder 270 Grad steht befindet sich der an diesem Strahl befestigte Stift auf dem Radius des inneren Kreises mit dem kürzeren Radius b. Nun ändert sich die Stiftlänge abhängig vom Winkel und hat ihr Maximum (a-b) bei 0- und 180 Grad. Die Gleichung ist im verlinkten Wikipedia Artikel unter "Parametric form in canonical position" zu finden. Hätte ich gut gebrauchen können.


Bei der Erklärung in der PDF Datei habe ich mich an den Brennpunkten gestört, ich wollte eine Funktion die vom Mittelpunkt der Ellipse ausgeht und den richtigen Radius der Ellipse abhängig vom Winkel korrekt berechnet und dann 360 Grad umläuft. Da wäre wohl die oben beschriebene parametrische Form eher meine Freundin gewesen. Leider erst heute gefunden. :roll:

wp_xyz hat geschrieben:Was ist eigentlich genau die Aufgabenstellung? Ein Tortenstück ("pie") zu zeichnen? Was sind die gegebenen Voraussetzungen? (Rechteck, das die Ellipse umschreibt; sowie zwei Punkte für die Definition der Start- und Endwinkel?)


Genau, ein Tortenstück zeichnen unter den gleichen Voraussetzungen, wie das die Windows API Funktion Pie() macht. Ein Rechteck umschließt die Ellipse. Die Koordinaten des Rechteckes werden der Pie Funktion übergeben. Zusätzlich erhält die Funktion die X,Y Koordinaten von Start- und Endpunkt zweier Strahlen, die vom Mittelpunkt der Ellipse ausgehen und den Winkel beschreiben, den das Tortenstück aufklappt.

Zu deinem neuen Code: Warum nimmst du plötzlich Windows-Funktionen?


Nicht jetzt erst, sondern von Beginn an. Im Demoprogramm "tut1" habe ich deshalb für die Originalfunktionen Windows. davor gesetzt, für meine Funktion den Namen meiner Unit "arcs" mit den von mir programmierten Funktionen. So kann ich das Verhalten meiner Funktionen testen, indem ich deren Verhalten mit dem Original vergleiche. Ich baue die Windows Funktionen nach.

wp_xyz hat geschrieben:Der alte Code funktioniert doch auch, wenn die Fehler mit der Winkelberechnung gelöst sind. Und da bist du jetzt ein gutes Stück weiter. Ich frage mich nur, warum du Left und Top in der Kathetenberechnung verwendest. Und der Fall wegen Division durch 0 fehlt noch.


Immer noch!?! Da muß ich unbedingt noch mal schauen.

Left und Top verwende ich, weil der Mittelpunkt xm,ym im die Ellipse umschließenden Rechteck liegt und ich ja den Winkel zwischen Abszisse und dem Start bzw. Endstrahl brauche. Für das Zeichnen der Ellipse an der korrekten Position habe ich deshalb die Offsets Left und Top zu xm,ym hinzu addiert (xm + Left, ym + Top). xm,ym ist der Mittelpunkt der Ellipse, die vom Rechteck umschlossen wird. Jedoch werden die X,Y Koordinaten der Endpunkte der beiden Strahlen in Fensterkoordinaten übergeben, nicht in Koordinaten des Rechteckes. Deshalb subtrahiere ich in der Winkelberechnungsfunktion Left und Top wieder und habe so die echten X,Y Koordinaten bezogen auf den Ellipsenmittelpunkt. Vorher hatte ich falsche Winkel, nicht nur geringfügig kleiner, sondern auch im falschen Quadranten und mit zu großer Abweichung in der Winkelöffnung. Habe faktisch so das die Ellipse umschließende Rechteck mit der linken oberen Ecke zum Punkt (0,0) meines Ausgabefensters verschoben (rechnerisch).

wp_xyz hat geschrieben:Die Winkelberechnung müsste so richtig sein (nicht getestet):


Scheint Ok zu sein, allerdings habe ich gegenüber der Originalfunktion eine Winkelabweichung, in der Originalfunktion ist der Winkel ein Stück größer.Die besagten gefühlten 3 Grad.

Aber ich kann ja noch mal die neu gefundene Wikipedia Seite zu Rate ziehen.

Da ist wohl die Konstante e der Abstand vom X-Radius zum Brennpunkt F?

Wenn dem so ist, dürfte der Rest Rechenarbeit sein. Kann ja noch ne andere Variante testen. :) Schaun wir mal. Vielleicht ist ja die parametrische Form genauer. Ein Weg um in mathematischen Dingen wieder fitter zu werden. :)
Zuletzt geändert von Lori am Fr 13. Nov 2015, 11:07, insgesamt 1-mal geändert.
Grund: Bitte den richtigen Highlighter verwenden

wp_xyz
Beiträge: 4885
Registriert: Fr 8. Apr 2011, 09:01

Re: Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Beitrag von wp_xyz »

Die Ungenauigkeit, die du erwähnst, rührt davon her, dass die Grad-Berechnung für sich zwar richtig ist, aber in dem Zusammenhang, nämlich den Winkel zwischen der x-Achse und der Geraden vom Ellipsenmittelpunkt zu einem Punkt auf der Ellipse zu bestimmen, angepasst werden muss. Wäre die Ellipse ein Kreis, dann wäre die Rechnung korrekt. Um auf den Fall der Ellipse zu verallgemeinern, stelle ich mir die Ellipse als Kreis vor, der in einer Koordinatenrichtung gestaucht bzw. gestreckt wird, im verhältnis der beiden Halbachsen a/b. Daher muss ich die eine Koordinate mit diesem Verhältnis multiplizieren, um in dieser Richtung eine entsprechend andere Längenskale zu erhalten (oder x mit b und y mit a multiplizieren), so dass die QuadrantAngle-Funktion wieder einen Kreis "sieht":

Code: Alles auswählen

 
var
  dx, dy: Integer;
begin
  dx := X - xc;
  dy := Y - yc;
  FStartAngle := QuadrantAngle(dx*b + xc, dy*a + yc, xc, yc);   

Zu deinem Windows-Code will ich mich nicht auslassen, ich habe, als ich von Delphi zu Lazarus gewechselt bin, aufgehört, mich mit den Windows-Zeichenroutinen zu befassen, TCanvas kann das alles auch, und dazu plattform-unabhängig. So viel nur zum Füllen: Hast du die Füllfarbe auf den Brush gelegt?

Ich lege dir mal meine Version basierend auf LCL-Canvas-Methoden bei. Klicke ins Formular und ziehe die Maus ellipsenförmig herum. Das Tortensegment zeichne ich als Polygon, damit kann man sich das langsame FloodFill sparen (mit Windows-Routinen geht das sicher auch). (man könnte natürlich auch TCanvas.Pie nehmen, aber das ist wahrscheinlich nicht Sinn der Übung...)

[EDIT]
Eine Bemerkung zum FloodFill ist mir noch eingefallen: FloodFill benötigt eine geschlossene Umrandung der zu bemalenden Fläche. Du zeichnest den Ellipsenbogen aber mit sukzessiven Aufrufen von SetPixel, was leicht Lücken zwischen den Punkten offen lassen kann. Nimm stattdessen aufeinanderfolgende Line-Befehle (oder gleich Polygon, was auch gleich die Füllfarbe setzt, wenn Anfangs- und Endpunkt zusammenfallen).
Dateianhänge
DrawPie.zip
(3.09 KiB) 101-mal heruntergeladen

fpGUIcoder
Beiträge: 199
Registriert: Di 20. Okt 2015, 23:13

Re: Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Beitrag von fpGUIcoder »

wp_xyz hat geschrieben:Die Ungenauigkeit, die du erwähnst, rührt davon her, dass die Grad-Berechnung für sich zwar richtig ist, aber in dem Zusammenhang, nämlich den Winkel zwischen der x-Achse und der Geraden vom Ellipsenmittelpunkt zu einem Punkt auf der Ellipse zu bestimmen, angepasst werden muss. Wäre die Ellipse ein Kreis, dann wäre die Rechnung korrekt. Um auf den Fall der Ellipse zu verallgemeinern, stelle ich mir die Ellipse als Kreis vor, der in einer Koordinatenrichtung gestaucht bzw. gestreckt wird, im verhältnis der beiden Halbachsen a/b. Daher muss ich die eine Koordinate mit diesem Verhältnis multiplizieren, um in dieser Richtung eine entsprechend andere Längenskale zu erhalten (oder x mit b und y mit a multiplizieren), so dass die QuadrantAngle-Funktion wieder einen Kreis "sieht":

Code: Alles auswählen

 
var
  dx, dy: Integer;
begin
  dx := X - xc;
  dy := Y - yc;
  FStartAngle := QuadrantAngle(dx*b + xc, dy*a + yc, xc, yc);   

Zu deinem Windows-Code will ich mich nicht auslassen, ich habe, als ich von Delphi zu Lazarus gewechselt bin, aufgehört, mich mit den Windows-Zeichenroutinen zu befassen, TCanvas kann das alles auch, und dazu plattform-unabhängig. So viel nur zum Füllen: Hast du die Füllfarbe auf den Brush gelegt?

Ich lege dir mal meine Version basierend auf LCL-Canvas-Methoden bei. Klicke ins Formular und ziehe die Maus ellipsenförmig herum. Das Tortensegment zeichne ich als Polygon, damit kann man sich das langsame FloodFill sparen (mit Windows-Routinen geht das sicher auch). (man könnte natürlich auch TCanvas.Pie nehmen, aber das ist wahrscheinlich nicht Sinn der Übung...)

[EDIT]
Eine Bemerkung zum FloodFill ist mir noch eingefallen: FloodFill benötigt eine geschlossene Umrandung der zu bemalenden Fläche. Du zeichnest den Ellipsenbogen aber mit sukzessiven Aufrufen von SetPixel, was leicht Lücken zwischen den Punkten offen lassen kann. Nimm stattdessen aufeinanderfolgende Line-Befehle (oder gleich Polygon, was auch gleich die Füllfarbe setzt, wenn Anfangs- und Endpunkt zusammenfallen).



Danke wie verrückt, jetzt funktioniert das Zeichnen meiner Ellipse!

Allerdings ungefüllt. Wir passiert dasFüllen Diner Ellipse? Da sehe ich im Quellcode nicht.

Hier der Ausschnitt aus DrawEllipse:

Code: Alles auswählen

 
  while not done do begin
    if ARotation <> rUndefined then
      angle := angle + delta_angle;
    if angle >= EndAngle then begin
      done := true;
      angle := EndAngle;
    end;
    points[nPoints] := Point(xc + round(a*cos(DegToRad(angle))), yc + round(b*sin(DegToRad(angle)))); //Ist doch sicher nur die Begrenzungslienie?
    inc(nPoints);
    if nPoints > Length(points) then SetLength(points, Length(points) + 1000);
  end;
  points[nPoints] := Point(xc, yc);
  inc(nPoints);
  SetLength(points, npoints);
 


Später werden die Punkte mit der Polgon Methode gezeichnet und vom System automatisch mit der angegebenen Füllfarbe gefüllt. Ich hatte die Ellipse mit SetPixel gezeichnet, daher nur die Außenlinie.

Wücnsch erst mal frohes Wochenende? :)

Werde erst mal alles ordnen!

wp_xyz
Beiträge: 4885
Registriert: Fr 8. Apr 2011, 09:01

Re: Winkelberechnung über 4 Quadranten nicht korrekt, warum?

Beitrag von wp_xyz »

fpGUIcoder hat geschrieben:Wir passiert dasFüllen Diner Ellipse? Da sehe ich im Quellcode nicht...Später werden die Punkte mit der Polgon Methode gezeichnet und vom System automatisch mit der angegebenen Füllfarbe gefüllt.

Der von dir zitierte Teil ist der 1.Schritt: er stellt die Eckpunkte des Polygons bereit. Im 2.Schritt wird dann das Polygon wird durch die Canvas-Methode "Polygon" gezeichnet. Es wird automatisch gefüllt, einmal weil es geschlossen ist (d.h. Endpunkt = Startpunkt) und weil der Brush-Style des Canvas nicht bsClear ist. Mit der entsprechenden Windows-Methode dürfte es ähnlich gehen.

Antworten