#include "Model3D.h"

#include "Animation3D.h"
#include "Bild.h"
#include "DXBuffer.h"
#include "Model2D.h"
#include "Textur.h"
#include "Welt3D.h"
#ifdef WIN32
#    include <d3d11.h>
#endif
#include <stdexcept>

using namespace Framework;

// Inhalt der Knochen Klasse

// Konstruktor
Knochen::Knochen(int id)
{
    pos = Vec3<float>(0, 0, 0);
    winkel = Vec3<float>(0, 0, 0);
    geschwister = 0;
    kinder = 0;
    this->id = id;
}

// Destruktor
Knochen::~Knochen()
{
    delete geschwister;
    delete kinder;
}

// public

// Setzt die Position des Knochens relativ zum Model Ursprung
//  pos: Die Position
void Knochen::setPosition(Vec3<float>& pos)
{
    this->pos = pos;
}

// Setzt die Drehung des Knochens relativ zum Model Ursprung
//  winkel: Ein Vektor der die Drehung um die verschiedenen Achsen als
//  Komponenten hat
void Knochen::setDrehung(Vec3<float>& winkel)
{
    this->winkel = winkel;
}

// Fügt dem Knochen ein Geschwister Knochen hinzu
//  k: Der Knochen, der hinzugefügt werden soll
void Knochen::addGeschwisterKnochen(Knochen* k)
{
    if (!geschwister)
        geschwister = k;
    else
        geschwister->addGeschwisterKnochen(k);
}

// Fügt einem bestimmten Knochen ein Kind Knochen hinzu
//  id: Die id des Knochens, wo der Knochen als Kind hinzugefügt werden soll
//  k: Der Knochen, der hinzugefügt werden soll
void Knochen::addKind(int id, Knochen* k)
{
    if (this->id == id)
    {
        if (!kinder)
            kinder = k;
        else
            kinder->addGeschwisterKnochen(k);
    }
    else
    {
        if (kinder)
            kinder->addKind(id, k);
        else
        {
            Text err = "Es wurde kein Knochen mit der Id: ";
            err += id;
            err += " im Skelett gefunden, um ein Kind Knochen hinzuzufügen. "
                   "Datei:" __FILE__ ", Zeile: ";
            err += __LINE__;
            err += "!";
            delete k;
            throw std::out_of_range(err);
        }
    }
}

// Berechnet die Matrizen des Knochen und die von all seinen Geschwister Knochen
// und Kind Knochen
//  elternMat: Die fertig berechnete Matrix des Elternknochens
//  matBuffer: Ein Array, in dem alle berechneten Matrizen gespeichert werden
//  sollen scaleFactor: Die Skallierung des Modells kamMatrix: Die vereiniegung
//  der view und projektions Matrizen
void Knochen::kalkulateMatrix(Mat4<float>& elternMat,
    Mat4<float>* matBuffer,
    float scaleFactor,
    Mat4<float>& kamMat)
{
    if (geschwister)
        geschwister->kalkulateMatrix(elternMat, matBuffer, scaleFactor, kamMat);
    matBuffer[id] = matBuffer[id].translation(pos * scaleFactor)
                  * matBuffer[id].rotationZ(winkel.z)
                  * matBuffer[id].rotationX(winkel.x)
                  * matBuffer[id].rotationY(winkel.y)
                  * matBuffer[id].scaling(scaleFactor);
    matBuffer[id] = elternMat * matBuffer[id];
    if (kinder)
        kinder->kalkulateMatrix(matBuffer[id], matBuffer, scaleFactor, kamMat);
    matBuffer[id] = kamMat * matBuffer[id];
}

Knochen* Framework::Knochen::zGeschwister() const
{
    return geschwister;
}

Knochen* Framework::Knochen::zKind() const
{
    return kinder;
}

// Kopiert den Knochen mit allen Geschwister Knochen und Kind Knochen
Knochen* Knochen::kopiereKnochen() const
{
    Knochen* ret = new Knochen(id);
    ret->pos = pos;
    ret->winkel = winkel;
    if (geschwister) ret->geschwister = geschwister->kopiereKnochen();
    if (kinder) ret->kinder = kinder->kopiereKnochen();
    return ret;
}

