unit a1_u;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls,vectors,math, Spin,Spheres, Buttons, ComCtrls,memory;

type
  TmainForm = class(TForm)
    SphereSpeed: TSpinEdit; //Kugelgeschwindigkeit
    NormalSpeed: TRadioButton; //Die Kugeln haben eine endliche Geschwindigkeit?
    KugelTeleportation: TRadioButton;
    CenterOnCenter: TButton; //Zentriert die Ansicht auf den Umkreismittelpunkt
    PaintBox1: TPaintBox; //Ausgabefeld für die Kugeln
    Timer1: TTimer;  //Bewegt die Kugeln
    size: TSpinEdit; //Vergrößerungsfaktor
    sphereCount: TSpinEdit; //Anzahl der zu erzeugenden Kugeln
    ProgressBar1: TProgressBar; //Fortschritt beim Kugel erstellen
    deleteOldSpheres: TCheckBox; //Alte Kugeln vor den erstellen von neuen löschen?
    numberThem: TCheckBox; //Kugeln durchnummerieren?
    differentRadiuses: TCheckBox; //Kugen mit verschiedenene Radien erzeugen?
    MinSphereRadius: TSpinEdit; //Minimaler
    MaxSphereRadius: TSpinEdit; //Maximaler
    stats: TButton; //Statistik aufrufen
    SpheresAsCircels: TCheckBox; //Kugeln als Kreise betrachten?
    createSpheres: TBitBtn; //Kugeln erzeugen
    showAll: TBitBtn;  //Zeigt alle Kugeln
    open3DMode: TBitBtn; //3D-Modus aufrufen
    colorOptions: TBitBtn;  //Farbeinstellungsdialog aufrufen
    close: TBitBtn; //Beendenbutton
    showedSpheres: TLabel;//Anzahl der angezeigten ruhenden Kugeln
    showedMoving: TLabel; //Anzahl der angezeigten sich bewegenden Kugeln
    saveSize: TSpinEdit; //Größe in der abgespeichert wird
    StatusBar1: TStatusBar; //Statusinformationen
    moveMarker: TShape; //Markiert die Bewegungspfeile
    save: TBitBtn; //Abspeichern
    fileName: TEdit; //Dateiname
    selectFileName: TBitBtn; //Ruft den Dateinamenauswahldialog von Windows auf
    SaveDialog1: TSaveDialog; //den hier
    invertedScroll: TCheckBox; //Scrollrichtung invertieren?
    //Buttons zum Scrollen der angezeigten Kugeln
    //Die Anordnung ist folgendermaßen
    SpeedButton1: TSpeedButton;SpeedButton2: TSpeedButton;SpeedButton3: TSpeedButton;
    SpeedButton4: TSpeedButton;SpeedButton5: TSpeedButton;SpeedButton6: TSpeedButton;
    SpeedButton7: TSpeedButton;SpeedButton8: TSpeedButton;SpeedButton9: TSpeedButton;
    Timer2: TTimer;
    //Designelemente
    Label3: TLabel;    Label4: TLabel;    Label1: TLabel;    Panel1: TPanel;
    Label6: TLabel;    Label7: TLabel;    Label8: TLabel;    Label10: TLabel;
    Label11: TLabel;    Label13: TLabel;    Label9: TLabel;    Label14: TLabel;
    Label5: TLabel;    Bevel1: TBevel;    Bevel2: TBevel;    Bevel3: TBevel;
    Label15: TLabel;
    showExactCircel: TButton;
    //Initialisierung der Balllisten
    procedure FormCreate(Sender: TObject);
    //Freigabe
    procedure FormDestroy(Sender: TObject);
    //Kugeln müssen neu gezeichen werden
    procedure PaintBox1Paint(Sender: TObject);
    //Erstellen neuer Kugeln per Maus
    //Klick
    procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    //Bewegung
    procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    //Loslassen
    procedure PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    //Weiterbewegen von Hand gemachten Kugeln 
    procedure Timer1Timer(Sender: TObject);
    //Doublebuffer neu einstellen
    procedure FormResize(Sender: TObject);
    //Kugeln erzeugen
    procedure createSpheresClick(Sender: TObject);
    //Auf einen Scrollpfeil geklickt => Scrollen gestartet
    procedure SpeedButtonMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    //Losgelassen => Scrollen gestoppt
    procedure SpeedButtonMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    //Scrollbewegung
    procedure Timer2Timer(Sender: TObject);
    //Scrollen über die Mittelpfeile startet
    procedure SpeedButton5MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    //Scrollen über die Mittelpfeile gestoppt
    procedure SpeedButton5MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    //Größe aktualisieren
    procedure sizeChange(Sender: TObject);
    //Auf Mittelpunkt zentrieren
    procedure CenterOnCenterClick(Sender: TObject);
    //Alle Kugel zeigen
    procedure showAllClick(Sender: TObject);
    //Dateinamen auswählen lassen
    procedure selectFileNameClick(Sender: TObject);
    //Abspeichern
    procedure saveClick(Sender: TObject);
    //Einstellen, dass es jetzt verschiedene Radien gibt
    procedure differentRadiusesClick(Sender: TObject);
    //3D-Modus starten
    procedure open3DModeClick(Sender: TObject);
    //Statistik öffnen
    procedure statsClick(Sender: TObject);
    //Farbeinstellungen aufrufen
    procedure colorOptionsClick(Sender: TObject);
    //Einstellen, dass Kugeln als Kreise behandelt werden sollen
    procedure SpheresAsCircelsClick(Sender: TObject);
    //Beenden
    procedure closeClick(Sender: TObject);
    //Beim ersten Start die Hilfe aufrufen
    procedure FormShow(Sender: TObject);
    procedure numberThemClick(Sender: TObject);
    procedure showExactCircelClick(Sender: TObject);
  private
    { Private-Deklarationen}
  public
    { Public-Deklarationen}

    Spheres: TSphereList; //Liste (und Speicherverwaltung) der Kugeln, siehe spheres.pas

    //minimale/maximale Ausdehnung eines Recteckes, das alle Kugeln enthält
    spMinX,spMaxX,spMinY,spMaxY:integer;
    //Mittelpunkt und Radius eines Kreises (genaugenommen unendlich hoher Zylinder)
    //der alle Kugeln enthält
    CircelCenter:TVector2Int;
    CircelRadius:integer;

    IsThereExactCircel: boolean;//Gibt an, ob der exakte Umkreis berechnet wurde
    ExactCircelCenter:TVector2Int; //Exakter Umkreismittelpunkt
    ExactCircelRadius:integer; //Exakter Umkreisradius


    xmove,ymove:integer;//Verschiebung der Kugeln zur Darstellung
    sizeFactor:integer;//Größenfaktor in Prozent (100: genau, 50: halb so groß)

    doubleBuffer:TBitmap;//Doublebuffer der Paintbox

    selectedMoveButton:tspeedbutton;//Ausgewählter Scrollbutton
    mouseMoving:boolean;//Folgt der Kugelhaufen der Maus
    amsX,amsY:integer; //Startposition des Scrollen
    moveCursor: array[0..2,0..2] of tcursor;//Richtungspfeilcursor

    creatingNewSphere:boolean; //Wird gerade eine neue Kugel erstellt?
    newSphereX,newSphereY:integer; //Position der neuen Kugel (Bildschirmkoordinaten)
    newSpherePos:TVector2Int; //Position der neuen Kugel (Virtuelle Koordinaten)
    newSphereDir:TVector2Float; //Richtung der neuen Kugel
    newSphereR:integer; //Radius

    //Einstellungen
    useStandardRadius:boolean; //soll es verschiedene Radien geben
    useCircels: boolean; //Sollen die Kugeln wie Kreise behandelt werden

    //Funktionen für den NonClient Hilfebutton (WindowProc zu überschreiben
    //wäre sinnvoller gewesen, aber ich habe wegem Abgabetermin nicht genug Zeit
    //dass noch zu ändern)
    pressedButton:integer; //gedrückter Buttn
    procedure WM_NCHITTEST(var mes:TWMNCHITTEST);message WM_NCHITTEST;
    procedure WM_NCLBUTTONDOWN(var mes:TWMNCLBUTTONDOWN);message WM_NCLBUTTONDOWN;
    procedure WM_NCLBUTTONUP(var mes:TWMNCLBUTTONUP);message WM_NCLBUTTONUP;
    procedure WM_NCPAINT(var mes:TWMNCPAINT);message WM_NCPAINT;
    procedure WM_NCACTIVATE(var mes:TWMNCPAINT);message WM_NCACTIVATE;

    //Kollision berechnen
    procedure calculateKollision(const dir:TVector2Float;const pos:TVector2Int;const radius:byte;
                                  out kollisionTime:single; out kollisionPos:TVector2Int);
    //Kugel hinzufügen (die in eine zufällige Richtung auf die Kugeln zurollt)
    function addSphere(const x,y:integer;const r:byte; len:single=-1):TMovingSphere;
    //Kugel mit angegebener Richtung hinzufügen
    function addUserSphere(const x,y:integer;const dir:TVector2Float;const r:byte):TMovingSphere;

    //Setzt die Werte des Umkreises zurück
    procedure resetCircelRadius();
    //Berechnet den Umkreis nach Einfügen von newSphere
    procedure updateCircel(const newSphere:TStaticSphere);

    //Berechnet die neuen Position der Kugeln nach Bewegen
    procedure updateSpheres;
    //Zeichnet die Kugeln mit den angegebenen Einstellungen
    procedure drawSpheres(const size,xmove,ymove:integer;const numbers:boolean;output:TBitmap);
    //Zeichnet die Kugeln mit den ausgewählten Einstellungen auf den Bildschirm
    procedure drawSpheresDefault;
  end;
