/* stlmodelexporter.cc			letzte Aenderung: 23.1.2021 */
#define VERSION "Version 0.1"
/*

 Modell im STL-Format einlesen und als BMF-Datei speichern (eigenes Format von Pilzschaf)
 Beschreibung der Formate:
 STL:  https://de.qaz.wiki/wiki/STL_(file_format)
       bei Dateien von freecad fehlt das UINT8[n], Attribut ist also immer 0
 BMF: bmf-dateiformat.txt

Die Einheiten von OpenGL in Spiel1 sind als Meter definiert.
Die Einheiten in STL-Dateien sind eigentlich mm, aber in selbst entworfenen
Modellen (z.B. in figur.stl) sind cm gemeint.

History:
9.1.2021	Erstellung (RP)
16.1.2021       Erweiterung fuer mehrere Meshes (obwohl in STL wohl nicht unterstuetzt)
23.1.2021  0.1  Skalierfaktor und Offset korrigiert
*/

#include <stdio.h>
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <string.h>
#include "vektor3fklasse.cc"

typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;

/************************* eigene Klassen *****************************/
class Dreieck {
public:
 float normalen[3];
 float vertex1[3];
 float vertex2[3];
 float vertex3[3];
 uint16 attribut;
};

/********************** Globale Variablen *****************************/
uint64 numMeshes=0;
//float diffuse[3] = {0.4f, 0.2f, 0.1f}; //TODO
float diffuse[3]  = {180/255.0f, 125/255.0f, 73/255.0f};
float specular[3] = {diffuse[0]/4, diffuse[1]/4, diffuse[2]/4};
float emissive[3] = {0,0,0};
float offset[3] = {0,0,0}; //Offset in Meter beim Speichern zu beruecksichtigen
float shininess = 4.0;
float scalefaktor = 1.0; //Skalierfaktor, beim Einlesen der Daten zu beruecksichtigen

/************************* Vordeklarationen ***************************/
void unterprog(FILE *fp1,FILE *fp2);
void save_bmf(FILE *fp,Dreieck *dreiecke,uint32 numDreiecke);
void output_uint64(FILE *fp, uint64 u);

/**************** Routinen zur Parameterauswertung ********************/
#define MAXARG 2
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')
    {
     sscanf(s,"%f",&scalefaktor);
     //sscanf(s,"%lf",&scalefaktor); //wenn als double definiert
     while(isdigit(*s) || *s=='.') {s++;}
    }
  }
}

/*************************** Kleinkram ********************************/
int getline(FILE *fp,char *s,int lim)
{		/* liest eine Textzeile oder maximal lim Zeichen */
		/* und ersetzt den Zeilentrenner durch 0         */
 int c=0;
 while(--lim && (c=getc(fp))!=EOF && c!='\n')
	*s++ = c;
 *s='\0';
 return (c!=EOF);	/* TRUE wenn erfolgreich, FALSE wenn Fileende */
}

bool schonvorhanden(const char *name)
{
 FILE *fp=fopen(name,"rb");
 if(fp==NULL) return false;
 fclose(fp);
 return true;
}

bool ist_stlname(const char *zeile)
{
 int n = strlen(zeile) - 4;
 return (n>0 && strcmp(&zeile[n],".stl")==0);
}

void farbe_einlesen(const char *zeile, float *data)
{
 float r,g,b;
 while(*zeile!=0 && (*zeile++)!=':') {}
 int n=sscanf(zeile,"%f, %f, %f",&r,&g,&b);
 if(n!=3) {printf("Fehler in farbe_einlesen(): zu wenige Parameter\n");}
 if(r>1 || g>1 || b>1)
  {
   r /= 255.0f;  g /= 255.0f;  b /= 255.0f;
  }
 data[0]=r;
 data[1]=g;
 data[2]=b;
}

void drei_floats_einlesen(const char *zeile, float *data)
{
 float x,y,z;
 int n=sscanf(zeile,"%f, %f, %f",&x,&y,&z);
 if(n!=3) {printf("Fehler in drei_floats_einlesen(): zu wenige Parameter\n");}
 data[0]=x;
 data[1]=y;
 data[2]=z;
}

