/*
 * Autor: Bernhard Emese
 * Demo für 2d 3d Clipping Algorithmus: Rechteckiges Source-
 * und Destinationclipping nach Cohen-Sutherland für Aztec-C
 * Version 5.2a. Compileraufruf:
 * cc cliptest.c -ps -f8 -c2
 * Wer über keinen Floatingpoint-Prozessor und keinen
 * MC68020/30-Prozessor verfügt, muß die Include-Datei
 * <libraries/mathffp.h> einbinden und übersetzen mit
 * cc cliptest.c -ps -ff
 */
#include <functions.h>
#include <stdlib.h>
#include <stdio.h>
#include <exec/types.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>

#define WIDTH  640
#define HEIGHT 512       // 512 für Interlace, sonst 256
#define XMin 20          // erste erlaubte Koordinate links
#define XMax (WIDTH-20)  // letzte erlaubte Koordinate rechts
#define YMin 20          // erste erlaubte Koordinate oben
#define YMax (HEIGHT-20) // letzte erlaubte Koordinate unten
#define OBEN   0x01      // Flags für 2-D-Clipping
#define UNTEN  0x02
#define LINKS  0x04
#define RECHTS 0x08
#define FALSE 0
#define TRUE  1
#define NULL 0L

struct RastPort *RPort;
LONG   x1,y1,x2,y2;     // Anfangs- und Endkoordinate der zu
                        // zeichnenden 2d Linie
SHORT  k1,k2;    // Kanten-Flags für Anfangs und Endpunkt

void ClipBltBitMap(struct BitMap *SrcMap,SHORT sx,
                   SHORT sy,struct BitMap *DstMap,
                   SHORT dx,SHORT dy,SHORT sizex,SHORT sizey,
                   SHORT minTerm,SHORT mask, SHORT *tempA,
                   SHORT sw,SHORT sh,SHORT dw,SHORT dh)
{
  /* Source-Clipping -- das auszuschneidende Rechteck kann in
   * allen Richtungen zu groß sein */
  if(sx>=sw || sy>=sh)
     return;   // gewünschter Ausschnitt existiert nicht

  if(sx<0) {
    /* wenn sx kleiner 0, dann sx=0 setzen und sizex
     * verkleinern */
    sizex+=sx; // sizex wird kleiner, da sx negativ
    dx-=sx;    // dx entsprechend nach rechts verschieben
    sx=0;
  }
  if(sy<0) {
    /* Y wird genau wie X behandelt: sy=0 setzen und sizey
     * verkleinern */
    sizey+=sy; // sizey wird kleiner, da sy negativ
    dy-=sy;    // dy entsprechend nach unten verschieben
    sy=0;
  }

  /* Jetzt kann der Ausschnitt nur noch rechts und/oder unten
   * über den Quellbereich hinausragen */

  if(sizex > sw-sx)
    sizex=sw-sx;

  if(sizey > sh-sy)
    sizey=sh-sy;

  /* Destination-Clipping: Das zu zeichnende Recteck kann in
   * allen Richtungen zu groß sein. Die Vorgehensweise ist
   * zu dem des Source-Clippings identisch */

  if(dx>=dw || dy>=dh)
    return;   // Zeichenbereich liegt komplett außerhalb

  if(dx<0) {
     /* Wenn das Ziel zu weit links leigt, dann dx=0 setzen und
      * sizex verkleinern */
    sizex+=dx;
    sx-=dx;    // sx wird größer
    dx=0;      // am linken Rand wird geclippt
  }
  if(dy<0) {
    sizey+=dy;  // sizey wird kleiner
    sy-=dy;     // sy wird größer
    dy=0;       // am oberen Rand wird geclippt
  }

  /* Ragt der Ausschnitt noch über den linken und/oder unteren
   * Bildschirmrand ? */

  if(sizex > dw-dx)
    sizex=dw-dx;     // sizex verkleinern

  if(sizey > dh-dy)
    sizey=dh-dy;     // sizey verkleinern

  /* Jetzt kann durch Abschneiden an allen Ecken und Enden von
   * sizex und sizey evtl. nichts mehr übrig sein */

  if(sizex>0 && sizey>0)
    BltBitMap(SrcMap,sx,sy,DstMap,dx,dy,
              sizex,sizey,0xc0L,0xffL,NULL);
}

