unit threeD;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls,opengl,vectors,math, StdCtrls, Spin,helpWindow,
  Spheres3D, ComCtrls, Buttons;  //Spheres3D.pas ist mit Spheres.pas identisch, abgesehen davon, dass
            //die Koordinaten 3-dimensional sind, die Typen entsprechend
            //heißen und es keine Struktur für bewegte Kugeln gibt, da die
            //Positionierung im 3D-Raum auf dem Bildschirm ziemlich kompliziert
            //ist

type
  TthreeDForm = class(TForm)
    Timer1: TTimer; //Rerendert jede Sekunde das Bild neu
    newSphereCount: TSpinEdit;    //Kugelerstellungsstartbutton
    maxRadius: TSpinEdit; //maximaler Radius (standard: 50)
    minRadius: TSpinEdit; //mini...    "     (standard: 5)
    rotationY: TSpinEdit; //Rotation um die Y-Achse (standard: 0)
    rotationX: TSpinEdit; //Rotation um die X-Achse (standard: 0)
    size: TSpinEdit; //Entfernung zum Umkreismittelpunkt (standard: 100)
    SphereQuality: TScrollBar; //sqrt(Polygonzahl pro Kugel) (standard: 10)
    StatusBar1: TStatusBar; //Statusmeldungsausgabe
    outputPanel: TPanel; //Paintbox die über dem outputPanel liegt
    deleteOld: TCheckBox; //alte Kugeln vorher löschen? (standard: ja)
    wireframe: TCheckBox; //Drahtmodell? (standard: nein)
    gourandShading: TCheckBox; //Gourandshading? (standard: ja)
    differentRadiuses: TCheckBox; //Kugeln mit verschiedenen Radien erzeigen (s: nein)
    ProgressBar1: TProgressBar; //Aufruf der Farbeinstellungsform
    filename: TEdit; //Versuchen alle Kugeln zu zeigen
    //unwichtige Designkomponenten
    Panel: TPanel;Bevel1: TBevel;Bevel2: TBevel;Bevel3: TBevel;Label2: TLabel;
    Label1: TLabel;Label3: TLabel;Label4: TLabel;Label5: TLabel;Label6: TLabel;Label8: TLabel;Label9: TLabel;Label10: TLabel;Label11: TLabel;
    Label12: TLabel;Label13: TLabel;Label14: TLabel;Label19: TLabel;
    createSpheres: TBitBtn;
    save: TBitBtn;
    showAll: TBitBtn;
    renderCircel: TCheckBox;
    Label7: TLabel;
    SphereQuality2: TScrollBar;
    selectFile: TBitBtn;
    markShape: TShape;
    colorOptions: TBitBtn;
    sphereCount: TLabel;
    polyCount: TLabel;
    frameRate: TLabel;
    close: TBitBtn;
    procedure FormCreate(Sender: TObject); //Standardinitialisierung
    procedure FormShow(Sender: TObject); //OpenGL-Initialisierung
    procedure FormClose(Sender: TObject; var Action: TCloseAction); //OpenGL-Stop
    procedure FormDestroy(Sender: TObject); //Freigabe
    procedure createSpheresClick(Sender: TObject); //Kugeln erzeugen
    //2 Prozeduren zur Mauseingabeverarbeitung (Drehung/Skalierung)
    procedure outputPanelMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure outputPanelMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    //Versucht die Ansicht so zu skalieren, dass man alle Kugeln sieht
    procedure showAllClick(Sender: TObject);
    procedure colorOptionsClick(Sender: TObject); //Aufruf der Farbeinstellungen
    procedure selectFileClick(Sender: TObject); //Dateiauswahldialog
    procedure saveClick(Sender: TObject); //Abspeichern
    //Prozeduren die Werteingaben überprüfen oder neu rendern
    procedure Timer1Timer(Sender: TObject);
    procedure wireframeClick(Sender: TObject);
    procedure SphereQualityChange(Sender: TObject);
    procedure gourandShadingClick(Sender: TObject);
    procedure rotationYChange(Sender: TObject);
    procedure outputPanelResize(Sender: TObject);
    procedure renderCircelClick(Sender: TObject);
    procedure closeClick(Sender: TObject);
  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}
    //Begrenzungen Begrenzungsquader
    minX,maxX,minZ,maxZ,minY,maxY:integer;
    //Mittelpunkt und Radius einer Kugel, die alle Kugeln enthält
    CircelCenter:TVector3Int;
    CircelRadius:integer;

    Spheres:T3DSphereList; //Kugelliste

    panelDC:HDC;  //Device-Context vom Panel
    context:HGLRC; //OpenGL-Context
    SphereList1,SphereList2: integer; //Name der Displayliste zur Erzeugung einer Kugel

    mouseX,mouseY:integer; //Position der Maus bei Beginn der Mausbewegung
    startRotX,startRotY,startSize:integer; //Werte bei Beginn der Mausbewegung
    procedure render; //Neuzeichnen
    //Berechnet die Kollision einer Kugel mit den anderen und gibt die Enfernung
    //zum Mittelpunkt bei der Kollision zum Punkt pos zurück, und den Mittelpunkt
    procedure calculateKollision3D(const dir:TVector3Float;const pos:TVector3Int;const radius:byte;
                                    var kollisionTime:single; var kollisionPos:TVector3Int);
    //Berechnet den Begrenzunhsquader bzw. Kugel wenn eine neue Kugel erstellt wurde
    procedure updateCircel3D(const newSphere:T3DStaticSphere);
    //Berechnet die Displaylist für eine Kugel neu
    procedure recalculateSphere(const quality1,quality2:integer);
  end;
  //Jedes Panel hat einen versteckten Canvas, wenn man es dann in eine passende
  //Klasse mit öffentlichem Canvas konvertiert, kann man drauf zugreifen.
  TCanvasPanel=class(TPanel)
    published
      property Canvas;
  end;
