/* main.cpp  Spiel1  letzte Aenderungen: 24.5.2021  29.4.2022
*/
const char *VERSION="0.14";
#define NEUE_VERSION  //bei groesseren Aenderungen, zum Testen
//#define ZEITMESSUNG   //Zeitmessungen mit stoppuhr_reset, stoppuhr_read
//#define KRAFT_VERSION  //geht bisher nicht richtig
//#define SHADERS_120 //zum Ausprobieren von alter Shader-Version
//#define DRAHTGITTER_TEST  //zur Fehlersuche: Drahtgitter statt Flaechen zeichnen
//#define FULLSCREEN  //ohne weitere Flag-Angabe in Fullscreen starten
//#define OHNE_WASSER //Test ohne Meer und Teiche
#define HELLERE_SONNE
//#define LEUCHTTURM_LICHT //drehendes Licht auf dem Leuchtturm

#ifndef OHNE_WASSER
#define FIGUR_SCHWIMMEND //auskommentieren wenn es nicht auf Schwimmanimation wechseln soll
#endif
#define MEERWEITE 50.0f  //schoener mit 100.0f braucht aber viel Rechenzeit
#define MINIMALE_MEERWEITE 30.0f
/*
die folgenden zwei Pakete muessen installiert sein:
 libsdl2-dev
 libglew-dev

History:
16.1.2021   Erstellung aus OpenGLTutorial-031
17.4.2021   weitere Eintraege siehe Liesmich.txt
*/

/****** Sachen zum Debuggen: ******/
// Version der Shader auf 3.3 gesetzt.
// Wenn Fehler mit Version 3.3 passiert, geht es eventuell wenn so gestartet:
// MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330 ./main
// oder die folgenden 2 Zeilen definiert:
//#define MESA_GL_VERSION_OVERRIDE   "3.3"
//#define MESA_GLSL_VERSION_OVERRIDE "330"

//#define _DEBUG  //bei Bedarf im makefile definiert
//#define PERFORM_TEST

/* Debuggen auf Linux:
gdb ./main
(gdb) run
*/

/****** Fenstergroesse einstellen: ******/
static int fensterbreite=1000; //wird weiter unten noch richtig gesetzt
static int fensterhoehe=800;
//#define STARTYPOSITION 50.0f //Starthoehe, anpassen wenn Landschaft oder Startposition geaendert
float startposx= 0.01f; //-100 = ganz im Westen, 100=ganz im Osten
float startposz= 0.01f; //-100 = ganz im Norden, 100=ganz im Sueden
float startposy= 50.0f;//Starthoehe
int startpos_auto=0; //TODO
float liniendicke=1.0; //wird je nach fensterbreite noch angepasst

#include <iostream>
#include <fstream>
#include <vector>
#include <math.h>
#define GLEW_STATIC
#include <GL/glew.h>
#define SDL_MAIN_HANDLED

#define STB_IMAGE_IMPLEMENTATION
#include "libs/stb_image.h"
#undef STB_IMAGE_IMPLEMENTATION

#ifdef _WIN32
#include <SDL.h>
#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "glew32s.lib")
#pragma comment(lib, "opengl32.lib")
#else
#include <SDL2/SDL.h>
#endif

#ifdef _DEBUG

void _GLGetError(const char* file, int line, const char* call) {
	while(GLenum error = glGetError()) {
		std::cout << "[OpenGL Error] " << glewGetErrorString(error) << " in " << file << ":" << line << " Call: " << call << std::endl;
	}
}

#define GLCALL(call) call; _GLGetError(__FILE__, __LINE__, #call)

#else

#define GLCALL(call) call

#endif

#include "defines.h"
#include "vertex_buffer.h"
#include "index_buffer.h"
#include "shader.h"
#include "my_camera.h"
#include "mesh.h"
#include "kleinkram.h"
#include "texturen.h"
//#include "landschaften.h"  //weiter unten gemacht damit argflag[] verwendbar

/******* von Spectplorer, aber hier nicht wirklich gebraucht: ********/
char rasterdateiname[400];
int tracksmooth=0, rastersmooth=0;
float gpxtrackwidth=0, xyztrackwidth=0;
float xscalfaktor=0, yscalfaktor=0; //fuer Umrechnung User-Koordinaten nach OpenGL-Koordinaten
float hscal=0, zoffset=0; //(z-User-Koordinate --> y-OpenGL-Koordinate)
int logflags=0; //in usersettings setzbar
float hmax=50.0; //mit Option -a setzbar
float gpxhscal=0; //als Alternative zu hmax
float gpx_gipfelpos[3]={0,0,0};//hoechster Punkt von einem GPX-Track, oder auch xyz-Datei
float gpx_tiefpunkt[3]={0,0,0};//tiefster Punkt
float gpx_startpos[3]={0,0,0};//Start von GPX-Track
float gpx_zielpos[3]={0,0,0};//Ziel von GPX-Track
char kartenname[400];//TODO
int kflag=0; //TODO Kartenflag zum Einlesen von kartenname
/******* :von Spectplorer, aber hier nicht wirklich gebraucht ********/

#ifdef _DEBUG
void openGLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
{
 if(severity==GL_DEBUG_SEVERITY_HIGH)        {std::cout << "[OpenGL Error] " << message << std::endl;}
 else if(severity==GL_DEBUG_SEVERITY_MEDIUM) {std::cout << "[OpenGL Warning] " << message << std::endl;}
 //else                                        {std::cout << "[OpenGL Message] " << message << std::endl;}
}
#endif

void getmaxsize(int *breite, int *hoehe)
{
 //SDL_VideoInit(NULL);
 SDL_DisplayMode dm;
 int displayIndex=0;
 if(SDL_GetDesktopDisplayMode(displayIndex,&dm)!=0)
  {fprintf(stderr,"Error in getmaxsize(): %s\n",SDL_GetError());}
 *breite = dm.w;
 *hoehe  = dm.h;
}

static char argflag[128];
void setargflags(const char *s)
{
 int c;
 while((c= *s++)!=0)
  {if(c>='a' && c<='z')  c -= 'a'-'A';
   argflag[c&127]=1;
   if(c=='X' && (*s=='-' || isdigit(*s))) {sscanf(s,"%f",&startposx);}
   else if(c=='Z' && (*s=='-' || isdigit(*s))) {sscanf(s,"%f",&startposz);}
   else if(c=='Y' && (*s=='-' || isdigit(*s))) {sscanf(s,"%f",&startposy);}
  }
}

#include "landschaften.h"

/****************************** Stoppuhr: ******************************/
struct {uint64 einheit,startzeit,endzeit;} stoppuhrstruct;

void stoppuhr_reset()
{
 stoppuhrstruct.einheit = SDL_GetPerformanceFrequency();
 stoppuhrstruct.startzeit = SDL_GetPerformanceCounter();
}

int stoppuhr_read() //Zeit seit letztem stoppuhr_reset() in usec
{
 stoppuhrstruct.endzeit = SDL_GetPerformanceCounter();
 int usec = (stoppuhrstruct.endzeit - stoppuhrstruct.startzeit)*1000000/stoppuhrstruct.einheit;
 return usec;
}

void zeit_print(int usec)
{
 if(usec>=500000)
      printf("%d.%03d sec\n",usec/1000000,(usec/1000)%1000);
 else printf("%d.%03d ms\n",usec/1000,usec%1000);
}
/**************************** Ende Stoppuhr ****************************/

/*********** Vordeklarationen:  ***********/
void matrizen_an_GPU_senden(MyCamera* camera, glm::mat4 modelMatrix, Shader* shader);
void alpha_an_GPU_senden(float32 alpha, Shader* shader);
void read_settings(FILE *fp,float *fovy, float *startdist, float *lat,float *lon,
		   int *tag,int *monat,int *jahr,int *minuten,float *zeitfaktor);
glm::vec3 jahresposition(Vec3 sunDirection00, float phi, int tag, int monat);

/************************* Sammelobjekte *************************/
class Sammelorte {
public:
 int anzahlHerze=0, anzahlRinge=0, anzahlAndere=0;
 int symbolsize[2], symbolsizeBig[2];
 int ifontabstand=16; //wird mit init noch richtig gesetzt
 float herzpos[2]; //Position des ersten Herzes auf dem Status-Screen
 float herzabstand, symbolabstand, symbolabstandBig;
 float ringpos[2]; //Position des Hilfe-Rings auf dem Status-Screen
 float untenlinks[2];
 float untenrechts[2];
 void init();
 float punktepos[2];
 float posprintpos[2];
};
Sammelorte orte;

