unit statistik_u;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, ComCtrls, StdCtrls,vectors,Spheres,math,extralistview,helpWindow;

type
  //Funktion, die einen Wert berechnet
  TValueFunc=function(out calulating:boolean):string;
  //Ein angezeigter Wert
  TValuePair=record
    value:string; //Der eigentliche Wert
    calculating:boolean; //Wird der Wert gerade berechnet?
    getValue:TValueFunc; //Funktion zur Berechnung
    description:string; //Beschreibung
  end;
  TstatsForm = class(TForm)
    HeaderControl1: THeaderControl; //Spaltenanzeige
    ListBox1: TListBox; //Informationsausgabe
    Button1: TButton; //Beendenbutton
    Timer1: TTimer; //Timer der den Wert "wird berechnet" animiert
    Panel2: TPanel; Panel1: TPanel; //Designelemente
    
    procedure FormCreate(Sender: TObject); //Initalisierung
    procedure FormDestroy(Sender: TObject); //Freigabe
    procedure FormShow(Sender: TObject); //Statistik aktualisieren
    procedure DrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState); //Werte ausgeben
    //Werte, die gerade berechnet werden aktualisieren
    procedure Timer1Timer(Sender: TObject);
    //Berechnung stoppen
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private-Deklarationen}
    //Funktionen für den NonClient Hilfebutton (WindowProc zu überschreiben
    //wäre sinnvoller gewesen, aber ich habe nicht genug Zeit dass noch zu
    //ändern)
    pressedButton:integer;
    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;
  public
    { Public-Deklarationen}
    //Klasse die aus der ListBox eine Art TCustomListView mit variabler Höhe
    //macht.
    extraListView:TExtraListView;
    procedure writeStatistik; //Statistik schreiben
    procedure finishStatistik; //Statistik aktualisieren
  end;

var
  statsForm: TstatsForm;


var thread: thandle; //Der Thread in dem findOptimalCircel läuft
    circelSearchingState:(cssWaiting,cssStarted,cssStopped); //Status der Suche
    stopCircelSearch:boolean; //Wird auf true gesetzt, um die Suche abzubrechen
//Sucht einen möglichst kleinen Kreis der alle Kugeln enthält.
//Dazu werden drei auf dem Kreisrand liegende Punkte gesucht
procedure findOptimalCircel(out middle:TVector2Int;out radius:integer);
//Ruft findOptimalCircel in einem eigenen Thread auf
procedure startCircelSearch;
implementation

uses a1_u;

//Berechnet die Werte für die Statistik
//Geschätzer Umkreisradius
function getEstimatedRadius(out calulating:boolean):string;forward;
//Geschätzer Umkreismittelpunkt
function getEstimatedCenter(out calulating:boolean):string;forward;
//Geschätze Umkreisfläche
//var estimatedCircelArea: int64;
function getEstimatedCircelArea(out calulating:boolean):string;forward;

var exactRadius: integer; //Exakter Umkreisradius
    exactCenter: TVector2Int;//Exakter Umkreismittelpunkt
    exactArea: int64; //Exakte Umkreisfläche

//Exakter Umkreisradius
function getExactRadius(out calulating:boolean):string; forward;
//Exakter Umkreismittelpunkt
function getExactCenter(out calulating:boolean):string;forward;
//Exakte Umkreisfläche
function getExactCircelArea(out calulating:boolean):string;forward;

//Unterschiede zwischen den Radien
function getRadiusDifferenc(out calulating:boolean):string;forward;
//Entfernung der Mittelpunkte voneinander
function getCenterDistance(out calulating:boolean):string;forward;
//Unterschiede zwischen den Flächen
function getCircelAreaDifferenc(out calulating:boolean):string;forward;

//Anzahl der kollidierten Kugeln
function getStaticSpheres(out calulating:boolean):string;forward;
//Anzahl der bewegenden Kugeln
function getMovingSpheres (out calulating:boolean):string;forward;

//Gesamte, durch Kugeln ausgefüllte Fläche, wenn man sie auf eine Ebene projeziert.
var SphereArea:int64;
function getSphereArea (out calulating:boolean):string;forward;

//Unterschiede zwischen der durch Kugeln belegten Fläche und der exakten
//Umkreisfläche
function getSphereExactCircelAreaDifferenc (out calulating:boolean):string;forward;
//Unterschiede zwischen der durch Kugeln belegten Fläche und der geschätzten
//Umkreisfläche
function getSphereEstimatedCircelAreaDifferenc (out calulating:boolean):string;forward;

//Durchschnittliche Kugelentfernung vom geschätzen Umkreismittelpunkt
function getEstimatedAverageDistance (out calulating:boolean):string;forward;
//Durchschnittliche Kugelentfernung vom exakten Umkreismittelpunkt
function getExactAverageDistance (out calulating:boolean):string;forward;



var
  //String der für Werte ausgegeben wird, die gerade berechnet werden.
  //Es kommt immer ein Buchstabe hinzu, und wenn er ' ... wird berechnet ...'
  //ist, wird er wieder zurück gesetzt.
  isCalculated:string=' ... wird be';
  CountOfNowCalculated:integer; //Anzahl der momentan berechneten Werte




var
  //Die Namen der Werte in der Listbox
  valueText:array[0..28] of TValueInfo=(
   (typ:vtCaptionH1;name:'Statistik'),
   (typ:vtCaptionH2;name:'Umkreis'),
   (typ:vtCaptionH3;name:'Geschätzer Umkreis'),
   (typ:vtValue;name:'Radius'),
   (typ:vtValue;name:'Mittelpunkt'),
   (typ:vtValue;name:'Fläche'),
   (typ:vtCaptionH3),
   (typ:vtCaptionH3;name:'Exakter Umkreis'),
   (typ:vtValue;name:'Radius'),
   (typ:vtValue;name:'Mittelpunkt'),
   (typ:vtValue;name:'Fläche'),
   (typ:vtCaptionH3),
   (typ:vtCaptionH3;name:'Unterschiede zwischen diesen Umkreisen'),
   (typ:vtValue;name:'Radius'),
   (typ:vtValue;name:'Mittelpunkt'),
   (typ:vtValue;name:'Fläche'),
   (typ:vtCaptionH2),
   (typ:vtCaptionH2;name:'Kugeln'),
   (typ:vtValue;name:'Feste Kugeln'),
   (typ:vtValue;name:'Bewegliche Kugeln'),
   (typ:vtValue;name:'Belegte Fläche'),
   (typ:vtCaptionH3),
   (typ:vtCaptionH3;name:'Unterschiede zwischen der Umkreisfläche und der tatsächlich benötigten'),
   (typ:vtValue;name:'Zum geschätzen'),
   (typ:vtValue;name:'Zum exakten'),
   (typ:vtCaptionH3),
   (typ:vtCaptionH3;name:'Durchschnittliche Entfernung von dem Umkreismittelpunkten'),
   (typ:vtValue;name:'Zum geschätzen'),
   (typ:vtValue;name:'Zum exakten')
  );
  //Die Funktionen zur Wertberechnung und Beschreibung der einzelnen Zeilen der ListBox
  values:array[0..28] of TValuePair=(
   (),
   (),
   (),
   (getValue:getEstimatedRadius;description:'Der geschätzte Radius des kleinsten Kreises, der alle Kugeln enthält'),
   (getValue:getEstimatedCenter;description:'Der geschätzte Mittelpunkt des kleinsten Kreises, der alle Kugeln enthält'),
   (getValue:getEstimatedCircelArea;description:'Die geschätzte Fläche (pi * r²) des kleinsten Kreises, Kreises, der alle Kugeln enthält '),
   (),
   (),
   (getValue:getExactRadius;description:'Der exakte Radius des kleinsten Kreises, der alle Kugeln enthält'),
   (getValue:getExactCenter;description:'Der exakte Mittelpunkt des kleinsten Kreises, der alle Kugeln enthält'),
   (getValue:getExactCircelArea;description:'Die exakte Fläche (pi * r²) des kleinsten Kreises, der alle Kugeln enthält '),
   (),
   (),
   (getValue:getRadiusDifferenc;description:'Die Differenz zwischen dem exakten und geschätzen Radius'),
   (getValue:getCenterDistance;description:'Die Entfernung vom geschätzen Mittelpunkt zum exakten Mittelpunkt'),
   (getValue:getCircelAreaDifferenc;description:'Die Differenz zwischen der exakten und geschätzen Fläche'),
   (),
   (),
   (getValue:getStaticSpheres; description:'Die Anzahl der bereits kollidierten vorhandenen Kugeln'),
   (getValue:getMovingSpheres;description:'Die Anzahl der noch nicht kollidierten Kugeln'),
   (getValue:getSphereArea;description:'Die gesamte Fläche die von den Kugeln belegt wird, wenn man sie auf die Ebene projeziert'),
   (),
   (),
   (getValue:getSphereExactCircelAreaDifferenc;description:'Die Differenz zwischen der von den Kugeln benötigten Fläche und der vom geschätzen Umkreis'),
   (getValue:getSphereEstimatedCircelAreaDifferenc;description:'Die Differenz zwischen der von den Kugeln benötigten Fläche und der vom exakten Umkreis'),
   (),
   (),
   (getValue:getEstimatedAverageDistance;description:'Die durchschnittliche Entfernung der Kugelmittelpunkte zum geschätzen Umkreismittelpunkt'),
   (getValue:getExactAverageDistance;description:'Die durchschnittliche Entfernung der Kugelmittelpunkte zum exakten Umkreismittelpunkt')
   );
{$R *.DFM}