var
  threeDForm: TthreeDForm;

implementation

uses farbe, a1_u;

{$R *.DFM}

//Range-Random, Zufallszahl 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
//==============================================================================

//Diese Prozedure berechnet eine Kollision zwischen den ruhenden Kugeln
//und einer die sich mit Radius radius von der Stelle pos im 3D-Raum in die
//Richtung dir bewegt.
//Sie arbeitet völlig analog zur 2D Version in a1_u.pas, da die Rechnung ja
//nur mit Vektor arbeitet, die in beliebig hohen Dimensionen funktionieren
procedure TthreeDForm.calculateKollision3D(const dir:TVector3Float;const pos:TVector3Int;const radius:byte;
                                    var kollisionTime:single; var kollisionPos:TVector3Int);
var i:integer;
    {l,newl,}a,b,b_lookup,c,wurzelTerm,oldb,oldw,ltemp:single;
    nearer:boolean;
    Sphere2,endSphere:T3DStaticSphere;

begin
  //Kollisionspunkt berechnen:
  //Kollision Bewegungsvektor vom Kugel Nr. Sphere mit Kugel i
  a:=4*vecDotSelf(dir);
  if a=0 then begin
    kollisionTime:=1e35;
    kollisionPos:=makeVector(0,0,0);
    exit;
  end;
  b_lookup:=(dir.x*pos.x+dir.y*pos.y+dir.z*pos.z);
  oldb:=1e30;
  oldw:=-1e30;

  i:=0;
  while i<=Spheres.countStatic-1 do begin
    Sphere2:=Spheres.getStaticSphere(i);
    if i+StaticSpheres3DInArray<Spheres.countStatic then
      endSphere:=Spheres.getStaticSphere(i+StaticSpheres3DInArray-1)
     else
      endSphere:=Spheres.getStaticSphere(Spheres.countStatic-1);
    i:=i+StaticSpheres3DInArray;
    while (cardinal(Sphere2)<=cardinal(endSphere)) do begin
      //Zu finden Ein l mit distance(Sphere1.pos+l*Sphere1.dir,Sphere2.pos)=2*radius
      //siehe 2D Version
       b:=(-2)*(b_lookup-(dir.x*Sphere2.pos.x+dir.y*Sphere2.pos.y+dir.z*Sphere2.pos.z));
      if b<0 then begin
        inc(Sphere2);
        continue; //Bei b<0,  wäre die andere Kugel hinter dieser
      end;
      c:=sqr(pos.x-Sphere2.pos.x)+sqr(pos.y-Sphere2.pos.y)+sqr(pos.z-Sphere2.pos.z)-sqr(radius+Sphere2.r);

      wurzelTerm:=b*b-a*c;
      if (wurzelTerm<0) then begin
        inc(Sphere2);
        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));
          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));
          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(Sphere2);
    end;
  end;
  if oldw>=0 then begin
   kollisionTime:=2*(oldb-sqrt(oldw))/(a);
    if kollisionTime<1e10 then
      kollisionPos:=vecAdd(pos,vectrunc(vecmul(dir,kollisionTime)))
  end else kollisionTime:=1e35;