class Spielstand {
public:
 uint32 punktestand=0;
 uint32 numGesammelt;
 std::vector<uint32> typGesammelt;
 std::vector<uint32> anzGesammelt;
 //TODO
 void gesammelt(uint32 typ) {
  for(uint32 i=0;i<numGesammelt;i++)
   if(typ==typGesammelt[i])
    {
     anzGesammelt[i]++; return;
    }
  typGesammelt.push_back(typ);
  anzGesammelt.push_back(1);
 }
};
Spielstand spielstand;

uint32 sonne_id=0; //provisorisch

//#include "gpxread.h"
#include "objekte.h"
#include "screen.h"
#include "modelle_einlesen.h"

Objekte sammelobjekte;
Objekte weltobjekte;
#ifndef OHNE_WASSER
Objekte wasserobjekte;
#endif
std::vector<Animobjekt> animiertefiguren; //TODO: z.B. Raupe, Frosch, Leute, ...
uint32 numAnimiertefiguren=0;

#include "physik.h"
//#include "leuchtturmlicht.h"

Screen statusScreen;
#define ID_WELTANSICHT 1
#define ID_STATUSSCREEN 2

// Funktionen die von myfonts.cc benoetigt werden:
void fillbox(double x1,double y1,double x2,double y2)
{
 statusScreen.fillbox((float)x1,(float)y1,(float)x2,(float)y2);
}
void tek_punkt(double x,double y)
{
 statusScreen.drawPoint((float)x,(float)y);
}
void koordpix2user(int ix,int iy, double *x, double *y)
{
 float fx,fy;
 statusScreen.pixel2welt(ix,iy,&fx,&fy);
 *x=fx;
 *y=fy;
}
void rgbcolor(int r, int g, int b)
{
 statusScreen.rgbcolor(r,g,b);
}
// :Funktionen die von myfonts.cc benoetigt werden.

void Sammelorte::init()
{
 int maxbreite,maxhoehe;
 getmaxsize(&maxbreite, &maxhoehe);
 symbolsize[0] = symbolsize[1] = (maxhoehe+15)/30;
 symbolsizeBig[0] = symbolsizeBig[1] = (maxhoehe+10)/20;
 if(argflag['V']) printf("Symbolgroessen: %d (normal), %d (gross)\n",symbolsize[0],symbolsizeBig[0]);//test
 //Abstand der Symbole berechnen:
 int iabstand=symbolsize[0]*3/2;
 statusScreen.pixel2welt(iabstand, &herzabstand);
 symbolabstand=herzabstand;
 symbolabstandBig = symbolabstand*symbolsizeBig[0]/symbolsize[0];
 //Positionen auf dem Screen:
 statusScreen.pixel2welt(symbolsize[0], symbolsize[1], &herzpos[0], &herzpos[1]); //oben links
 statusScreen.pixel2welt(fensterbreite-symbolsize[0], symbolsize[1],
			 &ringpos[0], &ringpos[1]); //oben rechts
 statusScreen.pixel2welt(symbolsize[0], fensterhoehe-symbolsize[1], &untenlinks[0], &untenlinks[1]);
 statusScreen.pixel2welt(fensterbreite-symbolsize[0], fensterhoehe-symbolsize[1],
			 &untenrechts[0], &untenrechts[1]);
 ifontabstand = ((maxhoehe>1200)? 32 : 16) * 3/2;
 statusScreen.pixel2welt(fensterbreite/2, ifontabstand,
			 &punktepos[0], &punktepos[1]); //oben Mitte
 statusScreen.pixel2welt(fensterbreite/2, fensterhoehe-ifontabstand/2,
			 &posprintpos[0], &posprintpos[1]); //unten Mitte
}

/*
bool fastgleich(glm::vec3 a, glm::vec3 b, float d)
{
 float xdif=a.x-b.x; if(xdif<0.0f) xdif = -xdif;
 float ydif=a.y-b.y; if(ydif<0.0f) ydif = -ydif;
 float zdif=a.z-b.z; if(zdif<0.0f) zdif = -zdif;
 return (xdif<d && ydif<d && zdif<d);
}
*/

bool ist_bei_objekt(glm::vec3 pos, uint32 *modelid, uint32 *klonid)
{
 float kuerzester_abstand = 1000.0f;
 int mini=0, mink=0;
 for(uint32 i=0;i<sammelobjekte.numModels;i++)
  if(sammelobjekte.modeldatas[i]->art==ART_SAMMELOBJEKT)
  {
   Klonlist* list=sammelobjekte.klonlists[i];
   for(uint32 k=0;k<list->numKlones;k++)
    if(list->klones[k].screenId==1)
     {
      float abstand = vec3abs(pos - list->klones[k].position);
      if(abstand < kuerzester_abstand)
       {kuerzester_abstand = abstand; mini=i; mink=k;}
     }
  }
 //if(kuerzester_abstand <= 0.6) //TODO: ev. 0.75 oder 1.0 Meter? oder als Parameter?
 if(kuerzester_abstand <= 1.0)
  {
   *modelid = mini; //id's fuer Objekt das am naechsten ist, zurueckgeben.
   *klonid = mink;
   return true;
  }
 return false;
}

static int posprint_flag=0;

void hilfeseite_zeichnen(int nr)
{
 statusScreen.clearDrawings();
#ifdef HELLERE_SONNE
 statusScreen.rgbcolor(50,255,50); //Gruene Schrift
#else
 statusScreen.rgbcolor(100,255,100); //Gruene Schrift
#endif
 char txt[200];
 sprintf(txt,"%d",spielstand.punktestand);
 float x=orte.punktepos[0], y=orte.punktepos[1];
 aktfont->schrift(x, y, txt, 0, false);
 if(nr==1)
  {
   int yabstand=orte.ifontabstand*2;
   int ix=200, iy=100+yabstand;
   rgbcolor(255,255,255);//weisse Schrift
   int h=aktfont->get_ischrift_hoehe(), b=aktfont->get_ischrift_breite();
   aktfont->itextsize(b*2,h*2); //doppelte Schriftgroesse
   aktfont->ischrift(ix,iy,"Hilfeseite:");
   ix=300; iy += yabstand;
   aktfont->ischrift(ix,iy,"Klick auf Ring: Hilfe ein/aus");  iy += yabstand;
   aktfont->ischrift(ix,iy,"Tasten: w,a,s,d  Figur bewegen");  iy += yabstand;
   ix += 8*2*b;
   aktfont->ischrift(ix,iy,"Space    Schneller");  iy += yabstand;
   aktfont->ischrift(ix,iy,"x        Huepfen");  iy += yabstand;
   aktfont->ischrift(ix,iy,"p        Position anzeigen");  iy += yabstand;
   aktfont->ischrift(ix,iy,"ESC    Maus ein/aus");  iy += yabstand;
   aktfont->ischrift(ix,iy,"Alt Q  Spiel beenden");  iy += yabstand;
   ix -= 8*2*b; iy += yabstand;
   aktfont->ischrift(ix,iy,"Benutzereinstellungen: usersettings.txt");  iy += yabstand;
   aktfont->itextsize(b,h); //Schriftgroesse wieder auf vorherige setzen
  }
 if(posprint_flag)
  {
   float x,y,z;
   float xu=LINK.userpos.x, zu=LINK.userpos.z, yu=LINK.userpos.y;
   rgbcolor(0, 0, 0);//schwarze Schrift
   sprintf(txt,"X:%g Y:%g Z:%g",xu,yu,zu);
   int n=strlen(txt)/2;
   x=orte.posprintpos[0] - n*aktfont->schrift_breite;
   y=orte.posprintpos[1];
   aktfont->schrift(x, y, txt, 0, false);
  }
 statusScreen.flush();
}

static int hilfeflag=0;

void hilfe_aus() {hilfeseite_zeichnen(hilfeflag=0);}

uint32 objekt_sammeln(uint32 modelid, uint32 klonid)
{
 //Objekt (z.B. Herz) auf Status-Screen verschieben:
 uint32 typ=sammelobjekte.moveToScreen(modelid, klonid, &statusScreen);
 //printf("Objekt modelid=%d klonid=%d auf statusScreen verschoben\n",modelid,klonid);//test
 
 spielstand.punktestand++; //TODO
 spielstand.gesammelt(typ);
 hilfeseite_zeichnen(hilfeflag=0);
  
 /* Tests: zeichnen auf dem Status-Screen * /
 statusScreen.rgbcolor(255,255,0); //test: Gelbes Quadrat
 //statusScreen.ifillbox(fensterbreite-100, 100, fensterbreite-200, 200); //test: ein Viereck zeichnen
 printf("Testpunkt: gelbes Viereck zeichnen\n");//test
 statusScreen.fillbox(-0.01, -0.01, 0.01, 0.01); //test: ein Viereck zeichnen
 statusScreen.rgbcolor(0,255,0); //test: Gruene Vierecke
 for(int i=0;i<5;i++)
    {
     int x1=i*25+1, y1=32+1, bux=24, buy=32; //Viereck der Groesse eines 24x32 Buchstabens
     statusScreen.ifillbox(x1, y1, x1+bux, y1-buy); //test: Viereck zeichnen
    }
 statusScreen.flush();
 / *  */
 return typ;
}