// Gibt die Id des Knochens zurück
int Knochen::getId() const
{
    return id;
}

// Gibt die Drehung des Knochens zurück
Vec3<float> Knochen::getDrehung() const
{
    return winkel;
}

// Gibt die Position des Knochens zurück
Vec3<float> Knochen::getPosition() const
{
    return pos;
}

// Gibt den Radius des Knochens zurück
float Knochen::getRadius() const
{
    float r = pos.getLength();
    if (geschwister) r = MAX(r, geschwister->getRadius());
    if (kinder) r += kinder->getRadius();
    return r;
}

// Inhalt der Skelett Klasse

// Konstruktor
Skelett::Skelett()
    : ReferenceCounter()
{
    k = 0;
    nextId = 0;
}

// Destruktor
Skelett::~Skelett()
{
    if (k) delete k;
}

// Gibt die Id des nächsten Knochens zurück und berechnet die neue Id für den
// Knochen danach Es können maximal MAX_KNOCHEN_ANZ Knochen für ein Skelett
// existieren. Wenn diese Zahl überschritten wird, so wird -1 zurückgegeben
int Skelett::getNextKnochenId()
{
    return nextId++;
}

void Framework::Skelett::setNextKnochenId(int id)
{
    nextId = id;
}

// Fügt dem Skellet einen Knochen hinzu
//  k: Der Knochen
//  elternId: Die Id des Eltern Knochens. Wenn der Knochen kein Elternknochen
//  besitzt, kannder Parameter weggelassen werden.
void Skelett::addKnochen(Knochen* k, int elternId)
{
    if (!this->k)
        this->k = k;
    else
        this->k->addKind(elternId, k);
    if (k->getId() >= nextId) nextId = k->getId() + 1;
}

// Berechnet die Matrizen der Knochen
//  modelMatrix: Die Matrix, die das Skelett in den Raum der Welt transformiert
//  matBuffer: Ein Array von Matrizen, der durch die Knochen Matrizen gefüllt
//  wird scaleFactor: Die skallierung des Objektes kamMatrix: Die vereiniegung
//  der view und projektions Matrizen return: gibt die Anzahl der verwendeten
//  Matrizen zurück
int Skelett::kalkulateMatrix(Mat4<float>& modelMatrix,
    Mat4<float>* matBuffer,
    float scaleFactor,
    Mat4<float>& kamMatrix)
{
    k->kalkulateMatrix(modelMatrix, matBuffer, scaleFactor, kamMatrix);
    return nextId;
}

// Berechnet den Radius des Skeletts
float Skelett::getRadius() const
{
    if (k) return k->getRadius();
    return 0;
}

// gibt den Wurzel Knochen zurück
Knochen* Framework::Skelett::zKnochen() const
{
    return k;
}

// Kopiert das Skelett
Skelett* Skelett::kopiereSkelett() const
{
    Skelett* ret = new Skelett();
    ret->nextId = nextId;
    if (k) ret->addKnochen(k->kopiereKnochen());
    return ret;
}

int Framework::Skelett::zNextKnochenId() const
{
    return nextId;
}

// Inhalt des Polygon3D Struct

// Konstruktor
Polygon3D::Polygon3D()
{
    indexAnz = 0;
    indexList = 0;
}

// Destruktor
Polygon3D::~Polygon3D()
{
    delete[] indexList;
}

// Inhalt der Model3DData Klasse

// Konstruktor
Model3DData::Model3DData(
    DXBuffer* dxVertexBuffer, DXBuffer* dxIndexBuffer, int id)
    : ReferenceCounter(),
      dxIndexBuffer(dxIndexBuffer),
      dxVertexBuffer(dxVertexBuffer),
      id(id)
{
    skelett = 0;
    vertexList = 0;
    vertexCount = 0;
    polygons = new Array<Polygon3D*>();
    ambientFactor = 1.f;
    diffusFactor = 0.f;
    specularFactor = 0.f;
    indexCount = 0;
    indexBuffer = 0;
    radius = 0;
}