//NonClient Hilfebutton------------ (siehe a1_u.pas)
procedure TstatsForm.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 TstatsForm.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 TstatsForm.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 TstatsForm.WM_NCPAINT(var mes:TWMNCPAINT);
begin
  inherited;
  drawButton(self,pressedButton);
end;
procedure TstatsForm.WM_NCACTIVATE(var mes:TWMNCPAINT);
begin
  inherited;
  drawButton(self,pressedButton);
end;
//-------------NonClient Hilfebutton

//Signumfunktion
function sign(x:extended):extended;
begin
  if x<0 then result:=-1
  else if x>0 then result:=1
  else result:=0;
end;


//Geht die ausgegebene Statistik durch, und wenn irgendwo ein Wert gerade
//berechnet wird, wird die Funktion nochmals aufgerufen.
procedure TstatsForm.finishStatistik;
var i:integer;
begin
  for i:=0 to high(values) do
    with values[i], valueText[i] do
      if (calculating) and (typ=vtValue) and (assigned(getValue)) then begin
        //Wert wird berechnet
        value:=getValue(calculating); //Berechnungsfunktion aufrufen
        if calculating then value:=isCalculated //wird er immer noch berechnet?
        else dec(CountOfNowCalculated); //wenn nicht Anzahl verringern
        extraListView.drawItem(i); //Zeile aktualisieren
      end;
end;

//Schreibt die Statistik in die ListBox
procedure TstatsForm.writeStatistik;
var i:integer;
begin
  ListBox1.Clear; //Alte löschen
  for i:=0 to high(values) do
    with values[i], valueText[i] do begin
      listbox1.Items.Add(text); //Name einfügen
      if (typ=vtValue) and (assigned(getValue)) then begin
        //Soll in dieser Zeile ein Wert berechnet werdenß
        value:=getValue(calculating); //Wert berechnen
        if calculating then begin //Wird er im moment berechnet?
          value:=' ... wird berechnet ...';
          inc(CountOfNowCalculated);
        end;
      end;
    end;
end;
procedure TstatsForm.FormCreate(Sender: TObject);
begin
  //Größe der Spalte setzen
  HeaderControl1.Sections[2].Width:=HeaderControl1.ClientWidth-
             HeaderControl1.Sections[0].width-HeaderControl1.Sections[1].width;
end;

//zweite und dritte Zeile zeichen
procedure TstatsForm.DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
var tr:trect;
begin
  //Y-Koordinaten
  tr.top:=Rect.top;
  tr.bottom:=Rect.bottom;

  //Spaltenkoordinaten als X-Koordinaten nehmen
  tr.Left:=HeaderControl1.Sections[0].Right;
  tr.Right:=HeaderControl1.Sections[1].Right;
  //Wert zeichnen
  ListBox1.canvas.TextRect(tr,tr.left+2,tr.top+1,values[index].value);

  //Beschreibung zeichnen
  tr.Left:=HeaderControl1.Sections[1].Right;
  tr.Right:=HeaderControl1.Sections[2].Right;
  ListBox1.canvas.TextRect(tr,tr.left+2,tr.top+1,values[index].description);
end;

//Statistik schreiben
procedure TstatsForm.FormShow(Sender: TObject);
begin
  if extraListView=nil then begin
    //Wurde noch kein ExtraListView erzeugt (das macht aus der ListBox
    //eine Art CustomListView mit variabler Zeilenhöhe)?

    //Dann erzeuge jetzt eines (siehe extralistview.pas):
    extraListView:=TExtraListView.create(listbox1,HeaderControl1);
    extraListView.valueInfos:=@valueText;
    extraListView.OnDrawItem:=DrawItem;
  end;

  if mainForm.IsThereExactCircel then begin
    circelSearchingState:=cssStopped; //Thread braucht nicht gestartet werden
    exactCenter:=mainForm.ExactCircelCenter; //Da die Werte bereits berechnet wurden
    exactRadius:=mainForm.ExactCircelRadius;
  end else circelSearchingState:=cssWaiting; //Thread nicht gestartet
  CountOfNowCalculated:=0; //Keine Werte werden berechnet
  writeStatistik; //Statistik schreiben
  timer1.Enabled:=CountOfNowCalculated<>0; //Werden jetzt welche berechnet,
                                           //Timer starten
end;

procedure TstatsForm.Timer1Timer(Sender: TObject);
const isRealCalculated:string=' ... wird berechnet ... ';
begin
  //Hängt an isCalculated einen Buchstaben an, bis der ganze isRealCalculated
  //zusammen ist, und löscht ihn dann
  if length(isCalculated)<length(isRealCalculated) then
    isCalculated:=isCalculated+isRealCalculated[length(isCalculated)+1]
   else
    isCalculated:='';
  finishStatistik; //Überprüft, ob die Werte jetzt alle berechnet sind, und wenn
                   //nicht wird der neue Wert für '... wird berechnet ...' eingesetzt.
end;

procedure TstatsForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  //Beim Formschließen den Thread stoppen
  if circelSearchingState=cssStarted then TerminateThread(thread,0);
end;

procedure TstatsForm.FormDestroy(Sender: TObject);
begin
  extraListView.free; 
end;

//=================Berechnet die Werte für die Statistik=====================
//Geschätzer Umkreisradius
function getEstimatedRadius(out calulating:boolean):string;
begin
  Result:=IntToStr(mainForm.CircelRadius)+ ' Pixel';