/************************* Hauptprogramm *************************/
class LaufendesMittel {
 float summe;
 int ns,nsmax;
public:
 LaufendesMittel(int n) {summe=0; nsmax=n; n=1;}
 void add(float y) {
  if(ns==nsmax) {summe -= summe/ns; ns--;}
  summe += y; ns++;
 }
 float get() {return summe/ns;}
};
LaufendesMittel y_laufendes_mittel(60); //jeweils ueber 60 Punkte mitteln


int main(int argc, char** argv)
{
#ifdef   MESA_GL_VERSION_OVERRIDE
 setenv("MESA_GL_VERSION_OVERRIDE",MESA_GL_VERSION_OVERRIDE,1);
 printf("MESA_GL_VERSION_OVERRIDE gesetzt\n");
#endif
#ifdef   MESA_GLSL_VERSION_OVERRIDE
 setenv("MESA_GLSL_VERSION_OVERRIDE",MESA_GLSL_VERSION_OVERRIDE,1);
 printf("MESA_GLSL_VERSION_OVERRIDE gesetzt\n");
#endif
 
 printf("Spiel1  main.cpp  Version %s\n",VERSION);
 char fenstertitel[200];
 sprintf(fenstertitel,"Spiel1  Version %s",VERSION);
 
 char weltname[400]; weltname[0]=0;
 char figurname[400]; figurname[0]=0;
 char objektename[400]; objektename[0]=0;
 char wasserobjektename[400]="models/wasserobjekte.txt";
 int argj=0;
 for(int i=1;i<argc;i++)
  {
   int c;
   if((c= *argv[i])=='-' || c=='?') setargflags(argv[i]);
   else if(++argj==1) mystrncpy(weltname,argv[i],400);
   else if(argj==2) mystrncpy(figurname,argv[i],400);
   else if(argj==3) mystrncpy(objektename,argv[i],400);
  }
 if(weltname[0]==0) mystrncpy(weltname,"models/welt.txt",400);
 if(figurname[0]==0) mystrncpy(figurname,"models/figuranim.txt",400);
 if(objektename[0]==0) mystrncpy(objektename,"models/objekte.txt",400);
 bool endungenok = (hatendung(weltname,".txt")) && hatendung(figurname,".txt") && hatendung(objektename,".txt");
 if(argflag['?'] || argflag['H'] || !endungenok)
  {
   printf("Aufruf: %s [-Optionen] [models/welt.txt] [models/figuranim.txt] [models/objekte.txt]\n",argv[0]);
   printf("Optionen:\n");
   printf("  f = fullscreen\n");
   printf("  v = verbose\n");
   printf("  m = MESA_GL_VERSION_OVERRIDE setzen\n");//test
   printf("  x,y,z = Startposition (x=West/Ost, z=Nord/Sued, y=Hoehe, Beispiel: x-95.1)\n");
   printf("  ? = diese Hilfe\n\n");
   printf("Tastenbelegung im Spiel:\n");
   printf("  w = nach vorne laufen\n");
   printf("  s = nach hinten laufen\n");
   printf("  d = nach rechts laufen\n");
   printf("  a = nach links laufen\n");
   printf("  Leertaste = schneller laufen\n");
   printf("  Shifttaste = langsamer laufen\n");
   printf("  ESC oder Linke Maustaste = Mausmodus wechseln\n");
   printf("  Rechte Maustaste = Kamera um sich selbst drehen\n");
   printf("  Mittlere Maustaste = Kameraposition verschieben\n");//TODO
   printf("  Mausrad = Zoomen\n");
   printf("  ALT q = Spiel beenden\n");
   if(!argflag['H']) printf("  h = weitere Hilfe\n");
   else
    {
     printf("\nWeitere Einstellungen koennen in \"usersettings.txt\" gemacht werden:\n");
     printf(" fofy: Kamerawinkel, dist0: Startabstand der Kamera, ...\n");
    }
   return 0;
  }

 // usersettings einlesen:
 float fovy=45.0f, startdist=11.0f;
 float latitude=0.0f, longitude=0.0f;
 int tag=20, monat=3, jahr=2021;
 int minuten=0;//TODO
 float spielzeit=5.8f*3600; //Spielstart kurz vor 6 Uhr morgens
 float zeitfaktor=60; //Faktor um den die Spielzeit schneller laeuft als reale Zeit
 FILE *fp=myfopen("usersettings.txt","r");
 if(fp!=NULL)
  {
   read_settings(fp, &fovy, &startdist, &latitude, &longitude, &tag, &monat, &jahr, &minuten, &zeitfaktor);
   fclose(fp);
   if(monat<1 || monat>12) {printf("Datumfehler\n"); monat=3;}//test
   if(tag<1) tag=1; else if(tag>30) tag=30;
   if(minuten==0) minuten = spielzeit/60;
   else spielzeit = minuten*60.0;
   printf("lat=%f lon=%f  Datum: %d.%d.%d\n",latitude,longitude,tag,monat,jahr);//test
  }
 else printf("Warnung: kein usersettings.txt gefunden\n");
 if(argflag['V']) printf("fovy=%f startdist=%f\n",fovy,startdist);//test
 
 if(argflag['M'])
  {
   SDL_setenv("MESA_GL_VERSION_OVERRIDE", "3.3", 1);
   SDL_setenv("MESA_GLSL_VERSION_OVERRIDE", "330", 1);
  }
#ifdef FULLSCREEN
  setargflags("F");
#endif

 SDL_Window* window;
 SDL_Init(SDL_INIT_EVERYTHING);
 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
 SDL_GL_SetSwapInterval(1);

 getmaxsize(&fensterbreite, &fensterhoehe);
 //printf("maximale fensterbreite: %d\n",fensterbreite);//test
 if(fensterbreite>=2400) liniendicke=4.0;
 else if(fensterbreite>=1200) liniendicke=2.0;//TODO
 
 uint32 flags = SDL_WINDOW_OPENGL;
 if(argflag['F'])
  {
   //flags = SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS; //mit Alt q stoppen!
   flags = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP; //mit Alt q stoppen!
  }
 else
  {
   flags |= SDL_WINDOW_RESIZABLE;
   fensterbreite = fensterbreite*8/10;
   fensterhoehe = fensterhoehe*8/10;
  }
 window = SDL_CreateWindow(fenstertitel, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
			   fensterbreite, fensterhoehe, flags);
 SDL_GLContext glContext = SDL_GL_CreateContext(window);
 GLenum err = glewInit();
 if(err != GLEW_OK)
  {
   std::cout << "glewInit Error: " << glewGetErrorString(err) << std::endl;
   //std::cin.get();
   return -1;
  }
 std::cout << "OpenGL version: " << glGetString(GL_VERSION) << std::endl;

 SDL_GetWindowSize(window,&fensterbreite,&fensterhoehe);
 if(argflag['V'])
  printf("Fensterbreite=%d Fensterhoehe=%d\n",fensterbreite,fensterhoehe);
 
#ifdef _DEBUG
 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
 glEnable(GL_DEBUG_OUTPUT);
 glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
 glDebugMessageCallback(openGLDebugCallback, 0);
#endif

#ifdef SHADERS_120
 Shader shader("shaders/basic120.vs", "shaders/basic120.fs");
#else
 Shader shader("shaders/basic.vs", "shaders/basic.fs");
#endif
 shader.bind();
 if(argflag['V']) printf("shaders fuer 3D-Grafik geladen\n");

 //Richtungslicht (Sonne):
 GLuint shid=shader.getShaderId();
 int directionLocation = GLCALL(glGetUniformLocation(shid, "u_directional_light.direction"));
 int diffuseLocation = GLCALL(glGetUniformLocation(shid, "u_directional_light.diffuse"));
 int specularLocation = GLCALL(glGetUniformLocation(shid, "u_directional_light.specular"));
 int ambientLocation = GLCALL(glGetUniformLocation(shid, "u_directional_light.ambient"));
 glm::vec3 sunColor = glm::vec3(0.8f); //weiter unten: Helligkeit und Farbe der Sonne je nach Tageszeit
 glm::vec3 sunDirection00 = glm::vec3(-1.0f, 0.0f, 0.0f); //Sonne am 20.3. beim Aufgang genau im Osten
 glm::vec3 sunDirection = sunDirection00;
 GLCALL(glUniform3fv(diffuseLocation, 1, (float*)&sunColor));
 glm::vec3 sunSpecular = glm::vec3(0.2f);
 GLCALL(glUniform3fv(specularLocation, 1, (float*)&sunSpecular));
 glm::vec3 sunAmbient = sunColor * 0.3f;
 GLCALL(glUniform3fv(ambientLocation, 1, (float*)&sunAmbient));
 
 //noch andere Lichter (pointLight, spotLight):
 //TODO: mehrere Punktlichter, z.B. Strassenlaternen
#ifdef LEUCHTTURM_LICHT
 float leuchtturmHelligkeit = 0.25;
 float leuchtturmSpotHelligkeit = 0.25;
 leuchtturm_init(shid); //TODO
 leuchtturm_helligkeit(leuchtturmHelligkeit,leuchtturmSpotHelligkeit);
 
 //Leuchtturm-Position:
 glm::vec4 pointLightPosition = glm::vec4(-94.0f, 10.0f, 94.0f, 0.0f); //vec4 damit es drehbar ist
 glm::vec4 deltaposition = glm::vec4(15.0f, 0.0f, 0.0f, 1.0f); //drehbarer Lichtkegel neben dem Leuchtturm
 float leuchtturmWinkel=0;
 float leuchtturmWinkelMin=0, leuchtturmWinkelMax=270*GRAD;
 int positionLocationLeuchtturm = GLCALL(glGetUniformLocation(shid, "u_point_light.position"));
#endif
 
 glm::vec3 spotLightColor = glm::vec3(1.0f, 1.0f, 1.0f); //TODO: Farbe vom Spotlicht
 spotLightColor *= 0.25f; //TODO: Helligkeit vom Spotlicht
 GLCALL(glUniform3fv(glGetUniformLocation(shid, "u_spot_light.diffuse"), 1, (float*)&spotLightColor));
 GLCALL(glUniform3fv(glGetUniformLocation(shid, "u_spot_light.specular"), 1, (float*)&spotLightColor));
 spotLightColor *= 0.3f; //Helligkeit vom ambient-Anteil des Spotlichts
 GLCALL(glUniform3fv(glGetUniformLocation(shid, "u_spot_light.ambient"), 1, (float*)&spotLightColor));
 glm::vec3 spotLightPosition = glm::vec3(0.0f, 0.0f, 0.0f); //TODO: Position Spotlicht
 glm::vec3 spotLightDirection = glm::vec3(0.0f, 0.0f, 1.0f); //Richtungsvektor muss Normalisiert sein!
 GLCALL(glUniform3fv(glGetUniformLocation(shid, "u_spot_light.position"), 1, (float*)&spotLightPosition));
 GLCALL(glUniform3fv(glGetUniformLocation(shid, "u_spot_light.direction"), 1, (float*)&spotLightDirection));
 GLCALL(glUniform1f(glGetUniformLocation(shid, "u_spot_light.innerCone"), 0.95f));
 GLCALL(glUniform1f(glGetUniformLocation(shid, "u_spot_light.outerCone"), 0.80f));
 
 float phi = latitude*GRAD; //Noerdliche Breite
 glm::vec3 sunDirection0 = jahresposition(sunDirection00,phi,tag,monat);

 // Modell-Daten einlesen:
 
 //Objekte weltobjekte; //weiter oben als globale Variable definiert
#ifdef ZEITMESSUNG
 printf("Zeitmessung von weltobjekte.init()\n");//test
#endif
 stoppuhr_reset();
 weltobjekte.init(weltname, &shader);
 //TODO: Landschaft und Teichgrund berechnen mit welt.calc innerhalb welt.txt (weltname)
 //      Landschaft gemacht, Teichgrund noch als bmf-Datei
 int usec1 = stoppuhr_read();
#ifdef ZEITMESSUNG
 zeit_print(usec1);
 printf("fertig  Zeitmessung von weltobjekte.init()\n");//test
#endif
 if(usec1>200000)
  {
   float meerweite = MEERWEITE*200000/usec1;
   if(meerweite<MINIMALE_MEERWEITE) meerweite=MINIMALE_MEERWEITE;
   landschaft1.setMeerweite(meerweite);//test
   printf("meerweite=%f\n",meerweite);//test
  }

 Ding welt("Welt"); welt.setMasse(5.9723e24);
 dinge.push_back(welt);
 
 Animobjekt figur; //laufende Figur
#ifdef FIGUR_SCHWIMMEND
 Animobjekt figur_schwimmend;
 int figur_animstatus=0;
 figur_schwimmend.init("models/figurschwimmanim.txt", &shader);
 if(figur_schwimmend.get_maxIndex() != 8) printf("Fehler: .get_maxIndex() falsch\n");//test
#endif
 figur.init(figurname, &shader);
 uint16 maxFrameId = figur.get_maxIndex(); //sollte 8 ergeben
 uint16 frameId=0;
 if(argflag['V']) printf("maxFrameId=%d\n",maxFrameId);//test
 
 Ding link("Link"); //Name der Figur die vom Spieler gesteuert wird
 link.setPointer(NULL,&figur);
 dinge.push_back(link);
 if(argflag['V']) WELT.print();//test
 if(argflag['V']) LINK.print();//test

 //Startposition setzen:
 //LINK.position=glm::vec3(0.0f, STARTYPOSITION, 0.0f);//Startposition setzen
 //LINK.position=glm::vec3(3.0f, 111.2f, 0.5f);//test: auf Wuerfel stehend
 if(int(startposx)==startposx) startposx+=0.01; //TODO: verhindert Durchfallen wenn genau auf Raster
 if(int(startposz)==startposz) startposz+=0.01;
 LINK.position=glm::vec3(startposx, startposy, startposz);
 
 //Objekte sammelobjekte; //weiter oben als globale Variable definiert
 sammelobjekte.init(objektename, &shader);
 if(argflag['V']) printf("sammelobjekte.numModeldatas=%d\n",sammelobjekte.numModeldatas);//test

#ifndef OHNE_WASSER
 if(argflag['V']) printf("wasserobjekte.init(%s, &shader)\n",wasserobjektename);//test
#ifdef ZEITMESSUNG
 printf("Zeitmessung von wasserobjekte.init()\n");//test
 stoppuhr_reset();
#endif
 wasserobjekte.init(wasserobjektename, &shader);
#ifdef ZEITMESSUNG
 zeit_print(stoppuhr_read());
 printf("fertig  Zeitmessung von wasserobjekte.init()\n");//test
#endif
 if(argflag['V']) printf("wasserobjekte.numModeldatas=%d\n",wasserobjekte.numModeldatas);//test
#endif

 if(argflag['V']) printf("sonne_id = %d\n",sonne_id);//test
 
 SDL_GL_SetSwapInterval(1); //warte jeweils auf VSYNC (funktioniert nicht immer korrekt)
#ifdef DRAHTGITTER_TEST
 GLCALL(glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); //nur Drahtgitter zeichnen
#endif
 
 uint64 perfCounterFrequency = SDL_GetPerformanceFrequency();
 uint64 lastCounter = SDL_GetPerformanceCounter();
 float32 delta = 1.0f/60.0f; //Zeitschritt, wird jeweils pro Schlaufendurchlauf berechnet
 
 glm::mat4 worldMatrix = glm::mat4(1.0f);
 //mit worldMatrix wird die Ansicht der ganzen Welt veraendert
 //noch nicht veraendert - ev. fuer Erdbeben verwendbar?

 if(argflag['V']) printf("fovy=%f startdist=%f\n",fovy,startdist);//test
 MyCamera camera(fovy, fensterbreite, fensterhoehe, false); //false fuer perspektivisch, true fuer ortho
 //Anfangsposition der Kamera: 11 Meter in z-Achse vor dem Koordinatenursprung
 camera.setPosition(glm::vec3(0.0f, 0.0f, startdist));
 camera.move(LINK.position);
 glm::vec3 link_position_vorher = LINK.position; //spaeter Position nur mit Differenz anpassen
 matrizen_an_GPU_senden(&camera, worldMatrix, &shader);
 
 statusScreen.init(fensterbreite,fensterhoehe,ID_STATUSSCREEN,&shader);
 orte.init();
 
 float time=0; //Zeit eines Bildaufbaus wird dynamisch berechnet (in Sekunden, typischer Wert: 0.0167);
 int testmin0=0;//test
 
 float cameraSpeed = 6.0f;
 float cameraSpeed2 = 60.0f;
 
//Funktion von "Mausrad drehen" und "Mausbewegung mit Mittlerer Taste gedrueckt" getauscht
//(am 7.3.2022 gemacht)
 int mausrady=0; //wenn Mausrad gedreht +1 oder -1
 int mausmotionx=0, mausmotiony=0; //gesetzt wenn Mausbewegung mit Mittlerer Taste gedrueckt (neu 7.3.22)
 
 float cameraUp=0, cameraSide=0; //Lineare Kamerabewegung auf/ab und rechts/links (momentan nicht benutzt)
 
 static int escapeflag=0;
 SDL_SetRelativeMouseMode(SDL_TRUE);
 GLCALL(glEnable(GL_CULL_FACE));
 GLCALL(glEnable(GL_DEPTH_TEST));
 glm::vec3 animStep0 = {0.0f, 0.0f, 0.02f}; //ev. justieren fuer Laufgeschwindigkeit
 glm::vec3 animStep = animStep0; //wird bei Aenderung der Laufrichtung dann noch angepasst
 float animRotate = 0.0f; //Rotationswinkel der Figur in RAD
 float animRotStep = 1.0f*GRAD; //Winkelschritt 1 Grad
 float animRotStep2= 5.0f*GRAD; //groesserer Winkelschritt
 float frameSpeed = 1.5f; //Laufgeschwindigkeit in Meter/Sec
 float frameDist = 0.0f; //zurueckgelegte Distanz der Figur seit letztem Frame-Wechsel
 float frameStep = 0.125f; //Distanz fuer Frame-Wechsel
 uint8 mouse_button=0; //Zustand der Maustasten
 const uint8 MOUSE_LEFT=1, MOUSE_RIGHT=2, MOUSE_MIDDLE=4;
 bool buttona=false, buttons=false, buttond=false, buttonw=false; //Tasten zum laufen lassen der Figur
 bool buttonShift=0;
 bool buttonAsci[255]; for(int i=0;i<255;i++) buttonAsci[i]=false;
 const uint32 figurId=1; //TODO: aendern bei mehreren bewegten Figuren
 int mausx=0,mausy=0; //Mausposition im absoluten Modus

 int itest=0;
 for(bool close=false; !close;) //mainloop
  {
   SDL_Event event;
#ifdef ZEITMESSUNG
   if(itest<10) {
    printf("Zeitmessung von ersten 10 Frames\n");//test
    stoppuhr_reset();
   }
#endif
   while(SDL_PollEvent(&event))  //events-Auswerteschlaufe
    {
     if(event.type==SDL_WINDOWEVENT)
      {
       if(event.window.event==SDL_WINDOWEVENT_RESIZED)
	{
	 printf("Fenstergroesse hat geaendert\n");//test
	 //selbst neu setzen: SDL_SetWindowSize(window, breite, hoehe);
	 SDL_GetWindowSize(window,&fensterbreite,&fensterhoehe);
	 printf("Fensterbreite=%d Fensterhoehe=%d\n",fensterbreite,fensterhoehe);
	 //geht nicht: camera.reinit(45.0f, fensterbreite, fensterhoehe, false);
	 statusScreen.init(fensterbreite,fensterhoehe,ID_STATUSSCREEN,&shader);
	 orte.init();
	}
       else if(event.window.event==SDL_WINDOWEVENT_SIZE_CHANGED)
         printf("Fenstergroesse am aendern\n");//test
      }
     else if(event.type==SDL_QUIT) {close = true;}
     else if(event.type==SDL_KEYDOWN) //eine Taste gedrueckt
      {
       if(event.key.keysym.mod & (KMOD_LALT | KMOD_RALT | KMOD_ALT))
	{
	 int taste=event.key.keysym.sym; //Taste zusammen mit Alt-Taste gedrueckt
	 if(taste=='q')
	   {close=true; if(argflag['V']) printf("ALT q gedrueckt\n");}
#ifdef _DEBUG
	 else if(taste==0x400000E6) {}//bisher nur Alt-Taste gedrueckt
	 else if(taste>' ' && taste<='z') printf("Taste %c mit Alt-Taste gedrueckt\n",taste);
	 else printf("Taste 0x%02X mit Alt-Taste gedrueckt\n",taste);
#endif
	}
       else
	{
	 switch(event.key.keysym.sym) //einzelne Taste gedrueckt
	  {
	   case SDLK_ESCAPE:
	    escapeflag ^= 1;
	    SDL_SetRelativeMouseMode((escapeflag==0)?SDL_TRUE:SDL_FALSE);
	    break;
	  //case SDLK_UP: printf("Pfeil rauf gedrueckt\n"); break;
	  //case SDLK_DOWN: printf("Pfeil runter gedrueckt\n"); break;
	  //case SDLK_RIGHT: printf("Pfeil rechts gedrueckt\n"); break;
	  //case SDLK_LEFT: printf("Pfeil links gedrueckt\n"); break;
	  case SDLK_w: buttonw=true; break; //w-Taste gedrueckt
	  case SDLK_s: buttons=true; break;
	  case SDLK_d: buttond=true; break;
	  case SDLK_a: buttona=true; break;
	  case SDLK_q: hilfe_aus(); break;//test
	  case SDLK_SPACE: buttonAsci[' ']=true; break;
	  case SDLK_LSHIFT: case SDLK_RSHIFT: buttonShift=true; break;
	  case SDLK_p: printf("Figurposition: {%f %f %f}\n",
			      LINK.position.x, LINK.position.y, LINK.position.z); //test
#ifdef USERKOORDINATEN_UMRECHNUNG
		       float xu,yu,zu;
	               xu=koord.xopengl2userx(LINK.position.x);
	               yu=koord.zopengl2usery(LINK.position.z);
	               zu=koord.yopengl2userz(LINK.position.y);
		       //if(logflags&1) xu=powf(10.0f,xu);
		       //if(logflags&2) yu=powf(10.0f,yu);
		       //if(logflags&4) zu=powf(10.0f,zu);
		       LINK.userpos.x=xu; LINK.userpos.y=yu; LINK.userpos.z=zu; 
		       printf("User-Koordin.: {%g %g %g}\n",xu, yu, zu);
#else
		       LINK.userpos.x=LINK.position.x; LINK.userpos.y=LINK.position.y;
		       LINK.userpos.z=LINK.position.z;
#endif
	               posprint_flag=600; //Positionsanzeige waehrend 10 Sekunden (bei 60Hz Framerate)
		       hilfeseite_zeichnen(hilfeflag);
	               break;
	   case SDLK_x: //Taste 'x' gedrueckt: Huepfen: senkrechte Geschwindigkeit setzen
	    //if(fastnull(LINK.veloc.y)) LINK.veloc.y = 4.4f; //Absprung-Geschwindigkeit in m/s
	    if(fastnull(LINK.veloc.y)) LINK.veloc.y = 5.0f; //Absprung-Geschwindigkeit in m/s
	    if(argflag['V']) printf("Huepfer: LINK.veloc.y = %f\n", LINK.veloc.y);//test
	    break;
	   case SDLK_7:
	    printf(" SDLK_7\n");//test
	    break;
	   case SDLK_KP_7:
	    printf(" SDLK_KP_7\n");//test
	    break;
	   default:
	    // Hilfe fuer Tastencode: /usr/include/SDL2/SDL_keycode.h
	    buttonAsci[event.key.keysym.sym & 0xFF]=true;
//#ifdef _DEBUG
	    printf("Taste '%c' (0x%02X) gedrueckt\n",event.key.keysym.sym, event.key.keysym.sym);
//#endif
	    break;
	   }
	}
      }
     else if(event.type==SDL_KEYUP) //Taste losgelassen
      {
       switch(event.key.keysym.sym)
	{
	 case SDLK_w: buttonw=false; break;
	 case SDLK_s: buttons=false; break;
	 case SDLK_a: buttona=false; break;
	 case SDLK_d: buttond=false; break;
	 case SDLK_SPACE: buttonAsci[' ']=false; break;
	 case SDLK_LSHIFT: case SDLK_RSHIFT: buttonShift=false; break;
	 default: buttonAsci[event.key.keysym.sym & 0xFF]=false;
	}
      }
     else if(event.type==SDL_MOUSEMOTION) //Maus bewegt
      {
       if(SDL_GetRelativeMouseMode())
	{
	 if(mouse_button==MOUSE_LEFT)
	  {
	   //TODO: ev. andere Funktion mit linker Maustaste?
	  }
	 else if(mouse_button==MOUSE_RIGHT)
	  {camera.onMouseMoved(event.motion.xrel, event.motion.yrel);} //Kamerabewegung nach Tutorial
	 else if(mouse_button==MOUSE_MIDDLE)
	  {
	   //printf("MOUSE_MIDDLE  event.motion.xrel=%d yrel=%d\n",event.motion.xrel, event.motion.yrel);//test
	   //camera.moveToLookat(-event.motion.yrel); //alte Variante: naeher an Figur fahren
	   mausmotionx = event.motion.xrel;//neu 7.3.22
	   mausmotiony = event.motion.yrel;//neu 7.3.22
	  }
	 else
	  {camera.onMouseRotate(event.motion.xrel, -event.motion.yrel);} //um Figur herum drehen
	}
       else
	{ //absoluter Mausmodus
	 mausx = event.motion.x;
	 mausy = event.motion.y;
	}
      }
     else if(event.type==SDL_MOUSEBUTTONDOWN)
      {
       if(event.button.button==SDL_BUTTON_LEFT)
	{
	 if(escapeflag==1)
	  {
	   //printf("Mausklick mausx=%d mausy=%d\n",mausx,mausy);//test
	   if(mausy<=orte.symbolsizeBig[1]*3/2 && mausx >= fensterbreite - orte.symbolsizeBig[0]*3/2)
	    { //auf Ring geklickt
	     hilfeseite_zeichnen(hilfeflag^=1);
	     escapeflag=0;
	    }
	  }
	 escapeflag ^= 1;
	 SDL_SetRelativeMouseMode((escapeflag==0)?SDL_TRUE:SDL_FALSE);
	 mouse_button |= MOUSE_LEFT;
	}
       else if(event.button.button==SDL_BUTTON_RIGHT)
	{
	 //printf("Rechte Maustaste gedrueckt\n");//test
	 mouse_button |= MOUSE_RIGHT;
	}
       else if(event.button.button==SDL_BUTTON_MIDDLE)
	{
	 //printf("Mittlere Maustaste gedrueckt\n");//test
	 mouse_button |= MOUSE_MIDDLE;
	}
       else //if(event.button.button!=0)
	printf("Unknown button pressed: 0x%X\n",event.button.button);//test
      }
     else if(event.type==SDL_MOUSEBUTTONUP)
      {
       switch(event.button.button)
	{
	 case SDL_BUTTON_LEFT:   mouse_button &= ~MOUSE_LEFT; break;
	 case SDL_BUTTON_RIGHT:  mouse_button &= ~MOUSE_RIGHT; break;
	 case SDL_BUTTON_MIDDLE: mouse_button &= ~MOUSE_MIDDLE; break;
#ifdef _DEBUG
	 default: printf("Warning: unbekannter mouse_button 0x%X\n",event.button.button);
#endif
	}
       //printf("Maustaste 0x%X losgelassen\n",mouse_button);//test
      }
     else if(event.type==SDL_MOUSEWHEEL) //Mausrad
      {
       //printf("SDL_MOUSEWHEEL  event.wheel.x=%d y=%d\n",event.wheel.x, event.wheel.y);//test
       mausrady = event.wheel.y;
      }
     else if(event.type==SDL_AUDIODEVICEADDED)
      {
       printf("audio device avilable\n");//test
      }
#ifdef _DEBUG
     //else printf("event.type=%ld\n",(long)event.type);//test
     // Hilfe unter /usr/include/SDL2/SDL_events.h oder SDL_mouse.h
#endif
    }// end of  while(SDL_PollEvent(&event))
   
   bool steht_still=false;
   float winkel = 90.0f*GRAD; //entspricht Richtung in die Kamera schaut
   if(buttonw && buttona) winkel += 45.0f*GRAD;
   else if(buttonw && buttond) winkel -= 45.0f*GRAD;
   else if(buttons && buttona) winkel += 135.0f*GRAD;
   else if(buttons && buttond) winkel -= 135.0f*GRAD;
   else if(buttons) winkel -= 180.0f*GRAD;
   else if(buttona) winkel += 90.0f*GRAD;
   else if(buttond) winkel -= 90.0f*GRAD;
   else if(buttonw) {}//winkel schon ok
   else steht_still=true;

   float laufkraft=0.0f;//TODO
#ifdef KRAFT_VERSION
   //Berechnungen mit Kraefte-Berechnung in class Ding:
   const float laufkraftkonstante=2.0f;//TODO
   if(!steht_still) laufkraft=frameSpeed*laufkraftkonstante;
   LINK.laufrichtung=animStep;
   LINK.kraefte_berechnen(laufkraft);
   LINK.accel = LINK.kraft / LINK.masse; // Kraft = Masse * Beschleunigung
   LINK.position += LINK.veloc * delta; // ds=v*dt
   LINK.veloc    += LINK.accel * delta; // dv=a*dt
   LINK.veloc.y *= LINK.daempfung.y;//TODO
   printf(" Beschleunigung: %f  Geschwindigkeit: %f\n",absvec3(LINK.accel),absvec3(LINK.veloc));//test
#else
   //Berechnungen mit Schwerkraft-Simulation nur in y-Richtung:
   LINK.kraefte_berechnen(laufkraft);
   LINK.accel.y = -ERDBESCHLEUNIGUNG;
   LINK.position.y += delta * LINK.veloc.y; // ds=v*dt
   LINK.veloc.y    += delta * LINK.accel.y; // dv=a*dt
   float bodenhoehe = bodenhoehe_berechnen(LINK.position);
   if(LINK.position.y <= bodenhoehe) //bei Bodenberuehrung sofort stoppen
    {
     LINK.position.y = bodenhoehe;
     LINK.veloc.y = 0.0f;
     LINK.accel.y = 0.0f;
    }
   if(LINK.position.y < -1.6f)
    {
     LINK.position.y = -1.6f;//test: nicht tiefer als 1.6 Meter unter Meereshoehe
     LINK.veloc.y = 0.0f;
    }
#endif

#ifdef FIGUR_SCHWIMMEND
   if(bodenhoehe <= -1.6f || landschaft1.ist_in_wasser(LINK.position, -1.3))
    {
     if(figur_animstatus!=1)
      {
       //test link.setPointer(NULL,&figur_schwimmend);
       figur_animstatus=1;
 #ifdef _DEBUG
       printf("Figur schwimmend\n");//test
       printf("figur_schwimmend.position = %f %f %f\n",figur_schwimmend.position.x,figur_schwimmend.position.y,figur_schwimmend.position.z);//test
 #endif
      }
    }
   else
    {
     if(figur_animstatus!=0)
      {
       //test link.setPointer(NULL,&figur);
       figur_animstatus=0;
       printf("Figur laufend\n");//test
       printf("figur.position = %f %f %f\n",figur.position.x,figur.position.y,figur.position.z);//test
      }
    }
#endif

   //Lauf-Geschwindigkeit in der x-z-Ebene:
   //if(buttonAsci[' '])  {frameSpeed *= 1.02f; if(frameSpeed > 6.0f) frameSpeed=6.0f;}
   if(buttonAsci[' '])  {frameSpeed *= 1.02f; if(frameSpeed > 15.0f) frameSpeed=15.0f;}//test: schneller laufen
   else if(buttonShift) {frameSpeed /= 1.02f; if(frameSpeed < 1.5f) frameSpeed=1.5f;}
   else {frameSpeed /= 1.002f; if(frameSpeed < 1.5f) frameSpeed=1.5f;}
   
   if(!steht_still)
    {
     //Figur (LINK) in die Richtung drehen, in die die Kamera schaut:
     animRotate = winkel - camera.getYaw(); //yaw ist Winkel gegenueber x-Achse, z-Achse +90Grad
     animStep = rotateY(animStep0,animRotate);
     //Figur in diese Richtung bewegen und Kamera mit bewegen:
     LINK.neue_position(animStep*frameSpeed);
    }

   y_laufendes_mittel.add(LINK.position.y);
   glm::vec3 pos = LINK.position;
   pos.y = y_laufendes_mittel.get();
   
   camera.move(pos - link_position_vorher);
   link_position_vorher = pos;
   
   //fuer Bewegung der Beine:
   if(steht_still)
    {
     frameDist=0.0f;
     frameId = 0;
    }
   else
    {
     frameDist += delta * frameSpeed;
     if(frameDist >= frameStep)
      {
       frameId = (frameId+1)%maxFrameId;
       frameDist -= frameStep;
      }
    }

#ifdef FIGUR_SCHWIMMEND
   if(figur_animstatus==1) figur_schwimmend.animate(frameId, LINK.position, animRotate);
   else
#endif
   figur.animate(frameId, LINK.position, animRotate);
   
   uint32 modelid, klonid, art;
   if(ist_bei_objekt(LINK.position, &modelid, &klonid))
    {
     char name[64];
     int typ = sammelobjekte.getTypName(modelid,name,64,&art);
     if(art!=ART_SAMMELOBJEKT) printf("Fehler: art=%d\n",art);//test
     if(argflag['V']) printf("%s %d gefunden\n",name,klonid+1);//test
     int typ2=objekt_sammeln(modelid, klonid);
     if(typ2!=typ) printf("Fehler: typ=0x%X typ2=0x%X\n",typ,typ2);//test
     const uint32 TYP_RING = (('R'<<24)+('i'<<16)+('n'<<8)+'g');
     if(typ==TYP_RING) hilfeseite_zeichnen(hilfeflag=1);
    }

   GLCALL(glClearColor(0, 0, 0, 1.0)); //schwarzer Hintergrund
   GLCALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
   
   time += delta;
   //printf("time = %f\n",time);//test
   spielzeit += delta*zeitfaktor;
   if(spielzeit >= 24*3600.0f)
    {
     spielzeit -= 24*3600.0f;
     if(++tag==31) {tag=1; if(++monat==13) monat=1;}
     sunDirection0 = jahresposition(sunDirection00,phi,tag,monat);
    }
   
   if(mausrady!=0)
    {
     //float speed = (mouse_button==MOUSE_MIDDLE) ? cameraSpeed2 : cameraSpeed;//alt
     //camera.moveFront(delta*mausrady * speed);//alt
     float speed = cameraSpeed * vec3abs(camera.getPosition()-LINK.position);//neue Variante
     camera.moveToLookat(-mausrady*delta*speed); //naeher an Figur fahren //neue Variante
     mausrady=0;
     //je nach Kameraabstand near far aendern:
     static float abstvorher=10;
     static int nearfarflag=1; //0=nah dran, 2=weit weg, 1=dazwischen
     float abst=vec3abs(camera.getPosition()-LINK.position);
     if(abst<abstvorher)
      {
       if(nearfarflag>0 && abst < 2.0f)
	{nearfarflag=0; camera.setnearfar(0.01f, 5.0f);}
       else if(nearfarflag>1 && abst < 50.0f)
	{nearfarflag=1; camera.setnearfar(1.0f, 500.0f);}
      }
     else if(abst>abstvorher)
      {
       if(nearfarflag<2 && abst > 55.0f)
	{nearfarflag=2; camera.setnearfar(10.0f, 5000.0f);}
       else if(nearfarflag<1 && abst > 2.5f)
	{nearfarflag=1; camera.setnearfar(1.0f, 500.0f);}
      }
     abstvorher=abst;
    }
   if(mausmotionx!=0 || mausmotiony!=0) //neue Variante 7.3.2022
    {
     float speed = cameraSpeed*0.01*vec3abs(camera.getPosition()-LINK.position);
     //printf("speed=%f\n",speed);//test
     if(mausmotiony!=0) camera.moveFront(delta*mausmotiony*speed);
     if(mausmotionx!=0) camera.moveFrontx(-delta*mausmotionx*speed);
     mausmotionx=mausmotiony=0;
    }
   if(cameraSide!=0) {camera.moveSideways(cameraSide*delta * cameraSpeed); cameraSide=0;}
   if(cameraUp!=0)   {camera.moveUp(cameraUp*delta * cameraSpeed); cameraUp=0;}

   //Richtungslicht (Sonne):
   int teststd=(int)(spielzeit/3600), testmin=(int)(spielzeit/60)%60;
   if(teststd!=testmin0) printf("spielzeit: %02d:%02d Uhr  ",teststd,testmin);//test
   float alfa = (spielzeit-(6*3600.0f))/(24*3600.0f) * (360*GRAD);
   //float phi = latitude*GRAD; //Noerdliche Breite
   glm::vec3 drehvektor = glm::vec3(0.0f, sinf(phi), -cosf(phi));
   sunDirection = glm::rotate(sunDirection0, -alfa, drehvektor);
   if(teststd!=testmin0)
    {
     printf(" sunDirection = %f %f %f\n", sunDirection.x, sunDirection.y, sunDirection.z);//test
     testmin0=teststd;
    }//test
   static float ueberHorizontvorher=0.0f;
   static int sonne_testflag=0;
   float ueberHorizont = -sunDirection.y;
   if(ueberHorizont < 0) //Sonne unter dem Horizont?
    {
     if(sonne_testflag && ueberHorizontvorher>=0)
       printf("Sonnenuntergang %d.%d. %d:%02d\n",tag,monat,teststd,testmin);//test
     sunColor = glm::vec3(0.0f); //kein Sonnenschein
     sunAmbient = glm::vec3(0.32f);//(0.6f); //nur Umgebungslicht
     sunSpecular = glm::vec3(0.0f); //(0.16f);
    }
   else
    {
     if(sonne_testflag && ueberHorizontvorher<0)
       printf("Sonnenaufgang %d.%d. %d:%02d\n",tag,monat,teststd,testmin);//test
#ifdef HELLERE_SONNE
     if(ueberHorizont < 0.2) //Sonne nur sehr wenig ueber Horizont
           sunColor = glm::vec3(0.6f+2*ueberHorizont, 4*ueberHorizont, ueberHorizont); //Rote Sonne
     else if(ueberHorizont < 0.4) //Sonne nur wenig ueber Horizont
      sunColor = glm::vec3(1.0f, 1.0f, 0.4f+3*(ueberHorizont-0.2f)); //Gelbe Sonne
     else  sunColor = glm::vec3(1.0f); //weisse Sonne
     sunAmbient = glm::vec3(0.4f);
     sunSpecular = sunColor*0.5f;
#else
     if(ueberHorizont < 0.2) //Sonne nur sehr wenig ueber Horizont
           sunColor = glm::vec3(0.4f+2*ueberHorizont, 4*ueberHorizont, ueberHorizont); //Rote Sonne
     else if(ueberHorizont < 0.4) //Sonne nur wenig ueber Horizont
      sunColor = glm::vec3(0.8f, 0.8f, 0.2f+3*(ueberHorizont-0.2f)); //Gelbe Sonne
     else  sunColor = glm::vec3(0.8f); //weisse Sonne
     sunAmbient = glm::vec3(0.32f);
     sunSpecular = sunColor*0.5f;
#endif //HELLERE_SONNE
    }
   ueberHorizontvorher = ueberHorizont; sonne_testflag=1;
   GLCALL(glUniform3fv(diffuseLocation, 1, (float*)&sunColor));
   GLCALL(glUniform3fv(ambientLocation, 1, (float*)&sunAmbient));
   GLCALL(glUniform3fv(specularLocation, 1, (float*)&sunSpecular));
   if(sonne_id!=0)
    {
     Vec3 pos = sunDirection * (-400.0f) + LINK.position;
     uint32 klonid=0;
     sammelobjekte.setPosition(sonne_id, klonid, pos);
     if(sunDirection.y > 0)
      {
       sammelobjekte.setColor(sonne_id, Vec3(0.4f,0.0f,0.0f), &shader);//test
       //printf("test: Sonne unter Horizont = dunkelrot\n");//test
      }
     else
      {
       sammelobjekte.setColor(sonne_id, sunColor, &shader);
       //printf("test: Sonnenfarbe gesetzt: %f %f %f\n",sunColor.x,sunColor.y,sunColor.z);//test
      }
    }

   //Position der Lichter aktualisieren:
   glm::vec4 transormedSunDirection = glm::transpose(glm::inverse(camera.getView())) * glm::vec4(sunDirection, 1.0f);
   GLCALL(glUniform3fv(directionLocation, 1, (float*)&transormedSunDirection));

#ifdef LEUCHTTURM_LICHT
   glm::mat4 pointLightMatrix = glm::rotate(glm::mat4(1.0f), -delta, {0.0f, 1.0f, 0.0f}); //rotieren um y-Achse
   deltaposition = pointLightMatrix * deltaposition;
   leuchtturmWinkel += delta; if(leuchtturmWinkel>=360*GRAD) leuchtturmWinkel -= 360*GRAD;
   if(leuchtturmWinkel>leuchtturmWinkelMin && leuchtturmWinkel<leuchtturmWinkelMax
      && ueberHorizont<0.01) //Leuchtturm nur eingeschaltet wenn auf Meer zeigend und Nachts (oder Sonne sehr tief)
        {leuchtturmHelligkeit=0.25; leuchtturmSpotHelligkeit=0.25;}
   else {leuchtturmHelligkeit=0.0;  leuchtturmSpotHelligkeit=0;}
   glm::vec3 transformedPointLightPosition = (glm::vec3) (camera.getView() * (pointLightPosition+deltaposition));
   glUniform3fv(positionLocationLeuchtturm, 1, (float*)&transformedPointLightPosition);
   leuchtturm_helligkeit(leuchtturmHelligkeit,leuchtturmSpotHelligkeit);
   //Spotlicht vom Leuchtturm:
   glm::vec3 transformedLeuchtturmSpotPosition = (glm::vec3) (camera.getView() * glm::vec4(leuchtturmSpotPosition,1.0f));
   GLCALL(glUniform3fv(positionLocLeuchtturm, 1, (float*)&transformedLeuchtturmSpotPosition));
   leuchtturmSpotDirection = pointLightMatrix * leuchtturmSpotDirection;
   glm::vec4 transformedSpotDirection = glm::transpose(glm::inverse(camera.getView())) * leuchtturmSpotDirection;
   GLCALL(glUniform3fv(directionLocLeuchtturm, 1, (float*)&transformedSpotDirection));
#endif
   
   matrizen_an_GPU_senden(&camera, worldMatrix, &shader);
   weltobjekte.render(1, &camera, &shader);
   sammelobjekte.render(1, &camera, &shader);

   //matrizen_an_GPU_senden(&camera, figur.modelMatrix, &shader); //direkt in render() gemacht
#ifdef FIGUR_SCHWIMMEND
   if(figur_animstatus==1) figur_schwimmend.render(&camera, &shader);
   else
#endif
   figur.render(&camera, &shader);
   
#ifndef OHNE_WASSER
   //Wasser Wellenbewegungen berechnen:
   uint32 tmax=wasserobjekte.numModeldatas;
   //printf("recalc von %d Teichen\n",tmax);//test
   for(uint32 teichnr=0;teichnr<tmax;teichnr++)
    {
     wasserobjekte.modeldatas[teichnr]->recalc(delta,teichnr);
     delete wasserobjekte.models[teichnr];
     Model* model=new Model;
     model->init2(wasserobjekte.modeldatas[teichnr], &shader);
     wasserobjekte.models[teichnr] = model;
    }
   
   float alpha = cos(camera.getPitch());
   if(alpha<0.2f) alpha=0.2f; else if(alpha>0.9) alpha=0.9;
   alpha_an_GPU_senden(alpha, &shader);
   GLCALL(glEnable(GL_BLEND));
   GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
   //GLCALL(glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE));
   //GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA));
   //wassertestflag=1;//test
   wasserobjekte.render(1, &camera, &shader);
   //wassertestflag=0;//test
   GLCALL(glDisable(GL_BLEND));
#endif
   
   //Richtungslicht (Sonne) fuer Statusscreen:
   glm::vec3 statusColor = glm::vec3(0.8f);
   glm::vec3 statusSpecular = glm::vec3(0.16f);
   GLCALL(glUniform3fv(diffuseLocation, 1, (float*)&statusColor));
   GLCALL(glUniform3fv(specularLocation, 1, (float*)&statusSpecular));
   glm::vec4 statusDirection = glm::vec4(-1.0f, -1.0f, -1.0f, 1.0f);
   GLCALL(glUniform3fv(directionLocation, 1, (float*)&statusDirection));
   statusScreen.render(sammelobjekte);
   statusScreen.render2();
   
   SDL_GL_SwapWindow(window);

   uint64 endCounter = SDL_GetPerformanceCounter();
   uint64 counterElapsed = endCounter - lastCounter;
   delta = (float32)counterElapsed / (float32)perfCounterFrequency;
#ifdef PERFORM_TEST
   uint32 fps = (uint32)(1.0 / delta); //test: Frames Pro Sekunde
   std::cout << " FPS: " << fps << "  \r";//test
#endif
   lastCounter = endCounter;
   if(posprint_flag)
    {
     if(--posprint_flag==0) hilfeseite_zeichnen(hilfeflag);
    }
#ifdef ZEITMESSUNG
   if(itest<10) {
    printf("Frame: "); zeit_print(stoppuhr_read());
    itest++;
   }
#endif
  }// end of mainloop
 std::cout << std::endl;

 //aufraeumen:
 texturen_freigeben();
 //TODO: weiter aufraeumen?
 return 0;
}//end of main()