// Destruktor
Model3DData::~Model3DData()
{
    clearModel();
    polygons->release();
    dxIndexBuffer->release();
    dxVertexBuffer->release();
    delete[] indexBuffer;
}

// updates the DX Buffer gpu memory if changed
DLLEXPORT void Model3DData::updateGPUMemory()
{
    dxIndexBuffer->copieren();
    dxVertexBuffer->copieren();
}

// Löscht alle Model daten
void Model3DData::clearModel()
{
    delete[] vertexList;
    vertexCount = 0;
    vertexList = 0;
    for (Polygon3D* i : *polygons)
        delete i;
    polygons->leeren();
    if (skelett) skelett->release();
    skelett = 0;
    radius = 0;
    delete[] indexBuffer;
    indexBuffer = 0;
    indexCount = 0;
}

// Berechnet die normalen für die Eckpunkte des Modells
void Model3DData::calculateNormals()
{
    for (int i = 0; i < vertexCount; i++)
    {
        Vec3<float> normal(0, 0, 0);
        for (Polygon3D* p : *polygons)
        {
            int begin = 0;
            for (int j = 0; j < p->indexAnz; j++)
            {
                if (j % 3 == 0) begin = j;
                if (p->indexList[j] == i)
                {
                    Vec3<float> a = vertexList[p->indexList[begin]].pos;
                    Vec3<float> b = vertexList[p->indexList[begin + 1]].pos;
                    Vec3<float> c = vertexList[p->indexList[begin + 2]].pos;
                    normal += (b - a).crossProduct(c - a).normalize();
                    normal.normalize();
                }
            }
        }
        vertexList[i].normal = normal;
    }
}

//! Erstellt einen buffer für alle polygon indizes
void Model3DData::buildIndexBuffer()
{
    delete[] indexBuffer;
    indexCount = 0;
    for (Polygon3D* p : *polygons)
        indexCount += p->indexAnz;
    indexBuffer = new int[indexCount];
    int current = 0;
    for (Polygon3D* p : *polygons)
    {
        memcpy(indexBuffer + current, p->indexList, sizeof(int) * p->indexAnz);
        current += p->indexAnz;
    }
    dxIndexBuffer->setLength((int)(indexCount * sizeof(int)));
    dxIndexBuffer->setData(indexBuffer);
}

// Setzt den Zeiger auf ein standartmäßig verwendete Skelett
//  s: Das Skelett, das verwendet werden soll
void Model3DData::setSkelettZ(Skelett* s)
{
    if (skelett) skelett->release();
    skelett = s;
}

// Setzt einen Zeiger auf eine Liste mit allen Vertecies des Models
//  vertexList: Ein Array mit Vertecies
//  anz: Die Anzahl der Vertecies im Array
void Model3DData::setVertecies(Vertex3D* vertexList, int anz)
{
    delete[] this->vertexList;
    this->vertexList = vertexList;
    vertexCount = anz;
    maxPos = {-INFINITY, -INFINITY, -INFINITY};
    minPos = {INFINITY, INFINITY, INFINITY};
    radius = 0;
    for (int i = 0; i < anz; i++)
    {
        float r = vertexList[i].pos.getLength();
        if (r > radius) radius = r;
        if (vertexList[i].pos.x < minPos.x) minPos.x = vertexList[i].pos.x;
        if (vertexList[i].pos.y < minPos.y) minPos.y = vertexList[i].pos.y;
        if (vertexList[i].pos.z < minPos.z) minPos.z = vertexList[i].pos.z;
        if (vertexList[i].pos.x > maxPos.x) maxPos.x = vertexList[i].pos.x;
        if (vertexList[i].pos.y > maxPos.y) maxPos.y = vertexList[i].pos.y;
        if (vertexList[i].pos.z > maxPos.z) maxPos.z = vertexList[i].pos.z;
        vertexList[i].id = i;
    }
    dxVertexBuffer->setLength((int)(anz * sizeof(Vertex3D)));
    dxVertexBuffer->setData(vertexList);
}

