// Iterated Function Systems (mit Bewegung und Farbe)
// Version mit Verwendung von Pointern
//
// Aufruf: ifs-farbe
//
// Klaus Kusche, 2015

#include <stdlib.h>
#include <time.h>
#include "sdlinterf.h"

// Struktur für einen einzelnen Punkt
typedef struct {
  double x, y;   // Koordinaten des Punktes
  int r, g, b;   // Farbe des Punktes
} punkt;

// Struktur für die Farbberechnung eines Punktes
typedef struct {
  // Wieviel Prozent der Farbe des vorigen Punktes bekommt der neue Punkt
  // in rot, grün und blau? 
  int proz_rot, proz_gruen, proz_blau;
  // ... und wieviel wird dann zu dieser Farbe noch dazu- oder weggezählt?
  int dazu_rot, dazu_gruen, dazu_blau;
} farbinfo;

// Struktur für einen Koeffizienten in den Gleichungen
typedef struct {
  double wert;     // der aktuelle Wert
  double min_wert; // unterer Grenzwert
  double max_wert; // oberer Grenzwert
  double schritt;  // Schrittweite
                   // ändert bei Überschreiten des Grenzwertes das Vorzeichen!
} koeff;

// Der inkludierte File muss Folgendes enthalten:
//
// * Ein #define für GL_ANZ (Anzahl der Gleichungssysteme =
//   Anzahl der Elemente in den Arrays "prozent" und "c")
//
// * Ein #define für ANZAHL (Anzahl der zu berechnenden Punkte)
//
// * Ein #define für ERSTER (Nummer des ersten zu zeichnenden Punktes)
//
// * Eine Deklaration für ein int-Array "prozent" (mit Init-Werten)
//   mit GL_ANZ vielen Prozentwerten,
//   mit denen die einzelnen Gleichungssysteme ausgewählt werden:
//   Die Gleichungen Nummer i wird verwendet,
//   wenn eine Zufallszahl 0...99 >= prozent[i-1] und < prozent[i] ist
//   Der letzte Wert in "prozent" muss 100 sein!!!
//
// * Eine Deklaration für ein zweidimensionales Array "c" von koeff-Werten
//   mit Initialisierung (alle Gleichungskoeffizienten):
//   Für jeden Koeffizienten 0-5 gibt es GL_ANZ viele verschiedene Werte
//   Erster Index: Welche Möglichkeit bzw. welches Gleichungssystem?
//   Größe des ersten Index: GL_ANZ
//   Zweiter Index: Welcher Koeffizient im Gleichungssystem?
//   Zweite Größe ist fix 6, d.h. Index-Werte 0-5:
//   x = c[0] * x + c[1] * y + c[2]
//   y = c[3] * x + c[4] * y + c[5]
//
// * Drei #define für ROT, GRUEN und BLAU als Zahl 0...255:
//   Die Farbwerte des ersten Punktes
//
// * Ein Array farbe von GL_ANZ vielen farbinfo-Strukturen:
//   Das i-te Element beschreibt,
//   wie die Farbe des neuen Punktes berechnet wird,
//   wenn die Position des Punktes aus dem i-ten Gleichungssystem kommt

// Dieses #include muss nach den typedef's sein,
// weil der Header die definierten Typen benötigt!
#include "farn.h"

// Koordinaten und Farben der Punkte, global wegen der Größe der Arrays
punkt p[ANZAHL];  

// Berechne die neue Farbe als "alt * proz / 100 + dazu"
// und begrenze das Ergebnis auf 0...255
static inline int neue_farbe(int alt, int proz, int dazu)
{
  int f = ((alt * proz) / 100) + dazu;
  if (f > 255) return 255;
  if (f < 0) return 0;
  return f;
}