void matrizen_an_GPU_senden(MyCamera* camera, glm::mat4 modelMatrix, Shader* shader)
{
 static int startflag=1;
 static int modelViewProjMatrixLocation, modelViewLocation, invModelViewLocation;
 if(startflag)
  {
   GLuint shid=shader->getShaderId();
   modelViewProjMatrixLocation = GLCALL(glGetUniformLocation(shid, "u_modelViewProj"));
   modelViewLocation = GLCALL(glGetUniformLocation(shid, "u_modelView"));
   invModelViewLocation = GLCALL(glGetUniformLocation(shid, "u_invModelView"));
   //printf("test: modelViewProjMatrixLocation = %d\n",modelViewProjMatrixLocation);//test
   startflag=0;
  }
 glm::mat4 modelViewProj = camera->getViewProj() * modelMatrix;
 glm::mat4 modelView = camera->getView() * modelMatrix;
 glm::mat4 invModelView = glm::transpose(glm::inverse(modelView));
 GLCALL(glUniformMatrix4fv(modelViewProjMatrixLocation, 1, GL_FALSE, &modelViewProj[0][0]));
 GLCALL(glUniformMatrix4fv(modelViewLocation, 1, GL_FALSE, &modelView[0][0]));
 GLCALL(glUniformMatrix4fv(invModelViewLocation, 1, GL_FALSE, &invModelView[0][0]));
}