/************************* Hauptprogramm ******************************/
int main(int argc,char *argv[])
{
 char quellname[200],zielname[200];
 FILE *fp1,*fp2;
 int i,j,c;
 quellname[0]=zielname[0]=0;
 if(argc<=0)
  {/* es wurde von WorkBench (GUI, Desktop, ...) gestartet */
   j=0;
  }
 else
  {/* es wurde von Shell (Terminal, Konsole, ...) gestartet */
   for(j=0,i=1;i<argc;i++)
    {
     if((c= *argv[i])=='-' || c=='?') setargflags(argv[i]);
     else if(++j==1) strncpy(quellname,argv[i],200);
     else if(j==2) strncpy(zielname,argv[i],200);
    }
  }
 if(argflag['?'] || argflag['H'] || j<1 || j>MAXARG)
        {printf("%s  %s\n",argv[0],VERSION);
	 printf("Anwendung: %s [-Flags] Quelle.stl [Ziel.bmf]\n",argv[0]);
	 printf("     oder: %s [-Flags] Liste.txt Ziel.bmf\n",argv[0]);
	 printf("  Flags: x = Skalierfaktor, z.B: verkleinern: -x0.1 vergroessern: -x2.5\n");
	 printf("         s = SmoothFlag fuer Normalen, fuer abgerundete Kanten\n");
	 printf("         m = Mehrere Quelldateien einlesen, wobei die Namen der\n");
	 printf("             STL-Dateien dann in Liste.txt stehen muessen\n");
	 if(argflag['H']==0)
	  {printf("             (optional noch mit zusaetzlichen Farbinformationen).\n");
	   printf("         h = ausfuehrlichere Hilfe\n");
	  }
	 else
	  {printf("In Liste.txt koennen dann noch zusaetzliche Farbinformationen sthen:\n");
	   printf("farbname: r, g, b  //Zahlen 0.0 bis 1.0 oder Integer 0 bis 255\n");
	   printf("shininess: Fliesszahl (Groessenordnung 4.0)\n");
	   printf("scale: Fliesszahl (Skalierfaktor)\n");
	   printf("offset: x, y, z  //3 Fliesszahlen\n");
	   printf("\n");
	   printf("Beispiel:\n");
	   printf("figur.stl\n");
	   printf("diffuse: 180, 125, 73   //Farbe des Objekts\n");
	   printf("specular: 0.4, 0.2, 0.1 //Farbe der Reflexion\n");
	   printf("emissive: 0, 0, 0       //Selbst leuchtende Farbe\n");
	   printf("shininess: 4.0          //Staerke der Reflexion\n");
	   printf("wuerfel.stl\n");
	   printf("scale: 0.2\n");
	   printf("offset: 2.0, 0.0, 1.0\n");
	   printf("...\n");
	  }
	 exit(0);
	}
 if(!ist_stlname(quellname)) argflag['M']=1;
 if(*zielname==0)
  {
   if(argflag['M']==0)
    {
     for(i=0; (c=quellname[i])!=0 && c!='.'; i++)
      {zielname[i]=quellname[i];}
     zielname[i++]='.';
     zielname[i++]='b';
     zielname[i++]='m';
     zielname[i++]='f';
     zielname[i]=0;
    }
   else
    {
     for(;;)
      {
       printf("Zieldatei: "); scanf("%s",zielname);
       if(schonvorhanden(zielname)) printf("Datei \"%s\" schon vorhanden\n",zielname);
       else break;
      }
    }
   printf("Zieldatei: \"%s\"\n",zielname);//test
  }
 if((fp1=fopen(quellname,"rb"))==NULL)
  {printf("Datei \"%s\" nicht vorhanden\n",quellname); return 1;}
 if((fp2=fopen(zielname,"wb"))==NULL)
  {printf("Konnte Datei \"%s\" nicht erstellen\n",zielname); fclose(fp1); return 2;}
 if(argflag['M'])
  {
   FILE *fpquelle=NULL;
   char zeile[200];
   while(getline(fp1,zeile,200))
    {
     if(ist_stlname(zeile))
      {
       if(fpquelle!=NULL) {++numMeshes; unterprog(fpquelle,fp2); fclose(fpquelle);}
       fpquelle=fopen(zeile,"rb");
       if(fpquelle==NULL) {printf("Datei \"%s\" nicht gefunden\n",zeile); continue;}
       printf("Datei %s wird eingelesen...\n",zeile);//test
      }
     else if(strncmp(zeile,"diffuse:",8)==0)
      {
       farbe_einlesen(zeile,diffuse);
      }
     else if(strncmp(zeile,"specular:",9)==0)
      {
       farbe_einlesen(zeile,specular);
      }
     else if(strncmp(zeile,"emissive:",9)==0)
      {
       farbe_einlesen(zeile,emissive);
      }
     else if(strncmp(zeile,"shininess:",10)==0)
      {
       sscanf(&zeile[10],"%f",&shininess);
      }
     else if(strncmp(zeile,"scale:",6)==0)
      {
       sscanf(&zeile[6],"%f",&scalefaktor);
       printf("scalefaktor = %f\n",scalefaktor);//test
      }
     else if(strncmp(zeile,"offset:",7)==0)
      {
       drei_floats_einlesen(&zeile[7],offset);
       printf("offset = {");//test
       for(int i=0;i<3;i++) {printf(i==2 ? "%f" : "%f, ", offset[i]);}//test
       printf("}\n");//test
      }
    }
   if(fpquelle!=NULL) {++numMeshes; unterprog(fpquelle,fp2); fclose(fpquelle);}
   if(numMeshes>1)
    {
     //am Anfang der geschriebenen Datei noch Anzahl Meshs ueberschreiben
     fclose(fp2);
     fp2=fopen(zielname,"rb+");
     if(fp2==NULL) printf("Fehler: wiederoeffnen von \"%s\" misslungen\n",zielname);
     else
      {
       output_uint64(fp2,numMeshes); //am Dateianfang ueberschreiben
      }
    }
  }
 else
  {
   numMeshes=1;
   unterprog(fp1,fp2);
  }
 fclose(fp1);
 fclose(fp2);
 return 0;
}// ende von main