end;

//Berechnet die Änderung an der Umkugeles nach dem Hinzufügen von newSphere.
procedure TthreeDForm.updateCircel3D(const newSphere:T3DStaticSphere);
var i:integer;  //Schleifenvariablen
    Sphere,endSphere:T3DStaticSphere;
    distance:integer; //Entfernung vom Umkugelmittelpunkt
    changed:boolean; //Wurde der Begrenzungsquader verändert?
begin
  //Neue Quadergrößen berechnen
  changed:=false;
  if newSphere.pos.x-newSphere.r<minX then begin
    minX:=newSphere.pos.x-newSphere.r;
    changed:=true;
  end;
  if newSphere.pos.x+newSphere.r>maxX then begin
    maxX:=newSphere.pos.x+newSphere.r;
    changed:=true;
  end;
  if newSphere.pos.y-newSphere.r<minY then begin
    minY:=newSphere.pos.y-newSphere.r;
    changed:=true;
  end;
  if newSphere.pos.y+newSphere.r>maxY then begin
    maxY:=newSphere.pos.y+newSphere.r;
    changed:=true;
  end;
  if newSphere.pos.Z-newSphere.r<minZ then begin
    minZ:=newSphere.pos.Z-newSphere.r;
    changed:=true;
  end;
  if newSphere.pos.Z+newSphere.r>maxZ then begin
    maxZ:=newSphere.pos.Z+newSphere.r;
    changed:=true;
  end;

  if changed then begin//Wenn das Rechteck verändert wurde
    //Neuen Mittelpunkt, als Schnittpunkt der Diagonalen, berechnen (O(1))
    CircelCenter.x:=(minX+maxX) div 2;
    CircelCenter.y:=(minY+maxY) div 2;
    CircelCenter.z:=(minZ+maxZ) div 2;

    //Neuen Radius berechnen (O(n))
    circelRadius:=0;
    i:=0;
    while i<=Spheres.countStatic-1 do begin
      Sphere:=Spheres.getStaticSphere(i);
      if i+StaticSpheres3DInArray<Spheres.countStatic then
        endSphere:=Spheres.getStaticSphere(i+StaticSpheres3DInArray-1)
       else
        endSphere:=Spheres.getStaticSphere(Spheres.countStatic-1);
      i:=i+StaticSpheres3DInArray;
      while (cardinal(Sphere)<=cardinal(endSphere)) do begin
        //--- Schleife ...
        distance :=trunc(vecLength(vecSub(Sphere.pos,CircelCenter)))+
                     Sphere.r;
        if distance>CircelRadius then CircelRadius:= distance;
        //... Schleifenende ---
        inc(Sphere);
      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
    distance:=trunc(sqrt(vecDotSelf(vecSub(newSphere.pos,CircelCenter))))+newSphere.r;
    if distance>CircelRadius then CircelRadius:=distance;
  end;
end;

//Erzeugt neue Kugeln
procedure TthreeDForm.createSpheresClick(Sender: TObject);
var
    useStandardRadius:boolean; //Sollen alle Kugeln den gleichen Radius haben?
    i:integer; //Anzahl der vorhanden Kugeln
    aimcount:integer; //zu erzeugende Kugeln
    failed:integer; //Anzahl der Kugeln die keine andere getroffen haben
    minR,maxR,radius:integer; //minimaler, maximaler und tatsächlicher Radius
    winkel1,winkel2:single; //Position der neuen Kugel im Bogenmaß
    cos,sin:extended; //Cosinus und Sinus von jeweils verchiedenen Winkeln
    time:cardinal; //Startzeitpunkt

    pos:TVector3Int; //Startposition der Kugel
    dir,olddir:TVector3Float; //Richtung der Kugel
    len:integer; //Kugelmittelpunktentfernung vom Umkugelmittelpunkt
    alpha:single; //Sichtwinkel
    usedalpha1:single; //Der Winkel zwischen der Kugelrichtung und einer Achse
    usedalpha2:single; // "  "      "        "                  " amderem "

    x,y,z,s,xx,yy,zz, xy, yz, zx, xs,ys,zs,one_c:single; //Variablen für Rotation

    kollisionTime:single; //Flugdauer der Kugel vom Start zum Kollsionspunkt
    kollisionPos:TVector3Int; //Mittelpunkt der Kugel nach der Kollision