SHORT CheckXY (LONG x,LONG y) {
  SHORT k;

  if(y<YMin)        // Liegt Y Koordinate unter Minimum ?
    k=UNTEN;
  else if(y>YMax)   // Wenn nicht, kann sie nur Oben liegen
    k=OBEN;
  else
    k=0;            // Wenn nicht Oben, dann im Bild

  if(x<XMin)           // Liegt X-Koordinate unter Minimum ?
    return(k|=LINKS);  // Return, da X nicht gleichzeitig
                       // Links und Rechts liegen kann
  else if(x>XMax)      // Wenn nicht, dann Rechts
    return(k|=RECHTS);
  return(k);           // Oder in Bildmitte
}

void MoveClip (LONG x,LONG y) {
  // setze Flags je nach Lage des Punkts in O, U, R, oder L
  k1=CheckXY(x1=x,y1=y);
}

SHORT DrawClip(LONG x2c,LONG y2c) {
  LONG  x1c,y1c,dx,dy;
  SHORT k;

  k2=CheckXY(x2=x2c,y2=y2c);   // setze Flags

  x1c=x1; y1c=y1; k=k1;

  /* x1c,y1c mit letzter nicht-geclippter Koordinate
   * x1,y1 laden */

  x1=x2c; y1=y2c; k1=k2;

  // ungeclippte neue Koordinate für nächstes Draw abspeichern

  if((k & k2)!=0)
    return(FALSE);  // Beide Punkte liegen auf gleicher Seite
                    // außerhalb
  dx=x2c-x1c;
  dy=y2c-y1c;
  if(k) {            // Liegt P1 irgendwo außerhalb ?
    if(k & LINKS) {  // wenn links, dann
      y1c+=(XMin-x1c)*dy/dx;   // dx kann hier nie 0 sein
      if(y1c<YMin || y1c>YMax)
        return(FALSE);  // Linie kreuzt außerhalb die Felder
      x1c=XMin;
    } else if(k & RECHTS) {
      y1c+=(XMax-x1c)*dy/dx;
      if(y1c<YMin || y1c>YMax)
        return(FALSE);   // Linie kreuzt außerhalb die Felder
      x1c=XMax;
    }
    if(k & UNTEN) {
      x1c+=(YMin-y1c)*dx/dy;  // dy kann hier nie 0 sein
      if(x1c<XMin || x1c>XMax)
        return(FALSE);   // Linie kreuzt außerhalb die Felder
      y1c=YMin;
    } else if(k & OBEN) {
      x1c+=(YMax-y1c)*dx/dy;
      if(x1c<XMin || x1c>XMax)
        return(FALSE);   // Linie kreuzt außerhalb die Felder
      y1c=YMax;
    }
  }
  if(k2) {               // liegt P2 irgendwo außerhalb
    if(k2 & LINKS) {
      y2c+=(XMin-x2c)*dy/dx;   // dx kann hier nie 0 sein
      if(y2c<YMin || y2c>YMax)
        return(FALSE);   // Linie kreuzt außerhalb die Felder
      x2c=XMin;
    } else if(k2 & RECHTS) {
      y2c+=(XMax-x2c)*dy/dx;
      if(y2c<YMin || y2c>YMax)
        return(FALSE);   // Linie kreuzt außerhalb die Felder
      x2c=XMax;
    }
    if(k2 & UNTEN) {
      x2c+=(YMin-y2c)*dx/dy;   // dy kann hier nie 0 sein */
      if(x2c<XMin || x2c>XMax)
        return(FALSE);   // Linie kreuzt außerhalb die Felder
      y2c=YMin;
    } else if(k2 & OBEN) {
      x2c+=(YMax-y2c)*dx/dy;
      if(x2c<XMin || x2c>XMax)
        return(FALSE);   // Linie kreuzt außerhalb die Felder
      y2c=YMax;
    }
  }
  Move(RPort,x1c,y1c);
  Draw(RPort,x2c,y2c);
  return(TRUE);
}

// Anfangs- und Endpunkte der 3-D-Linie
LONG X1,Y1,Z1,X2,Y2,Z2;
LONG   PRF;
SHORT  CE;       // Abstand der ClipEbene vom Brennpunkt
SHORT  BX,BY,BZ; // Betrachterstandpunkt (Kamera)

FLOAT ZeroMatrix[4][4] = {
  {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} };
FLOAT Matrix[4][4];

#define CONVERTX(x,z)   (PRF*(x)/((z)+PRF)+WIDTH/2)
#define CONVERTY(y,z)   (PRF*(y)/((z)+PRF)+HEIGHT/2)