int main(void)
{
  srand(time(NULL));

  sdlInit();

  for (;;) {
    // Grenzen der Koordinaten in beide Richtungen
    // beginne mit sehr großem positiven Wert für min
    // und sehr großem negativen Wert für max
    double xmin, xmax, ymin, ymax; 
    xmin = ymin = 1e300;
    xmax = ymax = -1e300;

    // Pointer auf den alten und den neuen Punkt im Array p
    punkt *pa, *pn;
    // Erster Punkt ist immer 0/0 und hat fixe Farbe
    pa = &(p[0]);
    pa->x = pa->y = 0;    
    pa->r = ROT; pa->g = GRUEN; pa->b = BLAU;
    
    // Die restlichen Punkte der Reihe nach berechnen
    for (pn = pa + 1; pn < &(p[ANZAHL]); pa = pn, ++pn) {
      // Gleichung Nummer n zufällig auswählen:
      // In welchen Bereich der Prozent-Angaben im Array prozent
      // fällt eine zufällige Zahl r zwischen 0 und 99 ?
      int n;
      int r = rand() % 100;
      for (n = 0; r >= prozent[n]; ++n) { }
      // Pointer auf die dazugehörigen Konstanten der n-ten Gleichung
      // Ein zweidimensionales Array mit nur einem Index
      // liefert einen Pointer auf die n-te Zeile des Arrays
      koeff *cp = c[n];
      // Pointer auf die dazugehörigen Farbwerte
      farbinfo *fp = &(farbe[n]);

      // neuen Punkt aus vorigem Punkt berechnen
      // cp ist ein Pointer auf eine Zeile eines zweidimensionalen Arrays,
      // lässt sich also wie ein eindimensionales Array verwenden
      pn->x = cp[0].wert * pa->x + cp[1].wert * pa->y + cp[2].wert;
      pn->y = cp[3].wert * pa->x + cp[4].wert * pa->y + cp[5].wert;
      // Farbe berechnen
      pn->r = neue_farbe(pa->r, fp->proz_rot, fp->dazu_rot);
      pn->g = neue_farbe(pa->g, fp->proz_gruen, fp->dazu_gruen);
      pn->b = neue_farbe(pa->b, fp->proz_blau, fp->dazu_blau);

      // die Bildgröße erweitern
      // wenn der neue Punkt außerhalb des bisherigen min und max liegt
      // nur für Punkte, die wirklich gezeichnet werden!
      if (pn >= &(p[ERSTER])) {
        if (pn->x < xmin) xmin = pn->x;
        if (pn->x > xmax) xmax = pn->x;
        if (pn->y < ymin) ymin = pn->y;
        if (pn->y > ymax) ymax = pn->y;
      }    
    }

    sdlSetBlack();
    // Punkte der Reihe nach zeichnen,
    // dabei Skalierung von double-Werten auf Pixel-Koordinaten im Fenster
    for (pn = &(p[ERSTER]); pn < &(p[ANZAHL]); ++pn) {
      sdlDrawPoint(((pn->x - xmin) / (xmax - xmin)) * (SDL_X_SIZE - 1),
                   (1 - (pn->y - ymin) / (ymax - ymin)) * (SDL_Y_SIZE - 1),
                   pn->r, pn->g, pn->b);
    }
    sdlUpdate();
    sdlMilliSleep(50);

    // Für das nächste Bild:
    // Alle Gleichungs-Koeffizienten ein bisschen verändern
    // Man kann ein zweidimensionales Array mit Pointern durchlaufen,
    // als ob es ein eindimensionales Array wäre
    //
    // Alternativ könnte man die beiden Schleifen mit i und j wie bisher lassen,
    // und in der inneren Schleife cp = &(c[i][j]); setzen
    for (koeff *cp = &(c[0][0]); cp < &(c[GL_ANZ][0]); ++cp) {
      cp->wert += cp->schritt;
      if ((cp->wert > cp->max_wert) ||
          (cp->wert < cp->min_wert)) {
        // Wert außerhalb der Grenzen
        // ==> Veränderungs-Richtung für das nächste Mal umdrehen
        cp->schritt = -cp->schritt;
      }
    }
  }
  
  sdlExit();

  exit(EXIT_SUCCESS);
}