begin
  if newSphereCount.Value > 50000 then //sehr viele Kugeln zu erstellen?
    if MessageBox(handle,pchar('Wollen Sie wirklich '+inttostr(newSphereCount.Value)+
                                ' Kugeln erstellen? Das wird ziemlich lange dauern.'),
                  '2D-Kugelsimulation', mb_yesno)=id_no then exit;
  Randomize;
  //Alten Kugeln löschen
  if deleteOld.Checked then begin
    for i:=Spheres.countstatic-1 downto 1 do Spheres.deleteStaticSphere(i);
    //Umkugel zurücksetzen
    minX:=-Spheres.getStaticSphere(0).r;
    maxX:=Spheres.getStaticSphere(0).r;
    minY:=minX;
    maxY:=maxX;
    minZ:=minX;
    maxZ:=maxX;
    CircelCenter:=Spheres.getStaticSphere(0).pos;
    CircelRadius:=Spheres.getStaticSphere(0).r;
  end;

  //Variableninit
  useStandardRadius:=not differentRadiuses.Checked; //Haben alle den gleich Radius?
  aimcount:=newSphereCount.Value; //Zu erstellende Anzahl
  i:=Spheres.countStatic;  //Bereits vorhandene
  failed:=0; //Mißlungene
  time:=GetTickCount; //Zeit
  minR:=minRadius.Value; //Minimaler Kugelradius
  maxR:=maxRadius.Value; //Maximaler   "

  StatusBar1.Panels[0].Text:='Kugeln werden erstellt, bitte warten.';
  ProgressBar1.Max:=aimcount;
  ProgressBar1.Position:=i;


  while i<aimcount do begin //Solange nicht alle Kugeln erstellt sind, tu ...
    try
      len:=2*(CircelRadius+maxR); //Entfernung von Umkugelmittelpunkt

      winkel1:=rrandom(1,360000)*pi/180000;
      winkel2:=rrandom(1,360000)*pi/180000;

      //Positionsberechnung auf der XY-Ebene
      SinCos(winkel1,sin,cos);
      pos.x:=round(len * cos);
      pos.y:=round(len * sin);

      //Um die Y-Achse drehen
      SinCos(winkel2,sin,cos);
      pos.z:=round(pos.x * sin);
      pos.x:=round(pos.x * cos);

      //Radius bekommen
      if useStandardRadius then radius:=SphereRadius
      else radius:=rrandom(minR,maxR);

      //--Richtung berechnen--
      //Sichtwinkel berechnen (Erklärung: Siehe 2D-Version)
      alpha:=(CircelRadius + radius)/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.
      usedalpha1:=rrandom(-alpha,alpha);

      //Einen Vektor vom Umkugelmittelpunkt (der wurde für diese Berechnung zum
      //Nullpunkt "gezogen") zum Kugelmittelpunkt entsprechend drehen
      dir:=vecRot(pos,usedalpha1);

      //Jetzt muss der Vektor noch um die durch pos-Vektor gebildete Achse gedreht werden,
      //damit auch alle möglichen Richtungen erreicht werden können. (Die Umkugel
      //sieht Mittelpunkt der Kugel wie eine Scheibe aus und die Achse pos geht genau
      //durch Mittelpunkt der Umkugel und der neuen Kugel)
      //Nach dem MESA OpenGL Rotations Code:
      usedalpha2:=rrandom(0.0,360.0); //Zufallswinkel
      SinCos(usedalpha2,sin,cos);
      //Jetzt wird gedreht
      x := pos.x/len;
      y := pos.y/len;
      z := pos.z/len;
      xx := x * x;
      yy := y * y;
      zz := z * z;
      xy := x * y;
      yz := y * z;
      zx := z * x;
      xs := x * sin;
      ys := y * sin;
      zs := z * sin;
      one_c := 1.0 - cos;

      olddir:=dir;
      dir.x:= ((one_c * xx) + cos)*olddir.x+ ((one_c * xy) + zs)*olddir.y + ((one_c * zx) - ys)*olddir.z;
      dir.y:= ((one_c * xy) - zs)*olddir.x+ ((one_c * yy) + cos)*olddir.y + ((one_c * yz) + xs)*olddir.z;
      dir.z:= ((one_c * zx) + ys)*olddir.x+ ((one_c * yz) - xs)*olddir.y + ((one_c * zz) + cos)*olddir.z;

      //fertig gedreht ;-), jetzt wird die Richtung die bisher wegzeigt umgedreht
      //und normalisiert
      dir.x:= -dir.x / len;
      dir.y:= -dir.y / len;
      dir.z:= -dir.z / len;

      //Bisher wurde als Umkugelmittelpunkt der Nullpunkt benutzt, das wird hetzt
      //korrigiert
      pos:=vecAdd(pos,circelCenter);
      //Kollision berechnen
      calculateKollision3D(dir,pos,radius,kollisionTime,kollisionPos);
      if kollisionTime<1e34 then begin //Gab es eine?
        //Kugel dahin beamen
        with Spheres.createStaticSphere^ do begin
          pos:=kollisionPos;
          r:=radius;
        end;
        //Umkugel neuberechnen
        updateCircel3D(Spheres.getStaticSphere(Spheres.countStatic-1));
        inc(i);
        if i and 255 =0then //alle 256 Kugeln die Fortschrittsanzeige aktualisieren
          ProgressBar1.Position:=i;
       end else inc(failed); //Schade, daneben
    except //Manchmal gibt es hier eine Division durch 0, obwohl das vor jeder Division getestet wird :-(
    end;
  end;
  ProgressBar1.Position:=aimcount; //fertig
  StatusBar1.Panels[0].Text:='Muster mit '+INtToStr(i)+' Kugel erzeugt. Benötigte Zeit: '+IntToStr((gettickcount-time) div 1000)+ ' Sekunden. Nicht kollidierte Kugeln: '+Inttostr(failed);
  sphereCount.Caption:='Anzahl der Kugeln: '+IntToStr(Spheres.countStatic);
  if renderCircel.Checked then
    polyCount.Caption:='Anzahl der Polygone: '+IntToStr(Spheres.countStatic*sqr(SphereQuality.Position)
                                                        +sqr(SphereQuality2.Position))
   else
    polyCount.Caption:='Anzahl der Polygone: '+IntToStr(Spheres.countStatic*sqr(SphereQuality.Position));
  showAll.click; //Alles anzeigen