end;
//Geschätzer Umkreismittelpunkt
function getEstimatedCenter(out calulating:boolean):string;
begin
  Result:='('+IntToStr(mainForm.CircelCenter.x)+'|'+IntToStr(mainForm.CircelCenter.y)+')';
end;
//Geschätze Umkreisfläche
function getEstimatedCircelArea(out calulating:boolean):string;
begin
  Result:=Format('%.0n', [1e-50+pi*sqr(mainForm.CircelRadius)])+ ' Quadratpixel';;
end;


//Exakter Umkreisradius
function getExactRadius(out calulating:boolean):string;
begin
  calulating:=true;
  //ist der Thread aus?
  if circelSearchingState=cssWaiting then startCircelSearch //dann starte ihn
  else if circelSearchingState=cssStopped then begin //ist er fertig
    calulating:=false; //Gebe das Ergebnis aus
    Result:=IntToStr(exactRadius)+ ' Pixel';
  end;
end;
//Exakter Umkreismittelpunkt
function getExactCenter(out calulating:boolean):string;
begin
 calulating:=true;
  if circelSearchingState=cssWaiting then startCircelSearch
  else if circelSearchingState=cssStopped then begin
    calulating:=false;
    Result:='('+IntToStr(exactCenter.x)+'|'+IntToStr(exactCenter.y)+')';
  end;
end;
//Exakte Umkreisfläche
function getExactCircelArea(out calulating:boolean):string;
begin
  calulating:=true;
  if circelSearchingState=cssWaiting then startCircelSearch
  else if circelSearchingState=cssStopped then begin
    calulating:=false;
    Result:=Format('%.0n', [1e-50+exactArea])+ ' Quadratpixel';
  end;
end;


//Unterschiede zwischen den Radien
function getRadiusDifferenc(out calulating:boolean):string;
var diff:integer;
begin
  calulating:=true;
  if circelSearchingState=cssWaiting then startCircelSearch
  else if circelSearchingState=cssStopped then begin
    calulating:=false;
    diff:=abs(exactRadius-mainForm.CircelRadius);
    Result:=IntToStr(diff)+ ' Pixel ('+IntToStr(100*diff div mainForm.CircelRadius)+'%)';
  end;
end;
//Entfernung der Mittelpunkte voneinander
function getCenterDistance(out calulating:boolean):string;
begin
  calulating:=true;
  if circelSearchingState=cssWaiting then startCircelSearch
  else if circelSearchingState=cssStopped then begin
    calulating:=false;
    Result:=IntToStr(round(vecDistance(exactCenter,mainForm.CircelCenter)))+ ' Pixel';
  end;
end;
//Unterschiede zwischen den Flächen
function getCircelAreaDifferenc(out calulating:boolean):string;
var estimatedArea,diff:integer;
begin
  calulating:=true;
  if circelSearchingState=cssWaiting then startCircelSearch
  else if circelSearchingState=cssStopped then begin
    calulating:=false;
    estimatedArea:=round(pi*sqr(mainForm.CircelRadius));
    diff:=abs(exactArea-estimatedArea);
    Result:=Format('%.0n', [1e-50+diff])+  ' Quadratpixel ('+
            IntToStr(100*diff div estimatedArea)+'%)';
  end;
end;

//Anzahl der kollidierten Kugeln
function getStaticSpheres(out calulating:boolean):string;
begin
  Result:=IntToStr(mainForm.Spheres.countStatic);
end;
//Anzahl der bewegenden Kugeln
function getMovingSpheres (out calulating:boolean):string;
begin
  Result:=IntToStr(mainForm.Spheres.countMoving);
end;


//Gesamte, durch Kugeln ausgefüllte Fläche, wenn man sie auf eine Ebene projeziert.
//Das hier ist nicht ganz exakt, wenn die Kugeln nicht als Kreise behandelt werden
//und sie unterschiedliche Radien haben, da dann Kugeln die über/unter einander
//liegen, doppelt gezählt werden
function getSphereArea (out calulating:boolean):string;
var Sphere,endSphere:tstaticSphere;
    SphereM,endSphereM:TMovingSphere;
    i:integer;
    area:int64;
begin
  if mainForm.useStandardRadius then //Wenn alle den gleichen Radius haben
    //Ist es einfach die Anzahl der Kugeln mal der Fläche von einem (pi kommt unten)
    area:=sqr(SphereRadius)*(mainForm.Spheres.countMoving+mainForm.Spheres.countStatic)
   else begin
    area:=0;
    //Ruhende
    i:=0;
    while i<=mainForm.Spheres.countStatic-1 do begin
      Sphere:=mainForm.Spheres.getStaticSphere(i);
      if i+StaticSpheresInArray<mainForm.Spheres.countStatic then
        endSphere:=mainForm.Spheres.getStaticSphere(i+StaticSpheresInArray-1)
       else
        endSphere:=mainForm.Spheres.getStaticSphere(mainForm.Spheres.countStatic-1);
      i:=i+StaticSpheresInArray;
      while (cardinal(Sphere)<=cardinal(endSphere)) do begin
        //Hier drin werden alle Kugeln durchlaufen
        area:=area+sqr(Sphere.r); //Pi kommt unten
        //Schleifenkörperende
        inc(Sphere);
      end;
    end;

    //Bewegende
    i:=0;
    while i<=mainForm.Spheres.countMoving-1 do begin
      SphereM:=mainForm.Spheres.getMovingSphere(i);
      if i+MovingSpheresInArray<mainForm.Spheres.countMoving then
        endSphereM:=mainForm.Spheres.getMovingSphere(i+MovingSpheresInArray-1)
       else
        endSphereM:=mainForm.Spheres.getMovingSphere(mainForm.Spheres.countMoving-1);
      i:=i+MovingSpheresInArray;
      while (cardinal(SphereM)<=cardinal(endSphereM)) do begin
        //Hier drin werden alle Kugeln durchlaufen
        area:=area+SphereM.r;
        //Schleifenkörperende
        inc(SphereM);
      end;
    end;
   end;
   SphereArea:=round(pi*area); //Das Pi wurde oben immer weggelassen
   Result:=format('%.0n',[1e-50+pi*area]) + ' Quadratpixel';
end;
//Unterschiede zwischen der durch Kugeln belegten Fläche und der geschätzten
//Umkreisfläche
function getSphereEstimatedCircelAreaDifferenc (out calulating:boolean):string;
var diff,tempArea:int64;
begin
  tempArea:=round(pi*sqr(mainForm.CircelRadius));
  diff:=abs(SphereArea-tempArea);
  Result:=IntToStr(diff)+ ' Pixel ('+IntToStr(100*diff div tempArea)+'%)';
end;
//Unterschiede zwischen der durch Kugeln belegten Fläche und der exakten
//Umkreisfläche
function getSphereExactCircelAreaDifferenc (out calulating:boolean):string;
var diff:int64;
begin
  calulating:=true;
  if circelSearchingState=cssWaiting then startCircelSearch
  else if circelSearchingState=cssStopped then begin
    calulating:=false;
    diff:=abs(SphereArea-exactArea);
    Result:=IntToStr(diff)+ ' Pixel ('+IntToStr(100*diff div exactArea)+'%)';
  end;
end;
//Durchschnittliche Kugelentfernung vom geschätzen Umkreismittelpunkt
function getEstimatedAverageDistance (out calulating:boolean):string;
var Sphere,endSphere:tstaticSphere;
    i:integer;
    distance:extended;