void alpha_an_GPU_senden(float32 alpha, Shader* shader)
{
 static int startflag=1;
 static int alphaLocation;
 if(startflag)
  {
   alphaLocation = GLCALL(glGetUniformLocation(shader->getShaderId(), "u_alpha"));
   startflag=0;
  }
 GLCALL(glUniform1f(alphaLocation, alpha));
}

void read_settings(FILE *fp,float *fovy, float *startdist, float *lat,float *lon,
		   int *tag,int *monat,int *jahr,int *minuten,float *zeitfaktor)
{
 char zeile[200], *str;
 while(getline(fp,zeile,200))
  {
   for(str=zeile;*str==' ' || *str=='\t';str++) {}//Leerzeichen an Zeilenanfang ueberlesen
   if(*str==0 || *str=='#' || (str[0]=='/' && str[1]=='/')) continue;//Kommentare ueberlesen
   kommentare_entfernen(str);
   if(strncmp(str,"fofy:",5)==0)
    {sscanf(&str[5],"%f",fovy);}
   else if(strncmp(str,"dist0:",6)==0)
    {sscanf(&str[6],"%f",startdist);}
   else if(strncmp(str,"koord:",6)==0)
    {sscanf(&str[6],"%f,%f",lat,lon);}
   else if(strncmp(str,"datum:",6)==0)
    {sscanf(&str[6],"%d.%d.%d",tag,monat,jahr);}
   else if(strncmp(str,"zeit:",5)==0)
    {
     int stunde,minute;
     sscanf(&str[5],"%d%*c%d",&stunde,&minute); *minuten=stunde*60+minute;
    }
   else if(strncmp(str,"zeitfaktor:",11)==0)
    {sscanf(&str[11],"%f",zeitfaktor);}
   else if(strncmp(str,"startposition:",14)==0)
    {
     str+=14; while(*str==' ') {str++;}
     //if(isdigit(*str))
      {
       int n=sscanf(str,"%f%*c%f%*c%f",&startposx,&startposz,&startposy);
       if(n!=3) printf("Fehler usersettings: 3 Fliesszahlen erwartet: \"%s\"\n",zeile);
      }
     //else if(strncmp(str,"auto",4)==0) {startpos_auto=1;}
    }
   else
    {printf("unbekannte Zeile in usersettings.txt: \"%s\"\n",zeile);}
  }
}

glm::vec3 jahresposition(Vec3 sunDirection00, float phi, int tag, int monat)
{
 float jahreswinkel = ((monat-1)*30+(tag-1))*GRAD;
 float drehwinkel = sinf(jahreswinkel-79*GRAD)*(23.5*GRAD);
 glm::vec3 drehvektor = glm::vec3(0.0f, cosf(phi), sinf(phi));
 return glm::rotate(sunDirection00, drehwinkel, drehvektor);
}