end;



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

//Kugeln neu zeichnen
procedure TthreeDForm.render;
const shines:array[0..0] of single=(128);//Reflexionsgrad
var i:integer; //Variablen für eine Schleife über alle Kugeln
    Sphere,endSphere:T3DStaticSphere;
    time:dword; //Startzeitpunkt
    sizecorrection:single; //Wert, der zu jedem Radius addiert wird
    c:tcolor; //Farbe im Windowsformat
    color:array[0..3] of single; //Farbe im OpenGL-Format
begin
  time:=GetTickCount;
  //Farbeinstellungen
  //Diffuse (matte Grundfarbe) Farbe
  c:=colorManager.getColor(Color_3D_Diffuse); //Farbe holen
  color[0]:=GetRValue(c)/256; //Ins OpenGL-Format konvertieren
  color[1]:=GetGValue(c)/256;
  color[2]:=GetBValue(c)/256;
  color[3]:=0.5;
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,@color[0]); //einstellen
  //Specular (reflektierte) Farbe
  c:=colorManager.getColor(Color_3D_Specular);
  color[0]:=GetRValue(c)/256;
  color[1]:=GetGValue(c)/256;
  color[2]:=GetBValue(c)/256;
  glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,@color[0]);
  //Reflexionsgrad
  glMaterialfv(GL_FRONT_AND_BACK,GL_SHININESS,@shines[0]);


  //Frame zurücksetzten
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glLoadIdentity;
  //Position einstellen
  glTranslatef(0,0,-size.Value);    //Entfernung  setzen
  glRotatef(rotationY.Value,0,1,0); //Rotationswinkel "
  glRotatef(rotationX.Value,1,0,0);
  //Den Umkreismittelpunkt, als Rotationszentrum nehmen
  //(Diese Transformation werden wegen der Art der Matrixmultiplikation von OpenGL
  //rückwärts im Vergleich zum Audruf ausgeführt)
  glTranslatef(-CircelCenter.x,-CircelCenter.y,-CircelCenter.z);

  //Wert, der zu jedem Radius addiert wird, berechnen da es bei niedrigen Polygonen
  //aussieht, als würden sich die Kugeln nicht berühren
  if SphereQuality.Position<5 then sizecorrection:=4
  else if SphereQuality.Position<10 then sizecorrection:=3
  else if SphereQuality.Position<20 then sizecorrection:=2
  else if SphereQuality.Position<30 then sizecorrection:=1
  else sizecorrection:=0.5;

  i:=0;
  while i<=Spheres.countStatic-1 do begin
    Sphere:=Spheres.getStaticSphere(i);
    if i+StaticSpheres3DInArray<Spheres.countStatic then
      endSphere:=Spheres.getStaticSphere(i+StaticSpheres3DInArray-1)
     else
      endSphere:=Spheres.getStaticSphere(Spheres.countStatic-1);
    i:=i+StaticSpheres3DInArray;
    while (cardinal(Sphere)<=cardinal(endSphere)) do begin
      //------ Schleife ...
        glPushMatrix; //Position sichern
        //Sphereposition berechnen
        glTranslatef(Sphere.pos.x,Sphere.pos.y,Sphere.pos.z);
        //Auf den entsprechenden Radius skalieren
        glScalef(Sphere.r+sizecorrection,Sphere.r+sizecorrection,Sphere.r+sizecorrection);
        glCallList(SphereList1);
        glPopMatrix;  //Position wiederherstellen
      //... --------
      inc(Sphere);
    end;
  end;

  if renderCircel.Checked then begin
    //Diffuse (matte Grundfarbe) Farbe
    c:=colorManager.getColor(Color_3D_Circel);
    color[0]:=GetRValue(c)/256;
    color[1]:=GetGValue(c)/256;
    color[2]:=GetBValue(c)/256;
    color[3]:=0.5;
    glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,@color[0]);

    glDisable(GL_DEPTH_TEST); //Blending mit funktioniert Z-Buffer liefert falsche
    glEnable(GL_BLEND);       //Ergebnisse, wenn die Polygone nicht sortiert sind,
                              //deshalb wird er abgeschaltet.
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

    //zeichnen
    glTranslatef(CircelCenter.x,CircelCenter.y,CircelCenter.z);
    if (SphereQuality.Position<20)or(SphereQuality2.Position<20) then
      glScalef(CircelRadius+2,CircelRadius+2,CircelRadius+2)
     else if (SphereQuality.Position<40)or(SphereQuality2.Position<40) then
      glScalef(CircelRadius+1,CircelRadius+1,CircelRadius+1)
     else glScalef(CircelRadius,CircelRadius,CircelRadius);
    glCallList(SphereList2);

    glDisable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
  end;

  //ausgeben
  glFinish;
  SwapBuffers(panelDC);

  time:=getTickCount-time+1; //benötigte Zeit

  frameRate.Caption:='(theoretische) Framerate: '+IntToStr(round(1000/time));//Zeit ausgeben:
end;

//Displaylist für eine Kugel aktualisieren
procedure TthreeDForm.recalculateSphere(const quality1,quality2:integer);
var tempQuad:GLUquadricObj;
begin
  tempQuad:=gluNewQuadric; //Quadric Object erzeugen

  glNewList(SphereList1,GL_COMPILE); //Listenberechnung starten (Kugeln)
  gluSphere(tempQuad,1,quality1,quality1); //Kugel in die Liste zeichen
  glEndList; //Listenberechnung beenden

  glNewList(SphereList2,GL_COMPILE); //Listenberechnung starten (Umkugel)
  gluSphere(tempQuad,1,quality2,quality2); //Kugel in die Liste zeichen
  glEndList; //Listenberechnung beenden

  gluDeleteQuadric(tempQuad); //Quadric Object löschen
end;

//Setzt die Projektionseinstellungen bei einer Fenstervergrößerung/kleinerung neu
procedure TthreeDForm.outputPanelResize(Sender: TObject);
begin
  //Projektionseinstellungen setzen
  //Ausgabebereich
  glViewport(0, 0, outputPanel.clientWidth, outputPanel.clientHeight);
  //Perspektive neuberechnen (Sichtwinkel 45°, Verzerrung durch nicht quadratische
  //Fenster werden ausgeglichen, und der Sichtbereich reicht von 1 bis 10000)
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0, outputPanel.clientWidth/ outputPanel.clientHeight, 1.0, 10000.0);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
end;

//Displayliste neuberechnen und neuzeichnen
procedure TthreeDForm.SphereQualityChange(Sender: TObject);
begin
  recalculateSphere(SphereQuality.Position,SphereQuality2.Position);
  sphereCount.Caption:='Anzahl der Kugeln: '+IntToStr(Spheres.countStatic);
  if renderCircel.Checked then
    polyCount.Caption:='Anzahl der Polygone: '+IntToStr(Spheres.countStatic*sqr(SphereQuality.Position)
                                                        +sqr(SphereQuality2.Position))
   else
    polyCount.Caption:='Anzahl der Polygone: '+IntToStr(Spheres.countStatic*sqr(SphereQuality.Position));
  render;
end;