uint16 input_uint16(FILE *fp)
{
 uint16 c;
 c = getc(fp) & 0xFF;
 c += ((getc(fp)&0xFF)<<8);
 return c;
}

uint32 input_uint32(FILE *fp)
{
 uint32 c;
 c = getc(fp) & 0xFF;
 c += ((getc(fp)&0xFF)<<8);
 c += ((getc(fp)&0xFF)<<16);
 c += ((getc(fp)&0xFF)<<24);
 return c;
}

float input_float(FILE *fp)
{
 union {float f; uint32 c;} fc;
 fc.c = getc(fp) & 0xFF;
 fc.c += ((getc(fp)&0xFF)<<8);
 fc.c += ((getc(fp)&0xFF)<<16);
 fc.c += ((getc(fp)&0xFF)<<24);
 return fc.f;
}

void input_vektor(FILE *fp,float* vektor,float scale)
{
 // Umrechnung Koordinatensystem von STL zum Koordinatensystem von OpenGL:
 vektor[2] = input_float(fp)*scale; //Achse z nach vorne in OpenGL, ist Achse x nach rechts in STL
 vektor[0] = input_float(fp)*scale; //Achse x nach rechts in OpengGL, ist Achse y nach hinten in STL
 vektor[1] = input_float(fp)*scale; //senkrechte Achse ist in OpenGL y, in STL aber z
}