// Fügt ein Polygon zum Model hinzu
//  polygon: Das Polygon, das hinzugefügt erden soll
void Model3DData::addPolygon(Polygon3D* polygon)
{
    polygons->add(polygon);
    buildIndexBuffer();
}

// Git den Factor an, mit dem das umgebungslicht (textur farbe) multipliziert
// wird
//  f: der neue Faktor (von 0 bis 1, ambient + specular + diffuse = 1)
void Model3DData::setAmbientFactor(float f)
{
    ambientFactor = f;
}

// Git den Factor an, mit dem die Lichtfarbe von Lichtquellen multipliziert wird
//  f: der neue Faktor (von 0 bis 1, ambient + specular + diffuse = 1)
void Model3DData::setDiffusFactor(float f)
{
    diffusFactor = f;
}

// Git den Factor an, mit dem die Reflektion von Lichtquellen multipliziert wird
//  f: der neue Faktor (von 0 bis 1, ambient + specular + diffuse = 1)
void Model3DData::setSpecularFactor(float f)
{
    specularFactor = f;
}

// Konvertiert ein 2d Model zu 3D
//  model: Das 2d Model, das zu 3d konvertiert werden soll
//  z: Die z koordinate aller punkte des Models
void Model3DData::copyModel2D(Model2DData* model, float z)
{
    if (model && model->vListen && model->polygons)
    {
        clearModel();
        int vAnz = 0;
        for (const Polygon2D& p : *model->polygons)
            vAnz += p.vertex->getEintragAnzahl();
        Vertex3D* vertexList = new Vertex3D[vAnz];
        int index = 0;
        for (auto i : *model->vListen)
        {
            Polygon3D* p = new Polygon3D();
            p->indexAnz = 0;
            for (auto j : *i)
            {
                for (auto k = j->zListe()->begin();
                     k && k.hasNext() && k.next().hasNext();
                     k++)
                    p->indexAnz += 3;
            }
            p->indexList = new int[p->indexAnz];
            p->indexAnz = 0;
            for (auto j : *i)
            {
                for (auto k = j->zListe()->begin(); k; k++)
                {
                    assert(index < vAnz);
                    if (index < vAnz)
                    {
                        vertexList[index].pos
                            = Vec3<float>(k->punkt->x, k->punkt->y, z);
                        vertexList[index].tPos = (Vec2<float>)*k->textur;
                        if (k.hasNext() && k.next().hasNext())
                        {
                            p->indexList[p->indexAnz] = index;
                            p->indexAnz++;
                            p->indexList[p->indexAnz] = index + 1;
                            p->indexAnz++;
                            p->indexList[p->indexAnz] = index + 2;
                            p->indexAnz++;
                        }
                    }
                    else
                        break;
                    index++;
                }
            }
            addPolygon(p);
        }
        this->setVertecies(vertexList, vAnz);
        buildIndexBuffer();
        calculateNormals();
    }
}

// Entfernt ein Polygon
//  index: Der Index des Polygons
void Model3DData::removePolygon(int index)
{
    if (!polygons->hat(index)) return;
    delete polygons->get(index);
    polygons->remove(index);
    buildIndexBuffer();
}

// Berechnet die Matrizen der Knochen
//  modelMatrix: Die Matrix, die das Skelett in den Raum der Welt transformiert
//  matBuffer: Ein Array von Matrizen, der durch die Knochen Matrizen gefüllt
//  wird scaleFactor: Die Skallierung des Modells kamMatrix: Die vereiniegung
//  der view und projektions Matrizen return: gibt die Anzahl der verwendeten
//  Matrizen zurück
int Model3DData::kalkulateMatrix(Mat4<float>& modelMatrix,
    Mat4<float>* matBuffer,
    float scaleFactor,
    Mat4<float>& kamMatrix) const
{
    if (!skelett) return 0;
    return skelett->kalkulateMatrix(
        modelMatrix, matBuffer, scaleFactor, kamMatrix);
}

// Gibt die Anzahl an Polygonen zurück
int Model3DData::getPolygonAnzahl() const
{
    return polygons->getEintragAnzahl();
}