//Lichtmodus an/ab stellen und neuzeichnen
procedure TthreeDForm.gourandShadingClick(Sender: TObject);
begin
  if gourandShading.Checked then glShadeModel(GL_SMOOTH)
  else glShadeModel(GL_FLAT);
  render;
end;

//Gradangaben überprüfen und neuzeichnen
procedure TthreeDForm.rotationYChange(Sender: TObject);
begin
  try
    if size.Value<0 then size.Value:=0
    else if rotationY.Value<0 then rotationY.Value:=rotationY.Value+360
    else if rotationY.Value>=360 then rotationY.Value:=rotationY.Value-360
    else if rotationX.Value<0 then rotationX.Value:=rotationX.Value+360
    else if rotationX.Value>=360 then rotationX.Value:=rotationX.Value-360
    else render;
  except
  end;
end;

//==============================================================================
//            Wichtige Methoden zum Abspeichern
//==============================================================================

//Datei auswählen
procedure TthreeDForm.selectFileClick(Sender: TObject);
begin
  if mainForm.SaveDialog1.Execute then  filename.Text:=mainForm.SaveDialog1.FileName;
end;

//Bild abspeichern
procedure TthreeDForm.saveClick(Sender: TObject);
var bitmap:tbitmap;
begin
  bitmap:=TBitmap.create ;
  try
    bitmap.Width:=outputPanel.Width;
    bitmap.Height:=outputPanel.Height;
    //Da das Panel seine Canvas-Eigenschatf auch, wenn sie versteckt ist, hat,
    //kann man auf sie zugreifen, wenn man eine entsprechend Typumwandlung macht
    bitmap.Canvas.CopyRect(rect(0,0,bitmap.Width,bitmap.Height),
                           TCanvasPanel(outputPanel).Canvas,
                           rect(0,0,bitmap.Width,bitmap.Height));
    bitmap.SaveToFile(filename.text);
  finally
    bitmap.free;
  end;
end;

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

//Jetzt beginnt wahrscheinlich eine Drehung/Skalierung mit der Maus, also werden
//die Werte gesichert
procedure TthreeDForm.outputPanelMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  mouseX:=x;
  mouseY:=y;
  startRotX:=rotationX.value;
  startRotY:=rotationY.value;
  startSize:=size.value;
end;

//Eine Drehung/Skalierung mit der Maus berechnen
procedure TthreeDForm.outputPanelMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if ssLeft in shift then begin
    //Die Kugeln werden genau um soviele Grad gedreht, wie die Maus an Pixeln
    //zurück gelegt hat
    rotationY.Value:=startRotY-(mouseX-x);
    rotationX.Value:=startRotX+(mouseY-y);
  end;
  if ssRight in shift then begin
    //Die Kugeln werden genau um soviel vergrößert, wie die Maus an Pixeln
    //auf der Y-Achse zurück gelegt hat
    size.Value:=startSize-(mouseY-Y);
  end;
end;

//Möglichstviele Kugeln sichtbar machen
procedure TthreeDForm.showAllClick(Sender: TObject);
begin
  //Um alle Kugeln zu zeigen, soll der Abstand vom Umkreismittelpunkt genau
  //so groß sein, das die Sichtbereitspyramide an der X/Y Koordinate des Umkreis-
  //mittelpunktes genau so hoch ist, wie der Durchmesser des Umkreises.
  //Der Winkel an der Pyramidenspitze ist 45° (=pi/4). Betrachtet man die Pyramide
  //von der Seite sieht man den oberen Tel als rechtwinklige Dreieck:
  {
     |\_____
     |      \_____        Strecke X
radi-|            \_____//
us:  |                  \_____
     |                        \_________
     X____________________________22.5°_X  Betrachter
     Umkreismittelpunkt       \\ Abstand
     }
  //Es gilt Abstand=cos(22.5°)*X, radius=sin(22.5°)*X
  //X = radius/sin(22.5°)
  //Abstand=radius*cos(22.5°)/sin(22.5°)
  //Abstand=radius/(sqrt(2)-1)
  size.Value:=round(circelradius/(sqrt(2)-1));
  render;
end;

//==============================================================================
//                           Sonstige Methoden
//==============================================================================
//OpenGL starten
procedure TthreeDForm.FormShow(Sender: TObject);
var pfd:TPixelFormatDescriptor;
    pixelFormat:integer;
    color:tcolor;