begin
  i:=0;
  distance:=0;
  while i<=mainForm.Spheres.countStatic-1 do begin
    Sphere:=mainForm.Spheres.getStaticSphere(i);
    if i+StaticSpheresInArray<mainForm.Spheres.countStatic then
      endSphere:=mainForm.Spheres.getStaticSphere(i+StaticSpheresInArray-1)
     else
      endSphere:=mainForm.Spheres.getStaticSphere(mainForm.Spheres.countStatic-1);
    i:=i+StaticSpheresInArray;
    while (cardinal(Sphere)<=cardinal(endSphere)) do begin
      //Hier drin werden alle Kugeln durchlaufen
      distance:=distance+vecDistance(mainForm.CircelCenter,Sphere.pos);
      //Schleifenkörperende
      inc(Sphere);
    end;
  end;
  result:=format('%.0n',[distance/mainForm.Spheres.countStatic]) + ' Pixel';
end;
//Durchschnittliche Kugelentfernung vom exakten Umkreismittelpunkt
function getExactAverageDistance (out calulating:boolean):string;
var Sphere,endSphere:tstaticSphere;
    i:integer;
    distance:extended;
begin
  calulating:=true;
  if circelSearchingState=cssWaiting then startCircelSearch
  else if circelSearchingState=cssStopped then begin
    calulating:=false;
    i:=0;
    distance:=0;
    while i<=mainForm.Spheres.countStatic-1 do begin
      Sphere:=mainForm.Spheres.getStaticSphere(i);
      if i+StaticSpheresInArray<mainForm.Spheres.countStatic then
        endSphere:=mainForm.Spheres.getStaticSphere(i+StaticSpheresInArray-1)
       else
        endSphere:=mainForm.Spheres.getStaticSphere(mainForm.Spheres.countStatic-1);
      i:=i+StaticSpheresInArray;
      while (cardinal(Sphere)<=cardinal(endSphere)) do begin
        //Hier drin werden alle Kugeln durchlaufen
        distance:=distance+vecDistance(exactCenter,Sphere.pos);
        //Schleifenkörperende
        inc(Sphere);
      end;
    end;
    result:=format('%.0n',[distance/mainForm.Spheres.countStatic]) + ' Pixel';
  end;
end;

//Das ist die Threadfunktion
function circelSearch(temp: pointer):dword; stdcall;
begin
  circelSearchingState:=cssStarted; //Status setzen
  stopCircelSearch:=false; //den Thread nicht gleich nicht abbrechen
  findOptimalCircel(exactCenter,exactRadius); //Genaue Werte berechnen
  exactArea:=round(pi*sqr(exactRadius)); //Fläche berechnen
  //Werte zurücksetzen
  thread:=0;
  circelSearchingState:=cssStopped;
  if not stopCircelSearch then begin //Wurde nicht abbgebrochen
    //Exakte Umkreiswerte der anderen Form übergeben
    mainForm.IsThereExactCircel:=true;
    mainForm.ExactCircelCenter:=exactCenter;
    mainForm.ExactCircelRadius:=exactRadius;
  end;

  result:=0;
  ExitThread(0); //Thread stoppen
end;

//Startet den Thread zur Berechnung des exakten Umkreises
procedure startCircelSearch;
var threadID:cardinal;
begin
  //Starten
  thread:=CreateThread(nil,0,@circelSearch,nil,0,threadID);
end;