// Gibt ein bestimmtes Polygon zurück
//  index: Der Index des Polygons
Polygon3D* Model3DData::getPolygon(int index) const
{
    if (!polygons->hat(index)) return 0;
    return polygons->get(index);
}

// Gibt einen Iterator zurück, mit dem sich die Polygons auflisten lassen
Iterator<Polygon3D*> Model3DData::getPolygons() const
{
    return polygons->begin();
}

// Gibt den radius einer Kugel zurück, die das gesammte Model umschließt
float Model3DData::getRadius() const
{
    return radius;
}

// Gibt die Id der Daten zurück, wenn sie in einer Model3DList registriert
// wurden. (siehe Framework::zM3DRegister())
int Model3DData::getId() const
{
    return id;
}

// Git den Factor an, mit dem das umgebungslicht (textur farbe) multipliziert
// wird
float Model3DData::getAmbientFactor() const
{
    return ambientFactor;
}

// Git den Factor an, mit dem die Lichtfarbe von Lichtquellen multipliziert wird
float Model3DData::getDiffusFactor() const
{
    return diffusFactor;
}

// Git den Factor an, mit dem die Reflektion von Lichtquellen multipliziert wird
float Model3DData::getSpecularFactor() const
{
    return specularFactor;
}

// Gibt eine Kopie des Skeletts zurück, welches für annimationen verwendet
// werden kann
Skelett* Model3DData::copySkelett() const
{
    return skelett ? skelett->kopiereSkelett() : 0;
}

// Gibt die Anzahl an Vertices zurück
int Model3DData::getVertexAnzahl() const
{
    return vertexCount;
}

// Gibt einen Buffer mit allen Vertecies des Models zurück
const Vertex3D* Model3DData::zVertexBuffer() const
{
    return vertexList;
}

//! Gibt eine refferenz auf den beginn des indexBuffers zurück
const int* Model3DData::getIndexBuffer() const
{
    return indexBuffer;
}

//! Gibt eine die Anzahl der indizes im indexBuffer zurück
int Model3DData::getIndexCount() const
{
    return indexCount;
}

//! Gibt den Index buffer zurück;
DXBuffer* Model3DData::zDXIndexBuffer() const
{
    return dxIndexBuffer;
}

//! Gibt den Vertex buffer zurück;
DXBuffer* Model3DData::zDXVertexBuffer() const
{
    return dxVertexBuffer;
}

//! gibt den minnimalen Punkt der Bounding box des Models zurück
Vec3<float> Model3DData::getMinPos() const
{
    return minPos;
}

//! gibt den maximalen Punkt der bounding box des Mopdels zurück
Vec3<float> Model3DData::getMaxPos() const
{
    return maxPos;
}

// Inhalt der Model3DTextur

// Konstruktor
Model3DTextur::Model3DTextur()
    : ReferenceCounter()
{
    textures = new Textur*[1];
    textures[0] = 0;
    textureCount = 1;
}

// Destruktor
Model3DTextur::~Model3DTextur()
{
    for (int i = 0; i < textureCount; i++)
    {
        if (textures[i]) textures[i]->release();
    }
    delete[] textures;
}

// Legt fest, welche Textur für welches Polygon ist
//  pI: Der Index des Polygons
//  txt: Die Textur des Polygons
void Model3DTextur::setPolygonTextur(int pI, Textur* txt)
{
    if (pI >= textureCount)
    {
        Textur** tmp = textures;
        textures = new Textur*[pI + 1];
        memcpy(textures, tmp, sizeof(Textur*) * textureCount);
        memset(textures + textureCount,
            0,
            sizeof(Textur*) * (pI + 1 - textureCount));
        delete[] tmp;
        textureCount = pI + 1;
    }
    if (textures[pI]) textures[pI]->release();
    textures[pI] = txt;
}

// Gibt einen Zeiger auf die Textur eines Polygons zurück ohne erhöhten
// Reference Counter
//  i: Der Index des Polygons
Textur* Model3DTextur::zPolygonTextur(int i) const
{
    return textures[i];
}

// Inhalt der AnimationData Struktur
Model3D::AnimationData* Model3D::AnimationData::getThis()
{
    return this;
}