void Transform3d (FLOAT M[4][4],LONG *X,LONG *Y,LONG *Z) {
  LONG XU,YU,ZU;

  XU=*X;     // zu drehende verschiebende Koordinate
             // zwischenspeichern
  YU=*Y;
  ZU=*Z;

  /* beliebige Tranformation ausführen */
  *X=M[0][0]*XU+M[1][0]*YU+M[2][0]*ZU+M[3][0];
  *Y=M[0][1]*XU+M[1][1]*YU+M[2][1]*ZU+M[3][1];

  /* die Art der Drehung, Verschiebung etc. ist vom
   * Inhalt der Matrix abhängig */
  *Z=M[0][2]*XU+M[1][2]*YU+M[2][2]*ZU+M[3][2];
}

void Move3dClip (LONG X,LONG Y,LONG Z) {
  Transform3d(Matrix,&X,&Y,&Z);

  X1=X-BX;   // Betrachterstandpunkt von Koordinate abziehen
  Y1=Y-BY;
  Z1=Z-BZ;
}

void Draw3dClip (LONG X,LONG Y,LONG Z) {
  FLOAT RQ;

  Transform3d(Matrix,&X,&Y,&Z);

  X=X-BX;     // Betrachterstandpunkt von Koordinate abziehen
  Y=Y-BY;
  Z=Z-BZ;

  if(Z1>=CE) {        // alte Koordinate hinter Clipebene ?
    MoveClip(CONVERTX(X1,Z1),CONVERTY(Y1,Z1));
    if(Z>=CE)
      // neue Koordinate auch: kein Clipping erforderlich
      DrawClip(CONVERTX(X,Z),CONVERTY(Y,Z));
    else {
      // Zielkoordinate clippen, Streckenverhältnis berechnen
      RQ=(FLOAT)(CE-Z1)/(Z-Z1);
      X1+=(X-X1)*RQ;      // X proportional verkürzen
      Y1+=(Y-Y1)*RQ;      // Y proportional verkürzen
      Z1=CE;              // Z auf Clipebene setzen
      DrawClip(CONVERTX(X1,Z1),CONVERTY(Y1,Z1));  // Zeichnen
    }
  } else             // alte Koordinate vor Clipebene
  if(Z>=CE) {   // neue Koordinate hinter Clipebene --> clippen
    RQ=(FLOAT)(CE-Z1)/(Z-Z1);
    X1+=(X-X1)*RQ;
    Y1+=(Y-Y1)*RQ;
    Z1=CE;
    MoveClip(CONVERTX(X1,Z1),CONVERTY(Y1,Z1));
    DrawClip(CONVERTX(X,Z)  ,CONVERTY(Y,Z));
  }
  X1=X; // neuen Punkt ungeclippt für nächstes Draw3d speichern
  Y1=Y;
  Z1=Z;
}

#ifndef PI
#define PI  ((FLOAT) 3.141592653589793)
#endif

void Rotate(FLOAT M[4][4],FLOAT A,FLOAT B,FLOAT C)
{
  FLOAT MSIN,SIN,COS,o;

  movmem((char *)ZeroMatrix,
         (char *)Matrix,(int)4*4*sizeof(FLOAT));

  SIN = sin(A=A*PI/180); MSIN = -SIN;
  COS = cos(A);
  o = M[0][1]*COS + M[0][2]*SIN;
  M[0][2] = M[0][1]*MSIN + M[0][2]*COS;
  M[0][1] = o;
  o = M[1][1]*COS + M[1][2]*SIN;
  M[1][2] = M[1][1]*MSIN + M[1][2]*COS;
  M[1][1] = o;
  o = M[2][1]*COS + M[2][2]*SIN;
  M[2][2] = M[2][1]*MSIN + M[2][2]*COS;
  M[2][1] = o;
  o = M[3][1]*COS + M[3][2]*SIN;
  M[3][2] = M[3][1]*MSIN + M[3][2]*COS;
  M[3][1] = o;

  SIN = sin(B=B*PI/180); MSIN = -SIN;
  COS = cos(B);
  o = M[0][0]*COS + M[0][2]*MSIN;
  M[0][2] = M[0][0]*SIN + M[0][2]*COS;
  M[0][0] = o;
  o = M[1][0]*COS + M[1][2]*MSIN;
  M[1][2] = M[1][0]*SIN + M[1][2]*COS;
  M[1][0] = o;
  o = M[2][0]*COS + M[2][2]*MSIN;
  M[2][2] = M[2][0]*SIN + M[2][2]*COS;
  M[2][0] = o;
  o = M[3][0]*COS + M[3][2]*MSIN;
  M[3][2] = M[3][0]*SIN + M[3][2]*COS;
  M[3][0] = o;

  SIN = sin(C=C*PI/180); MSIN = -SIN;
  COS = cos(C);
  o = M[0][0]*COS + M[0][1]*SIN;
  M[0][1] = M[0][0]*MSIN + M[0][1]*COS;
  M[0][0] = o;
  o = M[1][0]*COS + M[1][1]*SIN;
  M[1][1] = M[1][0]*MSIN + M[1][1]*COS;
  M[1][0] = o;
  o = M[2][0]*COS + M[2][1]*SIN;
  M[2][1] = M[2][0]*MSIN + M[2][1]*COS;
  M[2][0] = o;
  o = M[3][0]*COS + M[3][1]*SIN;
  M[3][1] = M[3][0]*MSIN + M[3][1]*COS;
  M[3][0] = o;
}