var
  mainForm: TMainForm;
implementation

uses statistik_u, farbe, threeD, helpWindow;

{$R *.DFM}
{$R cursors.res}

//Zufällige Zahlen zwischen min und max
function rrandom(min,max:integer):integer;overload;
begin
  result:=random(max-min+1)+min;
end;
function rrandom(min,max:single):single; overload;
begin
  result:=(random(trunc((max-min)*1000000))+trunc(min*1000000))/1000000;
end;

//==============================================================================
//            Wichtige Methoden für die automatische Kugelberechnung
//==============================================================================

//Kollision der Kugel mit radius mit Richtung dir an der Position pos mit den
//anderen Kugeln berechnen.
//kollisionTime ist die Entfernung zum Kollisionspunkt (also die Zeit die die
//Kugel noch rollen müsste)
procedure TmainForm.calculateKollision(const dir:TVector2Float;const pos:TVector2Int;const radius:byte;
                                    out kollisionTime:single; out kollisionPos:TVector2Int);
var i:integer;
    Sphere,endSphere:TStaticSphere;
    a,b,b_lookup,c:single;//Werte der abc-Formel
    wurzelTerm,oldb,oldw,ltemp:single; //temporäre Werte
    nearer:boolean; //dieser Kollisionspunkt ist näher