Model3D::AnimationData* Model3D::AnimationData::release()
{
    a->release();
    delete this;
    return 0;
}

// Inhalt der Model3D Klasse
// Konstruktor
Model3D::Model3D()
    : Zeichnung3D()
{
    model = 0;
    textur = 0;
    skelett = 0;
    animations = new RCArray<AnimationData>();
    ambientFactor = 1.f;
    diffusFactor = 0.f;
    specularFactor = 0.f;
}

// Destruktor
Model3D::~Model3D()
{
    if (model) model->release();
    if (textur) textur->release();
    if (skelett) skelett->release();
    animations->release();
}

// Fügt eine Animation hinzu
//  a: Die neue Animation
void Model3D::addAnimation(Animation3D* a, double speed)
{
    AnimationData* d = new AnimationData();
    d->a = a;
    d->speed = speed;
    d->offset = 0;
    animations->add(d);
}

// Entfernt eine Animation
//  zA: Die zu entfernende Animation
void Model3D::removeAnimation(Animation3D* zA)
{
    for (int i = 0; i < animations->getEintragAnzahl(); i++)
    {
        if (animations->z(i)->a == zA)
        {
            animations->remove(i);
            return;
        }
    }
}

// Setzt die Daten des Models
//  data: Die Daten
void Model3D::setModelDaten(Model3DData* data)
{
    if (model) model->release();
    if (skelett) skelett = (Skelett*)skelett->release();
    model = data;
    if (model)
    {
        skelett = model->copySkelett();
        this->ambientFactor = model->getAmbientFactor();
        this->specularFactor = model->getSpecularFactor();
        this->diffusFactor = model->getDiffusFactor();
    }
}

// Setzt die zum Zeichnen zu benutzenden Texturen
//  txt: Ein Liste mit Texturen zu den verschiedenen Polygonen zugeordnet
void Model3D::setModelTextur(Model3DTextur* txt)
{
    if (textur) textur->release();
    textur = txt;
}

// Git den Factor an, mit dem das umgebungslicht (textur farbe) multipliziert
// wird
//  f: der neue Faktor (von 0 bis 1, ambient + specular + diffuse = 1)
void Framework::Model3D::setAmbientFactor(float f)
{
    this->ambientFactor = f;
}

// Git den Factor an, mit dem die Lichtfarbe von Lichtquellen multipliziert wird
//  f: der neue Faktor (von 0 bis 1, ambient + specular + diffuse = 1)
void Framework::Model3D::setDiffusFactor(float f)
{
    diffusFactor = f;
}

// Git den Factor an, mit dem das umgebungslicht (textur farbe) multipliziert
// wird
//  f: der neue Faktor (von 0 bis 1, ambient + specular + diffuse = 1)
void Framework::Model3D::setSpecularFactor(float f)
{
    specularFactor = f;
}

// Errechnet die Matrizen aller Knochen des Skeletts des Models
//  viewProj: Die miteinander multiplizierten Kameramatrizen
//  matBuffer: Ein Array mit Matrizen, der gefüllt werden soll
//  return: Die Anzahl der Matrizen, die das Model benötigt
int Model3D::errechneMatrizen(Mat4<float>& viewProj, Mat4<float>* matBuffer)
{
    int ret = 0;
    if (skelett)
        ret = skelett->kalkulateMatrix(welt, matBuffer, size, viewProj);
    else if (model)
        ret = model->kalkulateMatrix(welt, matBuffer, size, viewProj);
    if (!ret) return Zeichnung3D::errechneMatrizen(viewProj, matBuffer);
    return ret;
}

// Verarbeitet die vergangene Zeit
//  tickval: Die zeit in sekunden, die seit dem letzten Aufruf der Funktion
//  vergangen ist return: true, wenn sich das Objekt verändert hat, false
//  sonnst.
bool Model3D::tick(double tickval)
{
    radius = model ? model->getRadius() : 0;
    if (skelett)
    {
        radius += skelett->getRadius();
        for (auto i = animations->begin(); i && i._; i++)
        {
            rend = i->speed > 0;
            i->a->apply(skelett, i->offset, tickval * i->speed);
        }
    }
    return Zeichnung3D::tick(tickval);
}