void unterprog(FILE *fp1,FILE *fp2)
{
 int c;
 uint32 i;
 char kopfdaten[80];
 printf("STL-Datei wird nach BMF konvertiert...\n");//test
 for(i=0;i<5;i++) kopfdaten[i]=getc(fp1);
 bool asciiflag=(strncmp(kopfdaten,"solid",5)==0);
 Dreieck *dreiecke=NULL;
 uint32 numDreiecke=0;
 if(asciiflag)
  {
   //STL-Datei im ASCII-Format einlesen:
   for(;i<80-1 && (c=kopfdaten[i])!=0 && c!='\n'; i++) kopfdaten[i]=getc(fp1);
   kopfdaten[i]=0;
   char zeile[200], *pz;
   i=0;
   int vnr=0;
   while(getline(fp1,zeile,200))
    {
     float x,y,z;
     for(pz=zeile; *pz==' '; pz++) {} //Leerzeichen ueberlesen
     if(strncmp(pz,"endsolid",8)==0) break;
     if(strncmp(pz,"facet normal",12)==0)
      {
       sscanf(&pz[12],"%f %f %f",&x,&y,&z);
       //Umrechnung des Koordinatensystems von STL nach OpenGL:
       dreiecke[i].normalen[0]=y;
       dreiecke[i].normalen[1]=z;
       dreiecke[i].normalen[2]=x;
      }
     else if(strncmp(pz,"endfacet",8)==0)
      {
       i++;
       vnr=0;
      }
     //else if(strncmp(pz,"outer loop",10)==0) {}
     else if(strncmp(pz,"endloop",7)==0)
      {
       //TODO: ueberpruefen ob ev. mehrere "outer loop" .. "endloop" vorkommen
       numDreiecke++;
       vnr=0;
      }
     else if(strncmp(pz,"vertex",6)==0)
      {
       float xstl,zstl,ystl;
       sscanf(&pz[6],"%f %f %f",&xstl,&ystl,&zstl);
       // Umrechnung Koordinatensystem von STL zum Koordinatensystem von OpenGL:
       x = ystl*scalefaktor; //Achse x nach rechts in OpengGL, ist Achse y nach hinten in STL
       y = zstl*scalefaktor; //senkrechte Achse ist in OpenGL y, in STL aber z
       z = xstl*scalefaktor; //Achse z nach vorne in OpenGL, ist Achse x nach rechts in STL
       if(++vnr==1)
	{
	 dreiecke[i].vertex1[0]=x;
	 dreiecke[i].vertex1[1]=y;
	 dreiecke[i].vertex1[2]=z;
	}
       else if(vnr==2)
	{
	 dreiecke[i].vertex2[0]=x;
	 dreiecke[i].vertex2[1]=y;
	 dreiecke[i].vertex2[2]=z;
	}
       else if(vnr==3)
	{
	 dreiecke[i].vertex3[0]=x;
	 dreiecke[i].vertex3[1]=y;
	 dreiecke[i].vertex3[2]=z;
	}
       else printf("Fehler: zu viele Vertexe pro Dreieck\n");//test
      }
    }
  }
 else
  {
   //Binaere STL-Datei einlesen:
   for(;i<80;i++) kopfdaten[i]=getc(fp1);
   numDreiecke = input_uint32(fp1);
   dreiecke = new Dreieck[numDreiecke];
   for(i=0;i<numDreiecke;i++)
    {
     input_vektor(fp1,dreiecke[i].normalen, 1.0);
     input_vektor(fp1,dreiecke[i].vertex1, scalefaktor); //Einlesen mit Skalierung
     input_vektor(fp1,dreiecke[i].vertex2, scalefaktor);
     input_vektor(fp1,dreiecke[i].vertex3, scalefaktor);
     dreiecke[i].attribut = input_uint16(fp1);
    }
  }
 if(dreiecke==NULL) {printf("Fehler: keine Dreiecke eingelesen\n");}
 else
  {
   save_bmf(fp2,dreiecke,numDreiecke);
   delete[] dreiecke;
  }
}

/*********************** Neue Datei schreiben *************************/
struct Position {
 float x,y,z;
};

void output_uint32(FILE *fp, uint32 u)
{
 for(int i=0;i<4;i++)
  {
   fputc(u&0xFF, fp);
   u >>= 8;
  }
}

void output_uint64(FILE *fp, uint64 u)
{
 for(int i=0;i<8;i++)
  {
   fputc(u&0xFF, fp);
   u >>= 8;
  }
}

void output_float(FILE *fp, float fx)
{
 union {float f; uint32 c;} fc;
 fc.f = fx;
 fputc(fc.c&0xFF, fp);
 fputc((fc.c>>8)&0xFF, fp);
 fputc((fc.c>>16)&0xFF, fp);
 fputc((fc.c>>24)&0xFF, fp);
}

void output_vec3(FILE *fp, float* data)
{
 output_float(fp, data[0]);
 output_float(fp, data[1]);
 output_float(fp, data[2]);
}

Position get_vertex(float *p)
{
 Position vertex;
 vertex.x = p[0];
 vertex.y = p[1];
 vertex.z = p[2];
 return vertex;
}