//Sucht einen möglichst kleinen Kreis (genaugenommen ein Zylinder, aber im folgenden
//gehe ich immer davon aus das die Kugeln auf die Ebene projeziert wurden, so dass
//ein Kreis um andere Kreise berechnet wird) der alle Kugeln enthält, berechnet.
//Dazu werden drei auf dem Kreisrand liegende Kugeln gesucht, und ein Kreis
//gesucht, der diese berührt.
procedure findOptimalCircel(out middle:TVector2Int;out radius:integer);
  //Diese Hilfsfunktion sucht, die am weitesten von einem Punkt entfernte
  //Kugeln (wenn sie jeweils auf die XY-Ebene projeziert sin)
  procedure findFarestSphere(const pos:TVector2Ext; out distance:extended;out foundSphere:TStaticSphere);
  var i:integer; //Schleifenwerte
      Sphere,endSphere:TStaticSphere;
      newRadius,newSqrDistance:extended; //Neuer Radius und neue Entfernung^2
      farer:boolean; //Ist eine Kugel weiter weg
      oldRadius,oldSqrDistance:extended; //Alte Radius und alte Entfernung^2
      ltemp:extended; //temporärer Wert
  begin
    distance:=0;
    foundSphere:=nil;
    oldSqrDistance:=0;
    oldRadius:=0;
    i:=0;
    while i<=mainForm.Spheres.countStatic-1 do begin
      Sphere:=mainForm.Spheres.getStaticSphere(i);
      if i+StaticSpheresInArray<mainForm.Spheres.countStatic then
        endSphere:=mainForm.Spheres.getStaticSphere(i+StaticSpheresInArray-1)
       else
        endSphere:=mainForm.Spheres.getStaticSphere(mainForm.Spheres.countStatic-1);
       i:=i+StaticSpheresInArray;
       while (cardinal(Sphere)<=cardinal(endSphere)) do begin
        //Hier drin werden alle Kugeln durchlaufen
        newSqrDistance :=vecDotSelf(vecSub(toExtVector(Sphere.pos),pos));
        newRadius:=Sphere.r;
        //Der folgende Teil berechnet:
        //farer := oldRadius+sqrt(oldSqrDistance)<newRadius+sqrt(newSqrDistance);
        //Dabei wird eine ähnliche Methode zur Wurzelvermeidung wie in
        //calculateKollision benutzt
        farer:=false;
        if oldRadius>= newRadius then begin
          if oldSqrDistance >= newSqrDistance then farer:=false
          else begin
            ltemp:=sqr(newRadius - oldRadius) - oldSqrDistance - newSqrDistance;
            if abs(ltemp)<1e2000 then
              if ltemp>= 0 then  farer:=false
              else farer:=ltemp*ltemp>4*oldSqrDistance*newSqrDistance;
          end ;
        end else begin
          if oldSqrDistance > newSqrDistance then begin
            ltemp:=sqr(newRadius - oldRadius) - oldSqrDistance - newSqrDistance;
            if abs(ltemp)<1e2000 then
              if ltemp>= 0 then farer:=true
              else  farer:=ltemp*ltemp<=4*oldSqrDistance*newSqrDistance;
            end else farer:=true; //Fall 2
          end;
          if farer then begin
            oldSqrDistance:=newSqrDistance;
            oldRadius:=newRadius;
            foundSphere:=Sphere;
          end;
          //Schleifenende
          inc(Sphere);
      end;
   end;
   distance:=trunc(sqrt(oldSqrDistance))+oldRadius;
  end; //Ende von findFarestSphere

  //Testet, ob im übergebenen Kreis wirklich aller Kugeln sind
  function isCenterAndRadiusOK(const center: TVector2Ext; const radius: extended):boolean;
  var distance:extended;
      temp:TStaticSphere;
  begin
    //Am weitesten vom Mittelpunkt entfernte Kugel
    findFarestSphere(center,distance,temp);
    result:=distance<=radius+1;

    {$ifdef DEBUG_findOptimalCenterAndRadius}
      //Zeichnet den getesteten Kreis:
      with mainForm do
        if (radius>0) or (radius<400000) then begin
          if Result then PaintBox1.Canvas.pen.Color:=clFuchsia
          else PaintBox1.Canvas.pen.Color:=clAqua;
          PaintBox1.Canvas.brush.style:=bsClear;
          PaintBox1.Canvas.Ellipse(trunc((center.x-radius)*sizeFactor) div 100 + xmove,
                                   trunc((center.y-radius)*sizeFactor) div 100 + ymove,
                                   trunc(center.x+radius)*sizeFactor div 100 +xmove,
                                   trunc(center.y+radius)*sizeFactor div 100+ymove);
          PaintBox1.Canvas.Ellipse(trunc(center.x)*sizeFactor div 100-1 + xmove,
                                   trunc(center.y*sizeFactor) div 100-1 +ymove,
                                   trunc(center.x*sizeFactor) div 100+1 +xmove,
                                   trunc(center.y*sizeFactor) div 100++1 +ymove);
      end;
    {$endif}
  end;

  {$ifdef DEBUG_findOptimalCenterAndRadius}
    //Markiert eine Kugel auf dem Bildschirm mit einem blauen Kreis
    procedure markSphere(const Sphere:TStaticSphere);
    begin
      PaintBox1.Canvas.brush.style:=bsClear;
      PaintBox1.Canvas.pen.Color:=clAqua;
      PaintBox1.Canvas.pen.mode:=pmCopy;
      PaintBox1.Canvas.Ellipse((Sphere.pos.x-Sphere.r)*sizeFactor div 100 + xmove,
                               (Sphere.pos.y-Sphere.r)*sizeFactor div 100 + ymove,
                               (Sphere.pos.x+Sphere.r)*sizeFactor div 100 + xmove,
                               (Sphere.pos.y+Sphere.r)*sizeFactor div 100 + ymove);
      PaintBox1.Canvas.pen.mode:=pmMaskNotPen;
    end;
  {$endif}

  var
      foundSphere1,foundSphere2,foundSphere3:TStaticSphere; //Erste, 2te und 3te Kugel auf dem Kreisrand

  //Sucht einen Punkt um den ein Kreis mit Radius r foundSphere1 und foundSphere2
  //berührt, indem diese Gleichung nach x und y aufgelöst wird:
  {         2            2           2
  (x - k1x)  + (y - k1y)  = (r - r1)

           2            2           2
  (x - k2x)  + (y - k2y)  = (r - r2)
  }
  //Glücklicherweise ging das in einem Matheprogramm relativ schnell, auch
  //wenn es nicht ganz zu Ende optimiert hatte.
  //Es gibt allerdings zwei mögliche Lösungen raus.
  procedure findXYForRadius(const b1,b2:TStaticSphere; const r:extended;out pos1,pos2:TVector2Ext);
  var tempDiv, temp1, temp2, tempWurzel:extended;
  begin
    tempDiv:=2*(sqr(1e-50+b1.pos.x - b2.pos.x) + sqr(1e-50+b1.pos.y -b2.pos.y));
    tempWurzel:=-(SQR(1e-50+b1.pos.x - b2.pos.x)+SQR(1e-50+b1.pos.y-b2.pos.y)-SQR(b1.r-b2.r))*
                 (SQR(1e-50+b1.pos.x - b2.pos.x)+SQR(1e-50+b1.pos.y-b2.pos.y)- sqr(b1.r + b2.r - 2*r));

    //Sind die Werte möglich
    if (tempDiv<> 0)and(tempWurzel>=0) then begin
      tempWurzel:=SQRT(tempWurzel);
      tempWurzel:=tempWurzel*SIGN(b1.pos.x-b2.pos.x);

      temp1:=(b1.pos.y-b2.pos.y)*tempWurzel;
      temp2:=(b1.pos.x + b2.pos.x)*SQR(1e-50+b1.pos.y - b2.pos.y)
             +(b1.pos.x - b2.pos.x)*(SQR(1e-50+b1.pos.x) - SQR(1e-50+b2.pos.x)
             - b1.r*(b1.r - 2*r) + b2.r*(b2.r-2*r));

      //Lösungen für X
      pos1.x:=(temp1 + temp2) / tempDiv;
      pos2.x:=-(temp1 - temp2) / tempDiv;

      temp1:=(b1.pos.x-b2.pos.x)*tempWurzel;
      temp2:=(b1.pos.y+b2.pos.y)*sqr(1e-50+b1.pos.x-b2.pos.x)
              -(b1.pos.y-b2.pos.y)*(SQR(1e-50+b2.pos.y)+SQR(b1.r)
              -SQR(b1.pos.y)-2*b1.r*r-b2.r*(b2.r-2*r));


      //Lösungen für Y
      pos1.y:=-(temp1-temp2) / tempDiv;
      pos2.y:=(temp1+temp2) / tempDiv;
    end else begin
      pos1.x:=0;
      pos2.x:=0;
      pos1.y:=0;
      pos2.y:=0;
    end;
  end;  //Ende von findXYForRadius

  //Diese Funktion findet zu den Kugeln b1,b2 eine dritte, so dass ein möglichst
  //kleiner Kreis, der die 3 Kugeln berührt und der alle Kugeln enthält.
  //(Es kann aber auch sein, dass ein Kreis der so um die beiden Kugeln gezeichnet
  //wird, dass der Mittelpunkt exakt in der Mittel zwischen den beiden Kreisen ist
  //auch alle Kreise enthält, in dem Fall ist das der kleinstmöglichst Kreis
  //und es wird keine dritte Kugel gesucht)
  //Um den Radius eines Umkreises zu finden, der alle 3 Kugeln berührt, habe
  //ich mit einem Matheprogramm diese Gleichung
  {         2            2           2
  (x - k1x)  + (y - k1y)  = (r - r1)

           2            2           2
  (x - k2x)  + (y - k2y)  = (r - r2)

           2            2           2
  (x - k3x)  + (y - k3y)  = (r - r3)
  }
  //in die Form ar^2+br+c=0 gebracht (eigentlich sollte das Programm die Gleichung
  //gleich ganz nach r auflösen, aber als es nach 5 Stunden mit der Berechnung
  //immer noch nicht fertig war, habe ich es abgebrochen), und mit der abc-Formel
  //gelöst.
  //Leider war die Lösung dann aber nicht vereinfacht, so dass ich es per Hand
  //nachoptimieren musste, weshalb sie immer noch ziemlich voluminös ist, allerdings
  //war sie vorher 5mal so groß, also ist das schon mal ein Fortschritt.
  function findThirdSphere(const b1,b2:TStaticSphere; out midpos:TVector2Ext; out radius:extended):TStaticSphere;
  var newr1,newr2:extended; //Mögliche Radien
      pos1,pos2:TVector2Ext; //Mögliche Mittelpunkten
      a,b,c,wurzel:extended; //Variablen für abc-Formel
      r1,r2,r3:extended; //Gleitkommaradius, um Überläufe zu verhindern
      b1pos,b2pos,b3pos:TVector2Ext;//Gleitkommamittelpunkte, um Überläufe zu verhindern
      i:Integer; //Schleifenvariablen
      Sphere,endSphere:TStaticSphere;
  begin
    //---------------------------- Dritte Randkugel ----------------------------
    result:=nil;

    //Hier wird ein Punkt berechnet, der auf der Strecke zwischen den beiden
    //Mittelpunkten liegt, und vom Außenrand gleich weit entfernt ist.
    //Das läuft folgendermaßen:
    //Die Entfernung der beiden Ränder von einander, ist ja die Entfernung der
    //Mittelpunkten + der beiden Radien, dass ist auch der Durchmesser des Umkreises
    //Der gesucht Punkt ist also der, dessen Entfernung von einem Mittelpunkt genau
    //der Radius minus dem zum Mittelpunkt gehörenden Radius (weil die
    //Entfernung jetzt vom Mittelpunkt und nicht vom Rand gemessen wird), und der
    //in Richtung des jeweils anderen Mittelpunkt liegt.

    radius:=(vecDistance(b1.pos,b2.pos)+b1.r+b2.r)/2;
    midpos:=vecAdd(toExtVector(b1.pos),
                   vecMul(
                     vecNorm(
                        toExtVector(vecSub(b2.pos,b1.pos))
                     ),
                     radius-b1.r
                   )
            );

    if isCenterAndRadiusOK(midpos,radius) then
      exit; //Wenn der Kreis geht, kann es keinen kleineren Kreis geben, da dann
            //eine der Kugeln dann angeschnitten würde.

    //Die Werte werden in Gleitkommazahlen umgewandelt, weil die Werte in der
    //Rechnung schnell so groß werden können, dass es einen Überlauf bei
    //Integer geben würde
    r1:=b1.r;
    r2:=b2.r;
    b1pos:=toExtVector(b1.pos);
    b2pos:=toExtVector(b2.pos);


    radius:=1e30;//Erstmal einen großen Radius annehmen
    i:=0;
    while i<=mainForm.Spheres.countStatic-1 do begin
      Sphere:=mainForm.Spheres.getStaticSphere(i);
      if i+StaticSpheresInArray<mainForm.Spheres.countStatic then
        endSphere:=mainForm.Spheres.getStaticSphere(i+StaticSpheresInArray-1)
       else
        endSphere:=mainForm.Spheres.getStaticSphere(mainForm.Spheres.countStatic-1);
      i:=i+StaticSpheresInArray;
      while (cardinal(Sphere)<=cardinal(endSphere)) do begin
        if stopCircelSearch then exit; //soll der Thread abgebrochen werden => raus
        //Hier drin werden alle Kugeln durchlaufen
        if (Sphere<>b1)and(Sphere<>b2) then begin
          //Ist die ausgewählte Kugel nicht eine der beiden?

          //Ihre Werte auch im Gleitkommaformat speichern
          r3:=Sphere.r;
          b3pos:=toExtVector(Sphere.pos);

          //Jetzt kommt die abc-Formel
          a:=4*(
             -SQR(b2pos.x*b3pos.y-b2pos.y*b3pos.x)
             -SQR(b1pos.x*(b2pos.y-b3pos.y)-b1pos.y*(b2pos.x-b3pos.x))
             +(SQR(b1pos.x)+SQR(b1pos.y))*SQR(r2-r3)
             +SQR(b2pos.x*(r1-r3)-b3pos.x*(r1-r2))
             +SQR(b2pos.y*(r1-r3)-b3pos.y*(r1-r2))
             -2*(
               (r1-r3)*(r2-r3)*(b1pos.x*b2pos.x+b1pos.y*b2pos.y)
                -(r1-r2)*(r2-r3)*(b1pos.x*b3pos.x+b1pos.y*b3pos.y)
                +(b1pos.x*(b2pos.y-b3pos.y)-b1pos.y*(b2pos.x-b3pos.x))*(b2pos.x*b3pos.y-b2pos.y*b3pos.x)));
          if a<>0 then begin
            b:=4*(-power(r1,3)*(SQR(b2pos.x-b3pos.x)+SQR(b2pos.y-b3pos.y))
                  -power(r2,3)*(SQR(b1pos.x-b3pos.x)+SQR(b1pos.y-b3pos.y))
                  -power(r3,3)*(SQR(b1pos.x-b2pos.x)+SQR(b1pos.y-b2pos.y))
                  -((b1pos.x-b2pos.x)*(b2pos.x-b3pos.x)+(b1pos.y-b2pos.y)*(b2pos.y-b3pos.y))*
                   (r1*r3*(r1+r3)+r2*(SQR(b1pos.x-b3pos.x)+SQR(b1pos.y-b3pos.y)))
                  +((b1pos.x-b3pos.x)*(b1pos.x-b2pos.x)+(b1pos.y-b2pos.y)*(b1pos.y-b3pos.y))*
                   (r2*r3*(r2+r3)+r1*(SQR(b2pos.x-b3pos.x)+SQR(b2pos.y-b3pos.y)))
                  +((b1pos.x-b3pos.x)*(b2pos.x-b3pos.x)+(b1pos.y-b3pos.y)*(b2pos.y-b3pos.y))*
                   (r1*r2*(r1+r2)+r3*(SQR(b1pos.x-b2pos.x)+SQR(b1pos.y-b2pos.y))));

            c:=(SQR(b1pos.x-b2pos.x)+SQR(b1pos.y-b2pos.y))*
               (SQR(b1pos.x-b3pos.x)+SQR(b1pos.y-b3pos.y))*
               (SQR(b2pos.x-b3pos.x)+SQR(b2pos.y-b3pos.y))
               -2*SQR(r1)*((b1pos.x-b2pos.x)*(b1pos.x-b3pos.x)+(b1pos.y-b2pos.y)*(b1pos.y-b3pos.y))*
               (SQR(b2pos.x-b3pos.x)+SQR(b2pos.y-b3pos.y))
               +2*SQR(r2)*((b1pos.x-b2pos.x)*(b2pos.x-b3pos.x)+(b1pos.y-b2pos.y)*(b2pos.y-b3pos.y))*
               (SQR(b1pos.y-b3pos.y)+SQR(b1pos.x-b3pos.x))
               -2*SQR(r3)*((b1pos.x-b3pos.x)*(b2pos.x-b3pos.x)+(b1pos.y-b3pos.y)*(b2pos.y-b3pos.y))*
               (SQR(b1pos.x-b2pos.x)+SQR(b1pos.y-b2pos.y))
               +(b3pos.x+b3pos.y)*SQR(SQR(r1)-SQR(r2))
               +(b1pos.y*(SQR(r2)-SQR(r3))-b2pos.y*(SQR(r1)-SQR(r3))+2*b3pos.y*(SQR(r1)-SQR(r2)))*
               (b1pos.y*(SQR(r2)-SQR(r3))-b2pos.y*(SQR(r1)-SQR(r3)))
               +(b1pos.x*(SQR(r2)-SQR(r3))-b2pos.x*(SQR(r1)-SQR(r3))+2*b3pos.x*(SQR(r1)-SQR(r2)))
               *(b1pos.x*(SQR(r2)-SQR(r3))-b2pos.x*(SQR(r1)-SQR(r3)));

            wurzel:=b*b-4*a*c;
            if wurzel>=0 then begin
              wurzel:=sqrt(wurzel);
              //Es gibt wieder mögliche Radien:
              newr1:=-(b+wurzel)/(2*a);
              newr2:=-(b-wurzel)/(2*a);

              if (newr1<radius) and (newr1>0) then begin
                //Ist der erste Radius kleiner, und möglich wird der Mittelpunkt
                //zum Kreis berechnet. Dabei gibt es auch zwei Lösungen.
                findXYForRadius(b1,b2,newr1,pos1,pos2);
                //Lösungen überprüfen, die tatsächliche Entfernung vom Mittelpunkt
                //zu der neuen Kugel muss der Radiuslösung entsprechen, und
                //der Kreis muss alle Kugeln enthalten.
                if (abs(vecDistance(pos1,toExtVector(Sphere.pos))+Sphere.r-newr1)<1) and
                   (isCenterAndRadiusOK(pos1,newr1)) then begin
                      Result:=Sphere; //Neuen Umkreis speichern
                      radius:=newr1;
                      midpos:=pos1;
                end;
                //Lösung überprüfen
                if (abs(vecDistance(pos2,toExtVector(Sphere.pos))+Sphere.r-newr1)<1) and
                    (isCenterAndRadiusOK(pos2,newr1)) then begin
                      Result:=Sphere;
                      radius:=newr1;
                      midpos:=pos2;
                end;
              end;
              if (newr2<radius) and (newr2>0) then begin
                //Dasselbe für die andere Lösung für den Radius
                findXYForRadius(b1,b2,newr2,pos1,pos2);
                if (abs(vecDistance(pos1,toExtVector(Sphere.pos))+Sphere.r-newr2)<1) and
                    isCenterAndRadiusOK(pos1,newr1) then begin
                   Result:=Sphere;
                   radius:=newr2;
                   midpos:=pos1;
                end;
                if (abs(vecDistance(pos2,toExtVector(Sphere.pos))+Sphere.r-newr2)<1) and
                  isCenterAndRadiusOK(pos2,newr1) then begin
                  Result:=Sphere;
                  radius:=newr2;
                  midpos:=pos2;
                end;
              end;
            end;
          end;
        end;
        //Schleifenende
        inc(Sphere);
      end;
    end;
  end; //Ende von findThirdSphere

  //Diese Funktion stellt fest, ob p und q auf der gleichen Seite von der Geraden
  //durch v1 und v2 sind.
  //Da die einfache Methode es über die Steigung der Geraden zu überprüfen,
  //Dezimalzahlen braucht, werden hier die Normale zu den durch v2/p und
  //v2/q mit Mittelpunkt v1 aufgespannten Ebenen im 3D-Raum berechnet.
  //(mit dem Kreuzprodukt, wo bei sich einige Stellen wegkürzen)
  //Wenn die Punkte auf der gleichen Seite liegen, zeigen beide Kreuzprodukte
  //in die selbe Richtung
  function AreOnSameSide(const v1,v2,p,q:Tvector2int): boolean;
  begin
    result:=not (
             (((v2.x-v1.x)*(p.y-v1.y) - (v2.y-v1.y)*(p.x-v1.x))>0) //Kreuzprodukt 1
             xor
             (((v2.x-v1.x)*(q.y-v1.y) - (v2.y-v1.y)*(q.x-v1.x))>0)//Kreuzprodukt 2
            )
  end;

  //Überprüft ob der Punkt p im Dreich v1,v2,v3 liegt, indem für jede Dreiecksseite
  //getestet wird, ob der Punkt p und der Eckpunkt der nicht auf der Dreiecksseite
  //liegt, auf der selben Seite der Dreieckseite liegen, ist das nicht der Fall
  //liegt p nicht im Dreick, ansonsten schon.
  //Liegt p nicht im Dreick, wird auch der Eckpunkt zurück gegeben, der durch eine
  //Dreiecksseite von dem Punkt p getrennt ist.
  function pointInTriangle(const  v1,v2,v3,p:TVector2Int;out oppositeVector:integer):boolean;
  begin
    Result:=false;
    //Seite 2 zu 3 testen
    if not AreOnSameSide(v2,v3,v1,p) then begin
      oppositeVector:=1;
      exit;
    end;
    //Seite 1 zu 3 testen
    if not AreOnSameSide(v1,v3,v2,p) then begin
      oppositeVector:=2;
      exit;
    end;
    //Seite 1 zu 2 testen
    if not AreOnSameSide(v1,v2,v3,p) then begin
      oppositeVector:=3;
      exit;
    end;
    oppositeVector:=-1;
    result:=true;
  end;

  //Gibt den Schnittpunkt des Randes einer Kugel mit dem größten Kreis zurück,
  //der, um den Punkt middle geschlagen sie berührt.
  //Das ist auch der am weitesten von middle entfernte Randpunkt der Kugel,
  //und die Entfernung ist genau die Entfernung vom Mittelpunkt plus den
  //Kugelradius.
  //Deshalb kann er berechnet werden, indem ein Vektor von middle zum Kugel-
  //mittelpunkt um den Radius verlängert wird.
  function SphereBorder(const Sphere:TStaticSphere; const middle:TVector2Int): TVector2Int;
  var temp:TVector2Int;
  begin
    temp:=vecsub(Sphere.pos,middle);
    Result:=vecadd(vecTrunc(vecMul(toFloatVector(temp),1+Sphere.r/vecLength(temp))),middle);
  end;