struct Library *IntuitionBase,*GfxBase;
struct Window  *Window;
static struct NewWindow nw = {
  0,0, WIDTH,HEIGHT, 0,1, 0, ACTIVATE, NULL, NULL,
  (UBYTE *)"2d  3d Clipping", NULL, NULL, 0,0,WIDTH,HEIGHT,
  WBENCHSCREEN,
};

/*
 * Zeichnet alle Kanten eines Drahtgitterwürfels und
 * löscht vorher den Hintergrund
 */
void Cube() {
  SetAPen(RPort,0);
  RectFill(RPort,XMin,YMin,XMax,YMax);
  SetAPen(RPort,1);
  Move3dClip(-250,+250,-250); Draw3dClip(+250,+250,-250);
  Draw3dClip(+250,-250,-250); Draw3dClip(-250,-250,-250);
  Draw3dClip(-250,+250,-250); Draw3dClip(-250,+250,+250);
  Draw3dClip(+250,+250,+250); Draw3dClip(+250,-250,+250);
  Draw3dClip(-250,-250,+250); Draw3dClip(-250,+250,+250);
  Move3dClip(+250,+250,-250); Draw3dClip(+250,+250,+250);
  Move3dClip(+250,-250,-250); Draw3dClip(+250,-250,+250);
  Move3dClip(-250,-250,-250); Draw3dClip(-250,-250,+250);
  Delay(2);
}

Movement(SHORT i) {
  if(i<100) return(-i);     // fährt zuerst nach links
  if(i<300) return(-200+i); // dann nach rechts
  return(400-i);            // dann wieder auf den Anfangspunkt
}

main() {
  SHORT i,j;

  IntuitionBase=OpenLibrary((UBYTE *)"intuition.library",NULL);
  GfxBase=OpenLibrary((UBYTE *)"graphics.library",NULL);

  Window=OpenWindow(&nw);  // öffnet Workbench-Fenster

  RPort=Window->RPort;

  SetAPen(RPort,2);

  // Zeichnet eine Umrahmung, die nicht überschritten wird.
  Move(RPort,XMin-1,YMin-1);
  Draw(RPort,XMax+1,YMin-1);
  Draw(RPort,XMax+1,YMax+1);
  Draw(RPort,XMin-1,YMax+1);
  Draw(RPort,XMin-1,YMin-1);

  PRF=1000; // Werte für 3d Clipping
  CE= 740;  // Clipebene ist 10 Einheiten vor dem Objekt
  BZ=-1000; // Betrachter ist 1000 Einheiten entfernt

  for(i=0;i<=400;i+=4) {
    // Würfeldrehungen in X,Y,Z
    Rotate(Matrix,Movement(i),Movement(i),Movement(i));
    Cube();
  }
  Delay(50);

  for(i=0;i<=400;i+=4) {
    BX=Movement(i)*10;
    // Würfel rollt nach rechts und links
    Rotate(Matrix,0,0,Movement(i));
    Cube();
  }
  Delay(50);

  for(i=0;i<=400;i+=4) {
    BY=Movement(i)*10;
    // Würfel rollt nach oben und unten
    Rotate(Matrix,-Movement(i),0,0);
    Cube();
  }
  Delay(50);

  for(i=0;i<=400;i+=4) {
    BZ=Movement(i)*10-1000;
    // Würfel rollt nach vorn und hinten
    Rotate(Matrix,-Movement(i),0,0);
    Cube();
  }
  Delay(50);

  CloseWindow(Window);
  CloseLibrary(GfxBase);
  CloseLibrary(IntuitionBase);
}