begin
  //Wie die Berechnung der Kollision abläuft steht in der Schleifen

  oldb:=1e30;
  oldw:=-1e30;

  //Vorberechnung von a (siehe Schleife)
  a:=4*vecDot(dir,dir);
  if a=0 then begin //Die Kugel bewegt sich gar nicht? Hoppla ;-)
    kollisionTime:=1e35;
    kollisionPos:=makeVector(0,0);
    exit;
  end;
  b_lookup:=(dir.x*pos.x+dir.y*pos.y); //Einen Teil von b berechnen

  i:=0;
  if useStandardRadius or useCircels then begin
    //Die Kugeln werden entweder als Kreise betrachtet, oder aber sie haben alle
    //den gleichen Radius, und verhalten sich auf jeden Fall wie Kreise.
    while i<=Spheres.countStatic-1 do begin
      Sphere:=Spheres.getStaticSphere(i);
      if i+StaticSpheresInArray<Spheres.countStatic then
        endSphere:=Spheres.getStaticSphere(i+StaticSpheresInArray-1)
       else
        endSphere:=Spheres.getStaticSphere(Spheres.countStatic-1);
      i:=i+StaticSpheresInArray;
      while (cardinal(Sphere)<=cardinal(endSphere)) do begin
        //Hier wird jetzt der Kollisionspunkt zwischen Sphere und den übergebenen
        //Werten gesucht:
        //Zu finden Ein l mit distance(pos+l*dir,Sphere.pos)=r1 + r2
        {
          length(pos+l*dir-Sphere.pos)=r1+r2

          sqrt(<pos+l*dir-Sphere.pos,pos+l*dir-Sphere.pos>)=(r1+r2)^2

          <pos+l*dir-sphere.pos,pos+l*dir-sphere.pos>=(r1+r2)^2

          <l*dir,pos+l*dir-sphere.pos>+
          <pos-sphere.pos,pos+l*dir-sphere.pos>=(r1+r2)^2

          <l*dir,pos-sphere.pos> + <l*dir,l*dir>+
          <pos-sphere.pos,pos+l*dir-sphere.pos>=(r1+r2)^2

          <l*dir,pos-sphere.pos> + <l*dir,l*dir>+
          <pos-sphere.pos,pos-sphere.pos>+<pos-sphere.pos,l*dir>=(r1+r2)^2

          <l*dir,l*dir>+
          <l*dir,pos-sphere.pos> + <pos-sphere.pos,l*dir>+
          (<pos-sphere.pos,pos-sphere.pos>-(r1+r2)^2)=0

          l*l*<dir,dir>+
          l*(<dir,pos-sphere.pos> + <pos-sphere.pos,dir>)+
          (<pos-sphere.pos,pos-sphere.pos>-(r1+r2)^2)=0

          l*l*<dir,dir>+
          l*(<pos-sphere.pos,dir> + <pos-sphere.pos,dir>)+
          (<pos-sphere.pos,pos-sphere.pos>-(r1+r2)^2)=0

          l*l*<dir,dir>+
          l*(2*<pos-sphere.pos,dir>)+
          (<pos-sphere.pos,pos-sphere.pos>-(r1+r2)^2)=0

          a=  <dir,dir>
          b=  <dir,pos-sphere.pos> + <pos-sphere.pos,dir>
          c=(-(r1+r2)^2 + <pos-sphere.pos,pos-sphere.pos>)

          b weiter optimiert:
          b = 2*<dir,pos-sphere.pos>
          = 2*(<dir,pos>-<dir,sphere.pos>)
          Das erste Skalarprodukt, kann vorberechnet werden
        }

        //Das b ist negativiert, um eine subtraktion zu sparen
        b:=(-2)*(b_lookup-(dir.x*Sphere.pos.x+dir.y*Sphere.pos.y));
        if b<0 then begin
          inc(Sphere);
          continue; //Bei b<0,  wäre der andere Kreis hinter diesem
        end;
        c:=sqr(pos.x-Sphere.pos.x)+sqr(pos.y-Sphere.pos.y)-sqr(radius+Sphere.r);

        wurzelTerm:=b*b-a*c;
        if (wurzelTerm<0) then begin
          inc(Sphere);
          continue;
        end;
        { Der Übersichtlichkeit halber nenn ich oldb b#, oldw w#, wurzelTerm w

          Die Frage ist jetzt, ist b# - sqrt(w#) > b - sqrt(w), also ist dieser
          Kollisionspunkt näher
          Fall 1: b# > b und w# < w:             JJJJJ    AAAA
               => sqrt(w#) < sqrt(w)                  J  AA    AA
               => b# + sqrt(w) > b + sqrt(w)     J    J  AAAAAAAA
               => b# - sqrt(w#)> b - sqrt(w)      JJJJ   AA    AA
               Ja

          Fall 2: b# < b und w# > w:            NNNN   NN  OOOOOOO
               => sqrt(w#) > sqrt(w)             NN NN  NN  OO   OO
               => b# + sqrt(w) < b + sqrt(w)     NN  NN NN  OO   OO
               => b# - sqrt(w#)< b - sqrt(w)     NN   NNNN  OOOOOOO
               Nein

          Fall 3: b# > b und w# > w
               b# - sqrt(w#)  ?  b - sqrt(w)
               b# - b ? sqrt(w#) - sqrt(w)
               Beide Seiten sind größer gleich 0
               sqr(b# - b) ? sqr(sqrt(w#) - sqrt(w))
               sqr(b# - b) ? w# - 2sqrt(w# * w) + w
               sqr(b# - b) - w# - w ?  - 2sqrt(w# * w)
               (w# + w - sqr(b# - b)) / 2   !? sqrt(w# * w)
               Die rechte Seite ist > 0.
                 Unterfall 1: Linke Seite >= 0
                   sqr((w# + w - sqr(b# - b)) / 2)   !?  w# * w
                   sqr((w# + w - sqr(b# - b)) )/ 4   !?  w# * w
                   sqr((w# + w - sqr(b# - b)) )      !?  4 * w# * w
                   Ja, wenn sqr((w# + w - sqr(b# - b))  < 4 * w# * w
                 Unterfall 2: Linke Seite < 0
                   Ja

          Fall 4: b# < b und w# < w
               b# - sqrt(w#)  ?  b - sqrt(w)
               b# - b ? sqrt(w#) - sqrt(w)
               Beide Seiten sind kleiner gleich 0
               sqr(b# - b) !? sqr(sqrt(w#) - sqrt(w))
               sqr(b# - b) !? w# - 2sqrt(w#*w) + w
               sqr(b# - b) - w# - w !?  - 2sqrt(w# * w)
               (w# + w - sqr(b# - b)) / 2   ? sqrt(w# * w)
               Die rechte Seite ist > 0.
                 Unterfall 1: Linke Seite > 0
                   sqr((w# + w - sqr(b# - b)) / 2)   ?  w# * w
                   sqr((w# + w - sqr(b# - b)) )/ 4   ?  w# * w
                   sqr((w# + w - sqr(b# - b)) )      ?  4 * w# * w
                   Ja, wenn sqr((w# + w - sqr(b# - b)) >  4 * w# * w
                 Unterfall 2: Linke Seite < 0
                   Nein
        }
        if oldb>= b then begin
          if oldw <= wurzelTerm then nearer:=true //Fall 1
          else begin //Fall 3
            ltemp:=(oldw + wurzelTerm - sqr(oldb - b));//(oldw + wurzelTerm - sqr(oldb - b)) / 2;
            if ltemp>= 0 then  nearer:=ltemp*ltemp<4*oldw*wurzelTerm//Unterfall 1
            else nearer:=true;//Unterfall2
          end;
        end else begin
          if oldw < wurzelTerm then begin //Fall 4
            ltemp:=(oldw + wurzelTerm - sqr(oldb - b));//(oldw + wurzelTerm - sqr(oldb - b)) / 2;
            if ltemp>= 0 then  nearer:=ltemp*ltemp>4*oldw*wurzelTerm//Unterfall 1
            else nearer:=false;//Unterfall 2
          end else nearer:=false; //Fall 2
        end;
        if nearer then begin
          oldb:=b;
          oldw:=wurzelTerm;
        end;
        inc(Sphere);
      end;
    end;
  end else begin
    //Wenn die Kugeln als Kugeln betrachtet werden, können sie unter/über
    //einander rollen und die Berechnung muss 3dimensional sein, wo bei ihre
    //Abstand von der Ebene genau der Radius ist
    while i<=Spheres.countStatic-1 do begin
      Sphere:=Spheres.getStaticSphere(i);
      if i+StaticSpheresInArray<Spheres.countStatic then
        endSphere:=Spheres.getStaticSphere(i+StaticSpheresInArray-1)
       else
        endSphere:=Spheres.getStaticSphere(Spheres.countStatic-1);
      i:=i+StaticSpheresInArray;
      while (cardinal(Sphere)<=cardinal(endSphere)) do begin
        //Das b ist negativiert, dann ist eine subtraktion gespart
        b:=(-2)*(b_lookup-(dir.x*Sphere.pos.x+dir.y*Sphere.pos.y));
        if b<0 then begin
          inc(Sphere);
          continue; //Bei b<0,  wäre der andere Kreis hinter
        end;
        // c:= Sphere2.pos.y) ... sqr(radius-Sphere2.r)-sqr(radius+Sphere2.r); wird zu:
        c:=sqr(pos.x-Sphere.pos.x)+sqr(pos.y-Sphere.pos.y)-4*radius*Sphere.r;

        wurzelTerm:=b*b-a*c;
        if (wurzelTerm<0) then begin
          inc(Sphere);
          continue;
        end;
        if oldb>= b then begin
          if oldw <= wurzelTerm then nearer:=true //Fall 1
          else begin //Fall 3
            ltemp:=(oldw + wurzelTerm - sqr(oldb - b));//(oldw + wurzelTerm - sqr(oldb - b)) / 2;
            if ltemp>= 0 then  nearer:=ltemp*ltemp<4*oldw*wurzelTerm//Unterfall 1
            else nearer:=true;//Unterfall2
          end;
        end else begin
          if oldw < wurzelTerm then begin //Fall 4
            ltemp:=(oldw + wurzelTerm - sqr(oldb - b));//(oldw + wurzelTerm - sqr(oldb - b)) / 2;
            if ltemp>= 0 then  nearer:=ltemp*ltemp>4*oldw*wurzelTerm//Unterfall 1
            else nearer:=false;//Unterfall 2
          end else nearer:=false; //Fall 2
        end;
        if nearer then begin
          oldb:=b;
          oldw:=wurzelTerm;
        end;
        inc(Sphere);
      end;
    end;
  end;
  if oldw>=0 then begin
   //Es gibt eine Kollision, jetzt wird die abc-Formel zu Ende geführt
   kollisionTime:=2*(oldb-sqrt(oldw))/a;
    if kollisionTime<1e10 then
      kollisionPos:=vecAdd(pos,vectrunc(vecmul(dir,kollisionTime)))
  end else kollisionTime:=1e35;
end;

//Fügt eine Kugel mit Radius r an der Stelle (x,y) ein
//len gibt den Abstand der Kugel vom Umkreismittelpunkt an, wenn er nicht angegeben
//wird, wird er berechnet
function TmainForm.addSphere(const x,y:integer;const r:byte; len:single=-1):TMovingSphere;
var Sphere:TMovingSphere; //Neue Kugel
    alpha,usedalpha:single; //Sichtwinkel
    longDir:TVector2Int; //Vektor von der Kugel zum Umkreismittelpunkt
begin
  Sphere:=Spheres.createMovingSphere; //Speicher für eine bewegende Kugel holen
  //Einstellungen setzen
  Sphere.currentTime:=0;
  Sphere.pos.x:=x;
  Sphere.pos.y:=y;
  Sphere.r:=r;

  //Richtung berechnen
  longDir:=vecSub(CircelCenter,Sphere.pos); //Vektor von der Kugel zum Mittelpunkt
  if len=-1 then //Die Entfernung kann zur Optimierung angegeben werden, da sie
    len:=vecLength(longDir); //beim automatischen Erzeugen immer bekannt ist.

  //Es gibt ein rechtwinkliges Dreieck zwischen dem Umkreismittelpunkt, dem
  //Kugelmittelpunkt und der Tangente des Umkreises durch den Kugelmittelpunkt
  //(allerdings wird ein Umkreis genommen, dessen Radius um den Kugelradius
  //verlängert wurde, da die Kugel auch mit ihrem Rand wo hängen bleiben kann),
  //wobei die Tangente die Hypotenuse bildet.
  //Der Sinus von dem Winkel zwischen der Tangent und der Strecke zwischen den
  //beiden Mittelpunkten, also der Sichtwinkel, ist nun der Radius, dividiert
  //durch die Entfernung.
  alpha:=(CircelRadius + r)/len;
  if ( alpha<0) or (alpha>1) then alpha:=1;
  alpha:=arcsin(alpha);

  //Einen zufälligen Winkel wählen, so dass die Kugel den Kreis berühren wird.
  usedalpha:=rrandom(-alpha,alpha);
  //Den Vektor entsprechend drehen:
  Sphere.dir:=vecRot(longDir,usedalpha);
  //Normalisieren
  Sphere.dir.x:=Sphere.dir.x/len;
  Sphere.dir.y:=Sphere.dir.y/len;

  //Kollision finden
  calculateKollision(Sphere.dir, Sphere.pos,Sphere.r,Sphere.kollisionTime,Sphere.kollisionPos);

  result:=Sphere;//Kugel zurückgeben
end;

//Aktualisiert den alle Kugeln enthaltenen Kreis mit Radius circelRadius
//um den Mittelpunkt CircelCenter
//Dabei wird versucht den Radius möglichst gering zu halten, gleichzeitig aber
//auch möglichst schnell zu sein, weshalb als Mittelpunkt der Schnittpunkt der
//Diagonalen eines alle Kugeln enthaltenen Rechteckes genommen wird, und als
//Radius der kleinste Wert für den noch alle Kugel im Kreis sind.
//Das läuft in O(n), das schnellste absolut präzise Verfahren was ich gefunden
//habe (einen Kreis zu schrumpfen), bräuchte zum Vergleich O(n^2)
//(siehe die Statistik)
procedure TmainForm.updateCircel(const newSphere:TStaticSphere);
var i:integer; //Schleifenvariablen
    Sphere,endSphere:TStaticSphere;
    dotProduct:integer; //Temporäres Kreuzprodukt
    changed:boolean;//Wurde die Größe des Umgebungsrechteckes geändert?
begin
  //Neue Rechteckgrößen berechnen
  changed:=false;
  if newSphere.pos.x-newSphere.r<spMinX then begin
    spMinX:=newSphere.pos.x-newSphere.r;
    changed:=true;
  end;
  if newSphere.pos.x+newSphere.r>spMaxX then begin
    spMaxX:=newSphere.pos.x+newSphere.r;
    changed:=true;
  end;
  if newSphere.pos.y-newSphere.r<spMinY then begin
    spMinY:=newSphere.pos.y-newSphere.r;
    changed:=true;
  end;
  if newSphere.pos.y+newSphere.r>spMaxY then begin
    spMaxY:=newSphere.pos.y+newSphere.r;
    changed:=true;
  end;

  if changed then begin//Wenn das Rechteck verändert wurde
    //Neuen Mittelpunkt berechnen (O(1))
    CircelCenter.x:=(spMinX+spMaxX) div 2;
    CircelCenter.y:=(spMinY+spMaxY) div 2;

    //Neuen Radius berechnen (O(n))
    if useStandardRadius then begin
      //Alle Kugeln haben den gleichen Radius
      CircelRadius:=0;
      i:=0;
      while i<=Spheres.countStatic-1 do begin
        Sphere:=Spheres.getStaticSphere(i);
        if i+StaticSpheresInArray<Spheres.countStatic then
          endSphere:=Spheres.getStaticSphere(i+StaticSpheresInArray-1)
         else
          endSphere:=Spheres.getStaticSphere(Spheres.countStatic-1);
        i:=i+StaticSpheresInArray;
        while (cardinal(Sphere)<=cardinal(endSphere)) do begin
          //Schleife------
          dotProduct :=vecDotSelf(vecSub(Sphere.pos,CircelCenter));
          if dotProduct>CircelRadius then CircelRadius:= dotProduct;
          //------Schleife
          inc(Sphere);
        end;
      end;
      CircelRadius:=trunc(sqrt(circelRadius))+SphereRadius;
    end else begin
      //Kugeln lönnen einen unterschiedlichen Radius haben
      circelRadius:=0;
      i:=0;
      while i<=Spheres.countStatic-1 do begin
        Sphere:=Spheres.getStaticSphere(i);
        if i+StaticSpheresInArray<Spheres.countStatic then
          endSphere:=Spheres.getStaticSphere(i+StaticSpheresInArray-1)
         else
          endSphere:=Spheres.getStaticSphere(Spheres.countStatic-1);
        i:=i+StaticSpheresInArray;
        while (cardinal(Sphere)<=cardinal(endSphere)) do begin
          dotProduct :=trunc(vecLength(vecSub(Sphere.pos,CircelCenter)))+
                       Sphere.r;
          if dotProduct>CircelRadius then CircelRadius:= dotProduct;
          inc(Sphere);
        end;
      end;
    end;
  end else begin
    //Das Umgebungsrechteck wurde zwar nicht geändert, aber trotzdem muss getestet
    //werden, ob die Kugel auch noch in den Kreis passt
    dotProduct:=trunc(sqrt(vecDotSelf(vecSub(newSphere.pos,CircelCenter))))+newSphere.r;
    if dotProduct>CircelRadius then CircelRadius:=dotProduct;
  end;
end;


//Es wurde auf "Kugeln erstellen" geklickt
procedure TmainForm.createSpheresClick(Sender: TObject);
var i,aimcount,failed:integer; //Anzahl der {erstellten|zu erstellenden|mißlungenen} Kugeln
    minR,maxR:integer; //Minimaler/Maximaler Kugelradius
    xtemp,ytemp:integer; //Zufällige Kugelposition (kartesisch)
    winkel:single; //Zufällige Kugelposition (polar in Bogenmaß)
    cos,sin:extended; //Sinus und Cosinus der polaren zufälligen Kugelposition
    Sphere:TMovingSphere; //Erstellte Kugeln
    time:cardinal; //Gebrauchte Zeit
begin

  if sphereCount.Value > 100000 then //sehr viele Kugeln zu erstellen?
    if MessageBox(handle,pchar('Wollen Sie wirklich '+inttostr(sphereCount.Value)+
                                ' Kugeln erstellen? Das wird ziemlich lange dauern.'),
                  '3D-Kugelsimulation', mb_yesno)=id_no then exit;
  Randomize;
  if deleteOldSpheres.Checked then begin
    //Alle alten Kugeln löschen
    for i:=Spheres.countMoving-1 downto 0 do Spheres.deleteMovingSphere(i);
    for i:=Spheres.countstatic-1 downto 1 do Spheres.deleteStaticSphere(i);
    //Und Begrenzung zurücksetzen
    spMinX:=0;
    spMaxX:=0;
    spMinY:=0;
    spMaxY:=0;
    resetCircelRadius;
  end;
  IsThereExactCircel:=false;//Exakten Umkreis löschen
  aimcount:=sphereCount.Value; //Zu erstellende Kugelanzahl
  ProgressBar1.Max:=aimcount; //Fortschrittsbalken Maximum
  i:=Spheres.countStatic; //Bereits vorhandene Kugelanzahl
  failed:=0; //Mißlungene 
  time:=GetTickCount; //Zeit
  minR:=MinSphereRadius.Value; //Minimale Kugelanzahl
  maxR:=MaxSphereRadius.Value; //Maximale    "
  while i<aimcount do begin //solange nicht alle erstellt wurden
     //zufällige Polarposition, die Länge ist das Doppelte des Umkreisradius plus
     //den maximalen Radius (damit die Kugel möglichst viele Stellen erreichen kann)
     winkel:=rrandom(1,360000) *pi/180000; //1000 Schritte pro Grad
     //Ins kartesische umrechnen
     SinCos(winkel,sin,cos);
     xtemp:=trunc((2*CircelRadius+maxR)*cos+CircelCenter.x);
     ytemp:=trunc((2*CircelRadius+maxR) *sin+CircelCenter.y);
     //Kugeln erstellen
     if useStandardRadius then
       Sphere:=addSphere(xtemp,ytemp,SphereRadius,2*CircelRadius+maxR)
      else
       Sphere:=addSphere(xtemp,ytemp,rrandom(minR,maxR),2*CircelRadius+maxR);
     if Sphere.kollisionTime<1e34 then begin //kollidiert sie?
       //Zum Kollisionspunkt springen
       Sphere.pos:=Sphere.kollisionPos;
       //In eine feste umwandeln
       Spheres.convertMovingToStatic(Spheres.countMoving-1);
       //Umkreis aktualisieren
       updateCircel(Spheres.getStaticSphere(Spheres.countStatic-1));
       inc(i);
       if i and 1023 =0then //Fortschritt alle 1024 Kugeln anzeigen
         ProgressBar1.Position:=i;
     end else begin
       inc(failed);
       Spheres.deleteMovingSphere(Spheres.countMoving-1);
     end;
  end;
  ProgressBar1.Position:=aimcount;
  StatusBar1.Panels[0].Text:='Muster mit '+INtToStr(i)+' Kugel erzeugt. Benötigte Zeit: '+
                             IntToStr((gettickcount-time) div 1000)+ ' Sekunden. Nicht kollidierte Kugeln: '+Inttostr(failed);
  showAll.Click;
  if aimcount < 200 then //Bei wenigen Kugeln auch den exakten Umkreis berechnen.
    showExactCircel.click();
end;

//==============================================================================
//                           Zeichenfunktionen
//==============================================================================

//Kugeln zeichnen
procedure TmainForm.drawSpheres(const size,xmove,ymove:integer;const numbers:boolean;output:TBitmap);
  //Pfeil an Position (x,y) in Richtung dir mit Länge dirLength zeichnenzeichnen
  procedure drawDirArrow(x,y:integer;const dir:TVector2Float;const dirlength:single);
  var tempdir:TVector2Float;
      dx,dy:integer;
  begin
    output.Canvas.pen.Color:=colorManager.getColor(Color_2D_Arrow);
    //Rumpft des Pfeiles zeichnen
    output.Canvas.MoveTo(x,y);
    x:=x+trunc(dir.x*dirLength);
    y:=y+trunc(dir.y*dirLength);
    output.Canvas.LineTo(x,y);

    //Dreht das Innenstück um 45° nach außen, um die eine Hälfte der Pfeilspitze
    //zu erhalten
    tempdir:=vecRot(dir,135*pi/180);
    dx:=trunc(tempdir.x*dirLength) div 2;
    dy:=trunc(tempdir.y*dirLength) div 2;

    //Erste Hälfte zeichnen
    output.Canvas.MoveTo(x,y);
    output.Canvas.LineTo(x+dx,y+dy);
    //Zweite um 90° verdreht zeichnen
    output.Canvas.MoveTo(x,y);
    output.Canvas.LineTo(x-dy,y+dx);
  end;

var x,y,r:integer; //Temporäre Koordinaten und Radius
    i: integer;//Schleifenvariable
    SphereMove:TMovingSphere; //bewegende Kugel
    SphereStatic:TStaticSphere; //ruhende Kugel
    temp:single;
    kolPos:TVector2Int; //Kollisionsposition
    arrowLength:single; //Pfeillänge
begin
  //Anzeigen aktualisieren
  showedSpheres.Caption:='Angezeigte Kugeln: '+IntToStr(Spheres.countStatic)+ ' feste ';
  showedMoving.Caption:=IntToStr(Spheres.countMoving)+' bewegliche';;
  //Pfeillänge einstellen
  arrowLength:=max(10,70*size /100);
  with output do begin
    //Hintergrund
    canvas.brush.Color:=colorManager.getColor(Color_2D_Background);
    canvas.brush.Style:=bsSolid;
    Canvas.Rectangle(-10,-10,Width+10,Height+10);

    //Rand der ruhenden Kugeln einstellen
    canvas.pen.Color:=colorManager.getColor(Color_2D_Pen_Static);
    canvas.pen.style:=psSolid;
    if size>40 then canvas.pen.Width:=4
    else if size>10 then canvas.pen.Width:=2
    else if size>5 then canvas.pen.Width:=1
    else canvas.pen.style:=psClear;
    //Fläche der ruhenden Kugeln
    if size>5 then canvas.brush.Color:=colorManager.getColor(Color_2D_Brush_Static)
    else canvas.brush.Color:=colorManager.getColor(Color_2D_Pen_Static);
  end;

  //Bereits kollidierte zeichnen
  for i:=0 to Spheres.countStatic-1 do begin
    SphereStatic:=TStaticSphere(Spheres.getStaticSphere(i));
    with output.Canvas do begin
      //Zeichnen
      x:=SphereStatic.pos.x*size div 100+xmove;
      y:=SphereStatic.pos.y*size div 100+ymove;
      r:=max(1,SphereStatic.r*size div 100);
      Ellipse(x-r,y-r,x+r,y+r);
      //Durchnummerieren
      if numbers then TextOut(x-textwidth(inttostr(i)) div 2,
                              y-textheight(inttostr(i)) div 2,inttostr(i));
    end;
  end;

  //Bewegliche
  with output.canvas.pen do begin
    Width:=1;
    style:=psSolid;
  end;
  for i:=0 to Spheres.countMoving-1 do begin
    SphereMove:=TMovingSphere(Spheres.getMovingSphere(i));
    with output.Canvas do begin
      //Zeichnen
      //(wahrscheinliche) Position der Kugeln nach eine Kollision
      x:=SphereMove.kollisionPos.x*size div 100+xmove;
      y:=SphereMove.kollisionPos.y*size div 100+ymove;
      r:=max(1,SphereMove.r*size div 100);
      pen.Color:=colorManager.getColor(Color_2D_Pen_Aim);
      brush.Color:=colorManager.getColor(Color_2D_Brush_Aim);
      Ellipse(x-r,y-r,x+r,y+r);

      //Die Kugel selbst
      x:=trunc((SphereMove.pos.x+SphereMove.dir.x*SphereMove.currenttime))*size div 100+xmove;
      y:=trunc((SphereMove.pos.y+SphereMove.dir.y*SphereMove.currenttime))*size div 100+ymove;
      pen.Color:=colorManager.getColor(Color_2D_Pen_Moving);
      brush.Color:=colorManager.getColor(Color_2D_Brush_Moving);
      Ellipse(x-r,y-r,x+r,y+r);

      //Ein Pfeil um die Richtung anzuzeigen
      drawDirArrow(x,y,SphereMove.dir,max(arrowLength,SphereMove.r*size div 100));

      //Durchnummerieren
      if numbers then TextOut(x-textwidth(inttostr(i)) div 2,
                              y-textheight(inttostr(i)) div 2,inttostr(i));

    end;
  end;

  with output.canvas do begin
    if creatingNewSphere then begin //Wird gerade eine neue Kugel erzeugt
      //Kugel zeichnen
      brush.Color:=colorManager.getColor(Color_2D_Brush_Creation);
      pen.Color:=colorManager.getColor(Color_2D_Pen_Creation);
      r:=max(1,newSphereR*size div 100);
      Ellipse(newSphereX-r,newSphereY-r,newSphereX+r,newSphereY+r);

      //Zielrichtung zeigen
      drawDirArrow(newSphereX,newSphereY,newSphereDir,max(arrowLength,newSphereR*size div 100));

      //Kollision berechnen
      calculateKollision(newSphereDir,newSpherePos,newSphereR,temp,kolPos);
      if temp<1e30 then begin
        //Gibt es eine?
        //Position nach der Kollision zeichnen
        pen.Color:=colorManager.getColor(Color_2D_Pen_Aim);
        brush.Color:=colorManager.getColor(Color_2D_Brush_Aim);
        x:=kolPos.x*size div 100+xmove;
        y:=kolPos.y*size div 100+ymove;
        Ellipse(x-r,y-r,x+r,y+r);
      end;
    end;

    //Umkreis zeichnen
    brush.Style:=bsClear;
    pen.Width:=3;
    pen.Color:=colorManager.getColor(Color_2D_Circel);
    x:=trunc((CircelCenter.x*size) /100)+xmove;
    y:=trunc((CircelCenter.y*size) /100)+ymove;
    r:=max(2,(CircelRadius*size) div 100);
    Ellipse(x-r,y-r,x+r,y+r);

    //Exakter Umkreis berechnet?
    if IsThereExactCircel then begin
      //auch zeichnen
      x:=trunc((ExactCircelCenter.x*size) /100)+xmove;
      y:=trunc((ExactCircelCenter.y*size) /100)+ymove;
        //(Plus1 um die Stiftbfreite auszugleichen)
      r:=max(1,(ExactCircelRadius*size) div 100)+1;
      Ellipse(x-r,y-r,x+r,y+r);
    end;

    //Pfeilkreuz zeichnen, wenn gerade mit der Maus gescrollt wird
    if mouseMoving then Draw(PaintBox1.Width div 2-7,PaintBox1.Height div 2-7, SpeedButton5.Glyph);
  end;
end;

//Standardmäßiges Zeichnen
procedure TmainForm.drawSpheresDefault;
begin
  //In den Doublebuffer zeichnen
  drawSpheres(sizeFactor,xmove,ymove,numberThem.Checked,doubleBuffer);
  //Auf dem Bildschirm ausgeben
  PaintBox1.Canvas.Draw(0,0,doubleBuffer);
end;


//==============================================================================
//            Wichtige Methoden zum Abspeichern
//==============================================================================
//Abspeichern
procedure TmainForm.saveClick(Sender: TObject);
var save:TBitmap; //Zielbild
    memory:int64; //Speicher
begin
  //Benötigter Speicherplatz (width*height)
  memory:=sqr(2*CircelRadius * saveSize.Value div 100);
  //Arbeitsspeicher
  if getfreeMem<memory then begin
    ShowMessage('Leider ist nicht genug Arbeitsspeicher für die angeforderte Aktion verfügbar. Bitte verringern Sie die Größe.');
    exit;
  end;
  //Festplatte
  if getFreeDiskSpace(fileName.text)<memory then begin
    ShowMessage('Leider ist nicht genug Festplattenspeicher für die angeforderte Aktion verfügbar. Bitte verringern sie die Größe, wählen Sie einen anderen Speicherort, oder löschen sie einige Dateien.');
    exit;
  end;

  save:=TBitmap.create;
  save.PixelFormat:=pf8bit; //Graustufen
  save.Width:=CircelRadius*2 * saveSize.Value div 100;
  save.Height:=save.Width;
  try
    //Kugeln in das Bitmap zeichnen
    drawSpheres(saveSize.Value,
                (CircelRadius -CircelCenter.x )*saveSize.Value div 100,(
                CircelRadius-CircelCenter.y)*saveSize.Value div 100,false,save);
    //speichern
    save.SaveToFile(fileName.text);
  finally
    save.free;
  end;
end;

//Dateinamen auswählen
procedure TmainForm.selectFileNameClick(Sender: TObject);
begin
  if SaveDialog1.Execute then
    fileName.Text:=SaveDialog1.FileName;
end;

//==============================================================================
//            Wichtige Methoden für die manuelle Kugelerzeugung
//==============================================================================
//Fügt eine Kugel mit Radius r an der Stelle (x,y) ein, die sich in Richtung dir
//bewegt
function TmainForm.addUserSphere(const x,y:integer;const dir:TVector2Float;const r:byte):TMovingSphere;
var Sphere:TMovingSphere; //Neue Kugel
begin
  if vecDotSelf(dir)>0 then begin //Richtung angegeben?
    Sphere:=Spheres.createMovingSphere; //Speicher holen
    //Einstellungen setzen
    Sphere.pos.x:=x;
    Sphere.pos.y:=y;
    Sphere.dir:=dir;
    Sphere.r:=r;
    Sphere.currentTime:=0;
    //Kollision berechnen
    calculateKollision(Sphere.dir, Sphere.pos,Sphere.r,Sphere.kollisionTime,Sphere.kollisionPos);
    drawSpheresDefault; //Kugeln aktualisieren
    result:=Sphere;
  end else result:=addSphere(x,y,r); //keine Richtung => Standardfunktion
end;

//Es wurde auf die Paintbox geklickt
procedure TmainForm.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  IsThereExactCircel:=false;//Exakten Umkreis löschen
  if (button =mbLeft) then begin
    //Bei Klick mit links
    if not creatingNewSphere then //und wenn momentan keine Kugel erstellt wird
      if useStandardRadius then //Erzeuge eine an der aktuellen Stelle mit zufälliger Richtung
        addSphere((x-xmove)*100 div sizeFactor,(y-ymove)*100 div sizeFactor,SphereRadius)
       else
        addSphere((x-xmove)*100 div sizeFactor,(y-ymove)*100 div sizeFactor,random(50)+10);
    creatingNewSphere:=false; //auf false setzen, damit das Erstellen einer Kugel
    drawSpheresDefault;       //abgebrochen wird.
  end else if button=mbRight then begin
    //Bei Klick mit rechts
    creatingNewSphere:=true; //Kugel erzeugung starten
    newSphereX:=x; //Kugelposition merken
    newSphereY:=y;
    newSphereR:=SphereRadius;
    //Kugelposition in virtuelle Koordinaten umrechnen
    newSpherePos.x:=(newSphereX-xmove)*100div sizeFactor;
    newSpherePos.y:=(newSphereY-ymove)*100div sizeFactor;
    drawSpheresDefault; //Kugeln zeichnen
  end;
end;
//Die Maus wurde auf der Paintbox bewegt
procedure TmainForm.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if creatingNewSphere then begin //wird gerade eine neue Kugel erzeugt
    //Richtung ist der normaliesiere Vektor von der Kugel zur Maus
    newSphereDir:=vecNorm(makeVector(x-newSphereX,y-newSphereY));
    if (ssShift in shift) then //Soll der Radius eingestellt werden?
      newSphereR:=min(250,max(5,trunc(sqrt(sqr(x-newSphereX)+sqr(y-newSphereY)))*100div sizeFactor));
      //Radius := Länge des (Pfeile von der Kugel zur Maus)
    drawSpheresDefault; //Kugeln zeichnen
  end;
end;
//Maustaste losgelassen
procedure TmainForm.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  //Wird eine Kugel erzeugt und die linke Maustaste ist nicht gedrückt,
  //Kugel einfügen
  if creatingNewSphere  then
    addUserSphere(newSpherePos.x,newSpherePos.y,newSphereDir,newSphereR);
  creatingNewSphere:=false;
  drawSpheresDefault;
end;

//Kugelposition aktualisieren
procedure TmainForm.updateSpheres;
var i,j:integer; //Schleifenvariablen
    Sphere,Sphere2:TMovingSphere;
    x,y:integer; //Temporärer Koordinaten
    window:TRect; //Sichtbarer Ausschnitt der Ebene
    kollision:boolean; //Gab es ein Kollision
begin
  //Welcher Teil der Ebene kann gesehen werden
  window.Left:=-xmove*100 div sizeFactor;
  window.top:=-ymove*100 div sizeFactor;
  window.right:=(-xmove+doubleBuffer.width)*100 div sizeFactor;
  window.bottom:=(-ymove+doubleBuffer.Height)*100 div sizeFactor;

  //Alle Kugeln durchlaufen
  kollision:=false;
  for i:=Spheres.countMoving-1 downto 0 do begin
    //Berechnen
    Sphere:=Spheres.getMovingSphere(i);
    //Sphere bewegen
    Sphere.currenttime:=Sphere.currenttime+SphereSpeed.Value;
    if (Sphere.currenttime>Sphere.kollisionTime)or //Über dem Kollisionspunkt hinaus
        //oder unendliche Geschwindigkeit eingestellt?
        (KugelTeleportation.Checked and (Sphere.currenttime>3*SphereSpeed.Value)and
        (Sphere.kollisionTime<1e34)) then
      Sphere.currenttime:=Sphere.kollisionTime; //Sphere zum Kollisionspunkt bringen
    //Position berechnen
    Sphere.currentPos.x:=Sphere.pos.x+trunc(Sphere.dir.x*Sphere.currenttime);
    Sphere.currentPos.y:=Sphere.pos.y+trunc(Sphere.dir.y*Sphere.currenttime);

    //Kugel kollidiert
    if Sphere.currenttime=Sphere.kollisionTime then begin
      //Als unbeweglich markiern
      Sphere.pos:=Sphere.currentPos;
      Spheres.convertMovingToStatic(i);
      //Umkreis aktualisieren
      updateCircel(Spheres.getStaticSphere(Spheres.countStatic-1));
      //Es gab eine Kollision
      kollision:=true;
      continue;
    end;

    if (Sphere.kollisionTime>1e34) then begin//Wenn die Kugel nie kollidiert
      x:=trunc((Sphere.pos.x+Sphere.dir.x*Sphere.currenttime));
      y:=trunc((Sphere.pos.y+Sphere.dir.y*Sphere.currenttime));
      if ((x<window.Left-Sphere.r) or (x>window.right+Sphere.r)or //und unsichtbar ist
       (y<window.top-Sphere.r) or (y>window.bottom+Sphere.r)) then begin
         Spheres.deleteMovingSphere(i); //löschen
         continue;
      end;
    end;
  end;

  //Kollisionserkennung zwischen den beweglichen Kugeln O(n^2), aber es gibt ja nie viele
  //Alle Kugeln durchlaufen
  for i:=Spheres.countMoving-1 downto 0 do begin
    j:=Spheres.countMoving-1;
    while (j>i) do begin
      //Je zwei Kugeln wählen
      Sphere:=Spheres.getMovingSphere(i);
      Sphere2:=Spheres.getMovingSphere(j);
      //Gibt es eine Kollision
      if vecDotSelf(vecSub(Sphere.currentPos,Sphere2.currentPos))<=
        4*SphereRadius*SphereRadius then begin
          Sphere.pos:=Sphere.currentPos; //Kugelposition speichern
          Sphere2.pos:=Sphere2.currentPos;//"               "
          Spheres.convertMovingToStatic(j);//Kugeln fest verankern
          Spheres.convertMovingToStatic(i);//"
          //Umkreis aktualisieren
          updateCircel(Spheres.getStaticSphere(Spheres.countStatic-1));
          updateCircel(Spheres.getStaticSphere(Spheres.countStatic-2));
          kollision:=true;
          break;
      end;
      dec(j);
    end;
  end;

  //Bei einer Kollision einer beweglichen Kugel, alle Kollisionszeitpunkte
  //mit festen Kugeln neu berechnen, da vorher bewegliche zu festen geworden sind
  //und damit oben bei einer Kollision mit einer Dritten nicht berücktsichtigt
  //wurden
  if kollision then
    for i:=Spheres.countMoving-1 downto 0 do begin
      Sphere:=Spheres.getMovingSphere(i);
      calculateKollision(Sphere.dir, Sphere.pos,Sphere.r,Sphere.kollisionTime,Sphere.kollisionPos)
    end;

  //Kugeln zeichnen
  drawSpheresDefault;
end;

//Ruft alle 150 ms updateSpheres, wenn bewegliche Kugeln vorhanden sind.
procedure TmainForm.Timer1Timer(Sender: TObject);
begin
  if (Spheres.countMoving>0) then updateSpheres;
end;

//==============================================================================
//                  Wichtige Methoden für die Ansichtswahl
//==============================================================================

//Auf den Mittelpunkt zentrieren
procedure TmainForm.CenterOnCenterClick(Sender: TObject);
begin
  //xmove und ymove so wählen, dass der Umkreismittelpunkt, genau in der Mitte
  //der Paintbox ist
  xmove:=PaintBox1.Width div 2-trunc(CircelCenter.x*(sizeFactor /100));
  ymove:=PaintBox1.height div 2-trunc(CircelCenter.y*(sizeFactor /100));
  drawSpheresDefault; //Kugenl zeichnen
end;

//Alles zeigen
procedure TmainForm.showAllClick(Sender: TObject);
begin
  //Vergrößerungsfaktor so wählen, dass der Umkreisradius genau horizontal/vertikal
  //in die Paintbox passt
  size.Value:=max(1,min(PaintBox1.Width,PaintBox1.Height)*50 div CircelRadius);
  CenterOnCenter.Click; //auf den Mittelpunkt zentrieren
end;

//Ein Richtungspfeil wurde eingedrückt
procedure TmainForm.SpeedButtonMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  //Merken auf welchen geklickt wurde
  selectedMoveButton:=sender as tspeedbutton;
  //Und den Scrolltimer reaktivieren
  timer2.Enabled:=true;
  Timer2Timer(timer2);
end;

//Eine Richtungspfeil wurde los gelassen
procedure TmainForm.SpeedButtonMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  //Scrolltimer abschalten
  timer2.Enabled:=false;
end;

//Es wurde auf die Kreuzpfeile geklickt, so dass die Kugeln der Maus solange
//folgen sollen, bis sie losgelassen wird.
procedure TmainForm.SpeedButton5MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var rect:trect; //Mausbereich
begin
  //Maus auf die Ausgabefläche begrenzen
  rect.TopLeft:=ClientToScreen(point(PaintBox1.left,PaintBox1.top));
  rect.BottomRight:=ClientToScreen(point(PaintBox1.left+PaintBox1.width,PaintBox1.top+PaintBox1.Height));
  ClipCursor(@rect);

  mouseMoving:=true; //Merken, dass die Kugeln der Maus folgen sollen
  screen.Cursor:=5; //Kreuzcursor
  //Startposition des Cursors
  amsx:=(rect.Left+rect.Right)div 2;
  amsy:=(rect.Top+rect.Bottom)div 2;
  SetCursorPos(amsX,amsY); //Mauszeiger hin versetzen
  Timer2.Enabled:=true; //Scrolltimer an
end;

//Taste wurde losgelassen, Scrollen stopp
procedure TmainForm.SpeedButton5MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  ClipCursor(nil); //Mausbegrenzung aufheben
  Timer2.Enabled:=false; //Scrolltimer aus
  mouseMoving:=false; //Maus verfolgung stoppen
  screen.Cursor:=crDefault; //Normaler Cursor
  drawSpheresDefault; //Kugeln aktualisieren
end;

//Scrolltimer
procedure TmainForm.Timer2Timer(Sender: TObject);
var inv:integer; //Bewegungsfaktor (negativ/positiv)
    cursor:TPoint; //Cursorposition
    dx,dy:integer; //Verschiebungsfaktor
    dirX,dirY:integer; //Richtung
begin
  //Wenn die Bewegungsrichtung invertiert, Vorzeichen vertauschen
  if invertedScroll.Checked then inv:=1
  else inv:=-1;
  if mouseMoving then begin //Kugeln folgen der Maus (bzw. fliehen)
    GetCursorPos(cursor); //Cursorposition
    //Die Geschwindigkeit in jede Richtung ist die Entfernung vom Startpunkt
    //dividiert durch 5
    dx:=(amsX-cursor.x) div 5;
    dy:=(amsY-cursor.y) div 5;
    //Feststellen in welche Richtung es sich bewegt
    if (dx>1) then dirX:=0
    else if (dx<-1) then dirX:=2
    else dirX:=1;
    if (dy>1) then dirY:=0
    else if (dy<-1) then dirY:=2
    else dirY:=1;
    //den entsprechenden Cursor aussuchen, so dass man die Bewegungsrichtung
    //erkennt
    screen.Cursor:=dirY*3+dirX+1;
  end else begin
    //Ist der Button links von der Mitte, nach links bewegen, sonst nach rechts
    if selectedMoveButton.Left<SpeedButton5.Left then dx:=-20*inv
    else if selectedMoveButton.Left>SpeedButton5.Left then dx:=20*inv
    else dx:=0;
    //Ist der Button über der Mitte, nach oben bewegen, sonst nach unten
    if selectedMoveButton.top<SpeedButton5.top then dy:=-20*inv
    else if selectedMoveButton.top>SpeedButton5.top then dy:=20*inv
    else dy:=0;
  end;
  //Bewegen
  xmove:=xmove+dx*inv;
  ymove:=ymove+dy*inv;
  //Neuzeichnen
  drawSpheresDefault;
end;

//Größenfaktor geändert
procedure TmainForm.sizeChange(Sender: TObject);
begin
  try
    sizeFactor:=size.value; //Faktor auslesen
    drawSpheresDefault; //aktualisieren
  except
  end;
end;

//==============================================================================
//                           Sonstige Methoden
//==============================================================================

//Umkreis zurücksetzen
procedure TmainForm.resetCircelRadius;
begin
  CircelCenter.x:=0;
  CircelCenter.y:=0;
  CircelRadius:=Spheres.getStaticSphere(0).r;
end;
//Initialisierung
procedure TmainForm.FormCreate(Sender: TObject);
var i:integer;
begin
  useStandardRadius:=true; //Alle Kugeln haben den selben Radius
  useCircels:=true; //Kugeln werden als Kreise benutzt
  IsThereExactCircel:=false; //exakter Umkreis nicht berechnet
  mouseMoving:=false; //Im Moment wird nicht gescrollt
  newSphereR:=SphereRadius; //Standardradius

  //Begrenzungsrechteck
  spMinX:=0;
  spMaxX:=0;
  spMinY:=0;
  spMaxY:=0;

  //Kugelliste erzeugen
  Spheres:=TSphereList.Create;
  with Spheres.createStaticSphere^ do begin
    pos.x:=0;
    pos.y:=0;
    r:=SphereRadius;
  end;
  resetCircelRadius; //Umkreis zurücksetzen

  //Doublebuffer ezeugen
  doubleBuffer:=TBitmap.Create;
  doubleBuffer.Width:=PaintBox1.Width;
  doubleBuffer.Height:=PaintBox1.Height;

  //Verschiebung
  xmove:=doubleBuffer.Width div 2;
  ymove:=doubleBuffer.Height div 2;
  //Größe
  sizeFactor:=100;

  //Cursor laden
  for i:=1 to 9 do
    screen.Cursors[i]:=LoadCursor(HINSTANCE, MAKEINTRESOURCE(i));
end;

//Freigabe
procedure TmainForm.FormDestroy(Sender: TObject);
begin
  Spheres.free; //Kugeln
  doubleBuffer.free; //Doublebuffer
end;


//Kugeln müssen neu gezeichnet werden
procedure TmainForm.PaintBox1Paint(Sender: TObject);
begin
  drawSpheresDefault;
end;

//Bei Vergrößerung/Kleiner den Doublebuffer neu berechnen
procedure TmainForm.FormResize(Sender: TObject);
begin
  doubleBuffer.Width:=PaintBox1.Width;
  doubleBuffer.Height:=PaintBox1.Height;
end;


//3D-Modus öffnen
procedure TmainForm.open3DModeClick(Sender: TObject);
begin
  if not threeDForm.Visible then
    threeDForm.show;
end;

//Statistik öffnen
procedure TmainForm.statsClick(Sender: TObject);
begin
  statsForm.showmodal;
end;

//Farbwahl öffnen
procedure TmainForm.colorOptionsClick(Sender: TObject);
begin
  colorManager.ShowModal;
end;

//Einstellen, ob es Kugeln mit verschiedenen Radien gibt
procedure TmainForm.differentRadiusesClick(Sender: TObject);
begin
  useStandardRadius:=not differentRadiuses.Checked;
  SpheresAsCircels.Enabled:=differentRadiuses.Checked;
end;
//Einstellen, ob Kugeln als Kreise behandelt werden sollen
procedure TmainForm.SpheresAsCircelsClick(Sender: TObject);
begin
  useCircels:=SpheresAsCircels.Checked;
end;

//Beenden
procedure TmainForm.closeClick(Sender: TObject);
begin
  tform(self).close;
end;

//Beim ersten Öffnen Hilde anzeigen
procedure TmainForm.FormShow(Sender: TObject);
begin
  help.ShowModal;
end;

//Kugeln aktualisieren, wenn die Ansicht gewechselt wird
procedure TmainForm.numberThemClick(Sender: TObject);
begin
  drawSpheresDefault;
end;

//Berechnen den optimalen Umkreis
procedure TmainForm.showExactCircelClick(Sender: TObject);
begin
  if IsThereExactCircel then begin //nicht mehrmals berechnen
    ShowMessage('Der exakte Umkreis ist bereits berechnet worden');
    exit;
  end;
  //Warteschrift ausgeben
  with PaintBox1.Canvas do begin
    font.Size:=72;
    TextOut((paintBox1.width - TextWidth('Bitte warten ... ')) div 2,
            (paintBox1.height - TextHeight('Bitte warten ... ')) div 2,
            'Bitte warten...');
  end;
  //findOptimalCircel aus der Statistikunit aufrufen
  statistik_u.stopCircelSearch:=false;
  statistik_u.findOptimalCircel(exactcircelCenter,exactCircelRadius);
  IsThereExactCircel:=true; //speichern, dass der exakte Umkreis berechnet wurde
  drawSpheresDefault;
end;

//NonClient Hilfebutton------------
procedure TmainForm.WM_NCHITTEST(var mes:TWMNCHITTEST);
var t:integer;
begin
  inherited;
  t:=getButtonAt(self,mes.XPos,mes.YPos);
  if t<>0 then
    mes.Result:=t;
end;
procedure TmainForm.WM_NCLBUTTONDOWN(var mes:TWMNCLBUTTONDOWN);
begin
  inherited;
  if (mes.HitTest=htHelp)or(mes.HitTest=htTip) then begin
    pressedButton:=mes.HitTest;
    drawButton(self,pressedButton);
  end;
end;
procedure TmainForm.WM_NCLBUTTONUP(var mes:TWMNCLBUTTONUP);
begin
  inherited;
  if (pressedButton<>0) then begin
    clickedAt(self,mes.XCursor,mes.YCursor);
    pressedButton:=0;
    drawButton(self,pressedButton);
  end;
end;
procedure TmainForm.WM_NCPAINT(var mes:TWMNCPAINT);
begin
  inherited;
  drawButton(self,pressedButton);
end;
procedure TmainForm.WM_NCACTIVATE(var mes:TWMNCPAINT);
begin
  inherited;
  drawButton(self,pressedButton);
end;
//-------------NonClient Hilfebutton

end.