var tempDistance:extended; //temporäre Variable, um findFarestSphere aufzurufen
    dir,pos:TVector2Ext; //Gerade beim Berechnen der 2ten Randkugel
    dirLen:extended;     //Länge von dir
    t,newt,tempT1,tempT2,tempTDiv,wurzel:extended; //temporäre Variablen im 2ten Schritt
    tempradius:extended; //Möglicher Umkreisradius
    tempmiddle:TVector2Ext; //Möglicher Umkreismittelpunkt
    oppositeVector:integer;//Ecke eines Dreieck, das von einer Dreiecksseite vom
                           //Mittelpunkt getrennt ist
    i:integer;                 //Schleifenvariablen
    Sphere,endSphere:TStaticSphere;
begin
  {$ifdef DEBUG_findOptimalCenterAndRadius}
    mainForm.PaintBox1.Canvas.pen.Mode:=pmMaskNotPen;
    mainForm.PaintBox1.Canvas.pen.width:=1;
  {$endif}
  //---------------------------- Erste Randkugel -------------------------------
  //Den ersten Schritt kann man sich so vorstellen, dass ein Kreis um den Nullpunkt
  //solange geschrumpft wird, bis er gegen eine Kugel prallt.
  //Das kann sehr schnell dadurch berechnet werden, dass die am weitesten
  //vom Nullpunkt entfernte Kugel gesucht wird, mit der kollidiert der Kreis.
  findFarestSphere(makeVectorExt(0,0),tempDistance,foundSphere1);
  if foundSphere1=nil then begin
    //Keine Kugel gefunden => es gibt keine Kugel
    middle:=makeVector(0,0);
    radius:=0;
    exit;
  end;
  {$ifdef DEBUG_findOptimalCenterAndRadius}
    markSphere(foundSphere1);
  {$endif}

  //-------------------------------Zweite Randkugel-----------------------------
  //Diesen Schritt kann man sich so vorstellen, das der Mittelpunkt des Umkreises
  //auf der Strecke vom Null zum Mittelpunkt befestigt wird, und der Umkreismittelpunkt
  //langsam zur bereits gefundenen Kugel bewegt wird, bis er gegen eine andere
  //Kugel prallt.
  //Das wird so simuliert, dass für jede Kugel der kleinste Umkreis gesucht wird,
  //dessen Mittelpunkt auf der Strecke liegt und den anderen Kreis berührt.
  //Der kleinste dieser Umkreise, der alle Kugeln enthält wird genommen.

  //Also:
  //Das ist die Richtung der Geraden, ein Pfeil der vom Nullpunkt zum Kugel-
  //mittelpunkt zeigt.
  dir:=toExtVector(foundSphere1.pos);
  dirLen:=vecLength(dir);

  foundSphere2:=nil;
  t:=0; //t gibt die Entfernung des Umkreismittelpunkt vom Nullpunkt an
  i:=0;
  while i<=mainForm.Spheres.countStatic-1 do begin
    Sphere:=mainForm.Spheres.getStaticSphere(i);
    if i+StaticSpheresInArray<mainForm.Spheres.countStatic then
      endSphere:=mainForm.Spheres.getStaticSphere(i+StaticSpheresInArray-1)
     else
      endSphere:=mainForm.Spheres.getStaticSphere(mainForm.Spheres.countStatic-1);
    i:=i+StaticSpheresInArray;
    while (cardinal(Sphere)<=cardinal(endSphere)) do begin
      if stopCircelSearch then exit;  //Abbruchmöglichkeit von außen
      //Hier drin werden alle Kugeln durchlaufen
      if (Sphere<>foundSphere1) then begin
        //Jetzt muss der Umkreis gefunden werden, der beide Kugeln berührt.
        //Dazu wird die Lösung zu diesem Gleichungssystem gesucht:
        {distance(t * dir, foundSphere1) + foundSphere1.r =
         distance(t * dir, Sphere) + Sphere.r}
        //und 0 < t < length(foundSphere1.pos), da der Umkreismittelpunkt sonst
        //hinter der zuerst gefundenen Kugel läge, was bedeutet, dass
        //die Kugel Sphere weiter vom Nullpunkt entfernt wäre als foundSphere1,
        //aber foundSphere ist ja gerade als die am weitesten entfernte
        //definiert.                                                               
        //Also ein Punkt auf der Geraden 0 + t*dir der von beiden Kugeln
        //gleichweit entfernt ist.
        //Da bei kommen zwei Lösungen raus
        tempTDiv:= 2* (sqr(foundSphere1.r - Sphere.r) * (sqr(1e-50+dir.x) + sqr(1e-50+dir.y))
                   - sqr(dir.x*(1e-50+foundSphere1.pos.x - Sphere.pos.x) + dir.y*(1e-50+foundSphere1.pos.y - Sphere.pos.y)));
        wurzel:=(sqr(foundSphere1.r - Sphere.r) - sqr(1e-50+foundSphere1.pos.x - Sphere.pos.x) - sqr(1e-50+foundSphere1.pos.y - Sphere.pos.y))*
                (
                  sqr(dir.x)*(sqr(foundSphere1.r - Sphere.r) - sqr(1e-50+foundSphere1.pos.x - Sphere.pos.x) - sqr(1e-50+foundSphere1.pos.y + Sphere.pos.y))
                  + 4*dir.x*dir.y*(1e-50+foundSphere1.pos.x*Sphere.pos.y + Sphere.pos.x*foundSphere1.pos.y)
                  + sqr(dir.y)*(sqr(foundSphere1.r - Sphere.r) - sqr(1e-50+foundSphere1.pos.x + Sphere.pos.x) - sqr(1e-50+foundSphere1.pos.y - Sphere.pos.y))
               );
        if (wurzel>=0) and (tempTDiv<>0) then begin
          tempT1:=dir.x*((1e-50+foundSphere1.pos.x + Sphere.pos.x)*sqr(foundSphere1.r - Sphere.r) + (sqr(1e-50+foundSphere1.pos.x) - sqr(1e-50+Sphere.pos.x) + sqr(1e-50+foundSphere1.pos.y) - sqr(1e-50+Sphere.pos.y))*(1e-50+Sphere.pos.x - foundSphere1.pos.x))
                  + dir.y*((1e-50+foundSphere1.pos.y + Sphere.pos.y)*sqr(foundSphere1.r - Sphere.r) + (sqr(1e-50+foundSphere1.pos.x) - sqr(1e-50+Sphere.pos.x) + sqr(1e-50+foundSphere1.pos.y) - sqr(1e-50+Sphere.pos.y))*(1e-50+Sphere.pos.y - foundSphere1.pos.y));
          tempT2:= ABS(foundSphere1.r - Sphere.r)*sqrt(wurzel);
          //Erste Lösung:
          newt:=(tempT1+tempT2)/tempTDiv;
          if (newt>t) and(newt<1) then begin
            //Punkt berechnen und überprüfen
            pos:=vecmul(dir,newt);
            if isCenterAndRadiusOK(pos,(1 - newt)*dirLen+foundSphere1.r) then begin
              foundSphere2:=Sphere;
              t:=newt;
            end;
          end;
          //Zweite Lösung:
          newt:=(tempT1-tempT2)/tempTDiv;
          if (newt>t) and(newt<1) then begin
            //Punkt berechnen und überprüfen
            pos:=vecmul(dir,newt);
            if isCenterAndRadiusOK(pos,(1 - newt)*dirLen+foundSphere1.r) then begin
              foundSphere2:=Sphere;
              t:=newt;
            end;
          end;
      end;end;
      //Schleifenende
      inc(Sphere);
    end;
  end;
  if foundSphere2=nil then begin //Keine zweite Kugel gefunden?
    //=>Es gibt nur ein Kugel
    middle:=foundSphere1.pos;
    radius:=foundSphere1.r;
    exit;
  end;
  {$ifdef DEBUG_findOptimalCenterAndRadius}markSphere(foundSphere2);{$endif}

  //Jetzt wird eine dritte Kugel gesucht, so dass der kleinste Kreis gefunden wird
  //der die beiden bereits gefundenen Kugeln und eine dritte berührt, und alle
  //anderen Kugeln enthält.
  foundSphere3:=findThirdSphere(foundSphere1,foundSphere2,tempmiddle,tempradius);
  if stopCircelSearch then exit; //soll der Thread abgebrochen werden => raus
  middle:=vecTrunc(tempmiddle);
  radius:=trunc(tempradius);
  if foundSphere3= nil then exit; //Keine dritte Kugel => zwei reichen

  {$ifdef DEBUG_findOptimalCenterAndRadius}markSphere(foundSphere3);{$endif}

  //Wenn der Mittelpunkt innerhalb dem von den Berührungspunkten des Umkreises
  //an den Kugeln gebildeten Dreieck ist, kann der Kreis nicht weitergeschrumpf
  //werden,  weil jeder kleinere Kreis eine der drei Kugeln schneiden würde,
  //ist der Mittelpunkt aber nicht drin, kann er noch schrumpfen.
  if pointInTriangle(foundSphere1.pos,foundSphere2.pos,foundSphere3.pos,middle,oppositeVector) then
    exit //Das war eine Näherung, da diese Dreieck im anderen enthalten ist
   else
    if pointInTriangle(SphereBorder(foundSphere1,middle),SphereBorder(foundSphere2,middle),
                      SphereBorder(foundSphere3,middle),middle,oppositeVector) then exit
    else begin
      //Die beiden Kugeln, die die Seite des Dreiecks bilden, das den Mittelpunkt
      //von der anderen Dreiecksecke trennt, liegen auf dem Rand des gesuchten
      //Umkreises.
      case oppositeVector of
        1: foundSphere1:=foundSphere3;
        2: foundSphere2:=foundSphere3;
        3: ;
        else exit;
      end;

      foundSphere3:=findThirdSphere(foundSphere1,foundSphere2,tempmiddle,tempradius);
      {$ifdef DEBUG_findOptimalCenterAndRadius}
        if foundSphere3<>nil then
          markSphere(foundSphere3);
      {$endif}
      middle:=vecTrunc(tempmiddle);
      radius:=trunc(tempradius);
    end;
end; //Ende von findOptimalCenterAndRadius
end.