Vektor3f get_vektor3f(float *p)
{
 Vektor3f vekt;
 vekt.x = p[0];
 vekt.y = p[1];
 vekt.z = p[2];
 return vekt;
}

uint32 vertex_suchen(uint32 imax, std::vector<Position>& positions, Position& vertex)
{
 for(uint32 i=0;i<imax;i++)
  {
   if(positions[i].x == vertex.x
      && positions[i].y == vertex.y
      && positions[i].z == vertex.z)
    //TODO: Rundungsfehler ausgleichen
    return i+1;
  }
 return 0;
}

void save_bmf(FILE *fp,Dreieck *dreiecke,uint32 numDreiecke)
{
 uint32 i, index, index1, index2, index3;
 uint32 next_index=1;
 uint64 numVertices=0;
 uint64 numIndices=0;
 std::vector<Position> positions;
 //std::vector<Position> normals;
 std::vector<Vektor3f> normals;
 std::vector<uint32> indices;
 Position vertex1,vertex2,vertex3;
 bool normalenSmoothFlag = argflag['S'];

 if(numMeshes==1) output_uint64(fp,numMeshes); //spaeter noch ueberschreiben
 output_vec3(fp, diffuse);
 output_vec3(fp, specular);
 output_vec3(fp, emissive);
 output_float(fp, shininess);
 
 printf("numDreiecke=%d\n",numDreiecke);//test
 for(i=0;i<numDreiecke;i++)
  {
   Vektor3f normalvek = get_vektor3f(dreiecke[i].normalen);
   vertex1 = get_vertex(dreiecke[i].vertex1);
   if(normalenSmoothFlag)
        index1 = vertex_suchen(numVertices, positions, vertex1);
   else index1 = 0;
   if(index1==0)
    {
     positions.push_back(vertex1); numVertices++;
     index1 = next_index++;
     normals.push_back(normalvek);
    }
   else
    {
     normals[index1-1] += normalvek; //aufsummieren der Normalen-Vektoren
    }
   indices.push_back(index1-1);
   
   vertex2 = get_vertex(dreiecke[i].vertex2);
   if(normalenSmoothFlag)
        index2 = vertex_suchen(numVertices, positions, vertex2);
   else index2 = 0;
   if(index2==0)
    {
     positions.push_back(vertex2); numVertices++;
     index2 = next_index++;
     normals.push_back(normalvek);
    }
   else
    {
     normals[index2-1] += normalvek;
    }
   indices.push_back(index2-1);
   
   vertex3 = get_vertex(dreiecke[i].vertex3);
   if(normalenSmoothFlag)
        index3 = vertex_suchen(numVertices, positions, vertex3);
   else index3 = 0;
   if(index3==0)
    {
     positions.push_back(vertex3); numVertices++;
     index3 = next_index++;
     normals.push_back(normalvek);
    }
   else
    {
     normals[index3-1] += normalvek;
    }
   indices.push_back(index3-1);
   
   numIndices += 3;
  }
 if(normalenSmoothFlag)
  {
   //die Normalenvektoren wieder zu Eiheitsvektoren machen, entspricht Durchschnitt von Aufsummierung
   for(i=0;i<next_index-1;i++) {normals[i].einheitsvektor();}
  }
 //numVertices = positions.size(); //waere etwas einfacher
 //numIndices = indices.size(); //waere etwas einfacher
 //printf("positions.size(): %ld\n",positions.size());//test
 //printf("indices.size(): %ld\n",indices.size());//test
 printf("normals.size(): %ld\n",normals.size());//test
 printf("numVertices=%ld\n",numVertices);//test
 printf("numIndices=%ld\n",numIndices);//test
 output_uint64(fp,numVertices);
 output_uint64(fp,numIndices);
 for(uint64 i=0; i<numVertices; i++)
  {
   output_float(fp, positions[i].x+offset[0]);
   output_float(fp, positions[i].y+offset[1]);
   output_float(fp, positions[i].z+offset[2]);
   output_float(fp, normals[i].x);
   output_float(fp, normals[i].y);
   output_float(fp, normals[i].z);
  }
 for(uint64 i=0; i<numIndices; i++)
  {
   output_uint32(fp, indices[i]);
  }
}