//! zum aktualisieren der shader daten
void Model3D::beforeRender(
    GraphicsApi* api, Shader* zVertexShader, Shader* zPixelShader)
{}

void Model3D::afterRender(
    GraphicsApi* api, Shader* zVertexShader, Shader* zPixelShader)
{}

// Gibt die Textur zurück
Model3DTextur* Model3D::getTextur()
{
    return textur ? dynamic_cast<Model3DTextur*>(textur->getThis()) : 0;
}

// Gibt die Textur zurück (ohne erhöhten Reference Counter)
Model3DTextur* Model3D::zTextur()
{
    return textur;
}

// Gibt die ModelDaten zurück
Model3DData* Model3D::getModelData()
{
    return model ? dynamic_cast<Model3DData*>(model->getThis()) : 0;
}

// Gibt die ModelDaten zurück (ohne erhöhten Reference Counter)
Model3DData* Model3D::zModelData()
{
    return model;
}

// prüft, ob ein Strahl dieses Objekt trifft
//  point: der startpunkt des Strahls in Weltkoordinaten
//  dir: die Richtung des Strahls in Weltkoordinaten
//  maxSqDist: Die maximale quadratische distanz die erlaubt ist
//  pId: die Id des Polygons, zu dem der Schnittpunkt gehört
//  return: den quadratischen Abstand des Schnittpunktes zum Ursprung des
//  Strahls oder -1, wenn kein schnittpunkt existiert
float Model3D::traceRay(
    Vec3<float>& p, Vec3<float>& d, float maxSqDist, int& pId) const
{
    if (!model) return -1;
    Vec3<float> dir = d;
    dir.rotateY(-angle.y);
    dir.rotateX(-angle.x);
    dir.rotateZ(-angle.z);
    Vec3<float> point = p;
    point.rotateY(-angle.y);
    point.rotateX(-angle.x);
    point.rotateZ(-angle.z);
    point -= pos;
    float nearest = (-dir.x * point.x - dir.y * point.y - dir.z * point.z)
                  / (dir.x * dir.x + dir.y * dir.y + dir.z * dir.z);
    float dist = (point + dir * nearest).getLengthSq();
    if (dist > (radius * size) * (radius * size)
        || (dir * nearest).getLength() - radius * size > sqrt(maxSqDist)
        || (nearest < 0
            && (dir * nearest).getLengthSq()
                   > radius * size * radius
                         * size)) // es gibt kein schnittpunkt
        return -1;
    bool existsHit = 0;
    if (skelett)
    { // todo
    }
    else
    {
        int index = 0;
        for (auto p = model->getPolygons(); p; p++)
        {
            for (int j = 0; j < p->indexAnz; j++)
            {
                if (j % 3 == 0)
                {
                    Vec3<float> a = model->zVertexBuffer()[p->indexList[j]].pos;
                    Vec3<float> b
                        = model->zVertexBuffer()[p->indexList[j + 1]].pos;
                    Vec3<float> c
                        = model->zVertexBuffer()[p->indexList[j + 2]].pos;
                    Vec3<float> normal
                        = (b - a).crossProduct(c - a).normalize();
                    if (normal * dir < 0) // Prüfe ob die Normale in Richtung
                                          // des Strahl ursprungs zeigt
                    {
                        nearest
                            = (a * normal - point * normal) / (dir * normal);
                        Vec3<float> hit = point + dir * nearest;
                        if ((b - a).angle(hit - a) <= (b - a).angle(c - a)
                            && (c - a).angle(hit - a) <= (b - a).angle(c - a)
                            && (a - b).angle(hit - b) <= (a - b).angle(c - b))
                        {
                            maxSqDist = (hit - point).getLengthSq();
                            pId = index;
                            existsHit = 1;
                        }
                    }
                    index++;
                }
            }
        }
    }
    return existsHit ? maxSqDist : -1;
}