begin
  //-------------------OpenGL initialisieren--------------------------
  //Pixelformat wählen
  ZeroMemory(@pfd,sizeof(pfd));
  with pfd do begin
    //Standardeinstellungen:
    nSize:=sizeof(pfd);
    nVersion        := 1;
    dwFlags         := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;
    cDepthBits      := 16;                   // Genauigkeit des Depht-Buffers
  end;

  panelDC:=getdc(outputPanel.handle); //WinAPI Zeichenhandle besorgen
  pixelFormat:=ChoosePixelFormat(PanelDC,@pfd); //Passendes Pixelformat suchen
  if pixelFormat = 0 then begin
    showMessage('Leider konnte die 3D-Ausgabe nicht geladen werden, da das PixelFormat nicht unterstützt wird');
    ModalResult:=mrAbort;
    close.Click;
  end;

  if (not SetPixelFormat(paneldc,pixelFormat,@pfd))  then begin //dieses setzen
    showMessage('Leider konnte die 3D-Ausgabe nicht geladen werden, da das PixelFormat nicht unterstützt wird');
    ModalResult:=mrAbort;
    close.Click;
  end;

  //OpenGL laden
  context:=wglCreateContext(panelDC);
  if context=0 then begin
    showMessage('Leider konnte die 3D-Ausgabe nicht geladen werden, da OpenGL nicht gestartet werden konnte');
    ModalResult:=mrAbort;
    close.Click;
  end;

  wglMakeCurrent(panelDC,context); //Bekannt geben, dass der Context benutzt werden soll

  //Ausgabeeinstellungen
  glShadeModel(GL_SMOOTH); //Gourandshading
  glEnable(GL_DEPTH_TEST); //Tiefentest
  glDepthFunc(GL_LESS);
  glEnable(GL_NORMALIZE);  //Normalen bei Skalierung neu berechnen
  glEnable(GL_CULL_FACE);  //Cullen
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //Schöne Perspektivenkorrektur
  glEnable(GL_LIGHTING); //Es werde Licht
  glEnable(GL_LIGHT0);

  color:=colorManager.getColor(Color_3D_Background);
  glClearColor(GetRValue(color)/256, GetGValue(color)/256, GetBValue(color)/256, 0.5); //Hintergrundfarbe
  glClearDepth(1.0);

  outputPanelResize(nil); //Projektionseinstellungen setzen

  //Eine Kugel berechnen und in eine DisplayList speichern
  SphereList1:=glGenLists(2);
  SphereList2:=SphereList1+1;
  recalculateSphere(SphereQuality.Position,SphereQuality2.Position);
end;


//OpenGL schließen
procedure TthreeDForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  wglMakeCurrent(panelDC,0); //Context wird nicht mehr gebraucht
  wglDeleteContext(context); //wird gelöscht
  ReleaseDC(outputPanel.Handle,panelDC); //DC wird gelöscht
end;

//Standardinitialisierungen
procedure TthreeDForm.FormCreate(Sender: TObject);
begin
  //Kugelliste erzeugen und Umkreiskugel "berechnen"
  Spheres:=T3DSphereList.Create;
  with Spheres.createStaticSphere^ do begin
    pos:=makeVector(0,0,0);
    r:=SphereRadius;
  end;
  minX:=-SphereRadius;
  maxX:=SphereRadius;
  minY:=minX;
  maxY:=maxX;
  minZ:=minX;
  maxZ:=maxX;
  CircelCenter:=makeVector(0,0,0);
  CircelRadius:=SphereRadius;
end;

//Kugelliste freigeben
procedure TthreeDForm.FormDestroy(Sender: TObject);
begin
  Spheres.free;
end;


//Wireframemodus an/ab stellen und neuzeichnen
procedure TthreeDForm.wireframeClick(Sender: TObject);
begin
  if wireframe.Checked then glPolygonMode(gL_FRONT_AND_BACK,GL_LINE)
  else glPolygonMode(gL_FRONT_AND_BACK,GL_FILL);
  render;
end;

//Farbeinstellungdialog aufrufen
procedure TthreeDForm.colorOptionsClick(Sender: TObject);
var color:tcolor;
begin
  colorManager.showModal;
  color:=colorManager.getColor(Color_3D_Background);
  glClearColor(GetRValue(color)/256, GetGValue(color)/256, GetBValue(color)/256, 0.5); //Hintergrundfarbe
end;

//neuzeichnen
procedure TthreeDForm.Timer1Timer(Sender: TObject);
begin
  if Visible then  render;
end;
procedure TthreeDForm.renderCircelClick(Sender: TObject);
begin
  render;
end;

procedure TthreeDForm.closeClick(Sender: TObject);
begin
  tform(self).close;
end;

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