// berechnet die Farbe des Schnittpunktes deines Strahls
//  point: der startpunkt des Strahls in Weltkoordinaten
//  dir: die Richtung des Strahls in Weltkoordinaten
//  zWelt: die Welt, aus der der Strahl kommt
//  return: die Farbe des Schnittpunktes
int Model3D::traceRay(
    Vec3<float>& p, Vec3<float>& d, int pId, Welt3D* zWelt) const
{
    Vec3<float> dir = d;
    dir.rotateY(-angle.y);
    dir.rotateX(-angle.x);
    dir.rotateZ(-angle.z);
    Vec3<float> point = p;
    point.rotateY(-angle.y);
    point.rotateX(-angle.x);
    point.rotateZ(-angle.z);
    point -= pos;
    int index = 0;
    for (auto p = model->getPolygons(); p; p++, index++)
    {
        for (int j = 0; j < p->indexAnz; j++)
        {
            if (j % 3 == 0)
            {
                if (pId == 0)
                {
                    const Vec3<float>& a
                        = model->zVertexBuffer()[p->indexList[j]].pos;
                    const Vec3<float>& b
                        = model->zVertexBuffer()[p->indexList[j + 1]].pos;
                    const Vec3<float>& c
                        = model->zVertexBuffer()[p->indexList[j + 2]].pos;
                    Vertex at = model->zVertexBuffer()[p->indexList[j]].tPos;
                    Vertex bt
                        = model->zVertexBuffer()[p->indexList[j + 1]].tPos;
                    Vertex ct
                        = model->zVertexBuffer()[p->indexList[j + 2]].tPos;
                    Vec3<float> normal
                        = (b - a).crossProduct(c - a).normalize();
                    float t = (a * normal - point * normal) / (dir * normal);
                    Vec3<float> hit = point + dir * t;
                    float a0 = (a - b).crossProduct(a - c).getLength() / 2;
                    float a1
                        = (b - hit).crossProduct(c - hit).getLength() / 2 / a0;
                    float a2
                        = (c - hit).crossProduct(a - hit).getLength() / 2 / a0;
                    float a3
                        = (a - hit).crossProduct(b - hit).getLength() / 2 / a0;
                    Vertex ht = at * a1 + bt * a2 + ct * a3;
                    Bild* tex = textur->zPolygonTextur(index)->zBild();
                    if (ht.x >= 0 && ht.y >= 0 && ht.x <= 1 && ht.y <= 1)
                        return tex->getPixel(
                            (int)(ht.x * ((float)tex->getBreite() - 1.f)
                                  + 0.5f),
                            (int)(ht.y * ((float)tex->getHeight() - 1.f)
                                  + 0.5f));
                    return 0xFF000000;
                }
                pId--;
            }
        }
    }
    return 0xFF000000;
}

// Gibt die Id der Daten zurück, wenn sie in einer Model3DList registriert
// wurden. (siehe Framework::zM3DRegister())
int Model3D::getDatenId() const
{
    return model ? model->getId() : -1;
}

// Git den Factor an, mit dem das umgebungslicht (textur farbe) multipliziert
// wird
float Model3D::getAmbientFactor() const
{
    return ambientFactor;
}

// Git den Factor an, mit dem die Lichtfarbe von Lichtquellen multipliziert wird
float Model3D::getDiffusFactor() const
{
    return diffusFactor;
}

// Git den Factor an, mit dem die Reflektion von Lichtquellen multipliziert wird
float Model3D::getSpecularFactor() const
{
    return specularFactor;
}

// Gibt die Anzahl an Vertices zurück
int Model3D::getVertexAnzahl() const
{
    return model ? model->getVertexAnzahl() : 0;
}

// Gibt einen Buffer mit allen Vertecies des Models zurück
const Vertex3D* Model3D::zVertexBuffer() const
{
    return model ? model->zVertexBuffer() : 0;
}

//! Gibt true zurück wenn ein bestimmtes polygon gezeichnet werden muss
bool Model3D::needRenderPolygon(int index)
{
    return 1;
}

Textur* Model3D::zEffectTextur()
{
    return 0;
}

float Model3D::getEffectPercentage()
{
    return 0;
}