#include "Model3D.h"

#include "Animation3D.h"
#include "DXBuffer.h"
#include "Image.h"
#include "Model2D.h"
#include "Texture.h"
#include "World3D.h"
#ifdef WIN32
#    include <d3d11.h>
#endif
#include <stdexcept>

using namespace Framework;

// Constructor
// \param id the id of the bone
Bone::Bone(int id)
{
    pos = Vec3<float>(0, 0, 0);
    rot = Vec3<float>(0, 0, 0);
    sibling = 0;
    child = 0;
    this->id = id;
}

//! Destructor
Bone::~Bone()
{
    delete sibling;
    delete child;
}

//! set the position of the bone relative to the parent bone
//! \param pos the position
void Bone::setPosition(const Vec3<float>& pos)
{
    this->pos = pos;
}

//! Set the rotation of the bone relative to the parent bone
//! \param rot the rotation
void Bone::setRotation(const Vec3<float>& rot)
{
    this->rot = rot;
}

//! add a sibling bone to this bone that shares the same parent bone
//! \param b The bone to be added
void Bone::addSiblingBone(Bone* b)
{
    if (!sibling)
        sibling = b;
    else
        sibling->addSiblingBone(b);
}

//! add a child bone to a specific child bone
//! \param id the id of the bone the new bone should be a child of
//! \param b the bone that should be added
bool Bone::addChildBone(int id, Bone* b)
{
    if (this->id == id)
    {
        if (!child)
        {
            child = b;
            return 1;
        }
        else
        {
            child->addSiblingBone(b);
            return 1;
        }
    }
    else
    {
        if (child)
        {
            if (child->addChildBone(id, b))
            {
                return 1;
            }
        }
        else if (sibling)
        {
            return sibling->addChildBone(id, b);
        }
        return 0;
    }
}

//! calculates the matrixes of this bone, all child bones and sibling
//! bones
//! \param elternMat the already calculated matrix of the parent bone
//! \param matBuffer the array to store the calculated matrixes
//! \param scaleFactor the scaling of the object
//! \param camMatrix the view-projection matrix of the used camera
void Bone::calculateMatrix(const Mat4<float>& elternMat,
    Mat4<float>* matBuffer,
    float scaleFactor,
    const Mat4<float>& kamMat)
{
    if (sibling)
        sibling->calculateMatrix(elternMat, matBuffer, scaleFactor, kamMat);
    matBuffer[id]
        = matBuffer[id].translation(pos * scaleFactor)
        * matBuffer[id].rotationZ(rot.z) * matBuffer[id].rotationX(rot.x)
        * matBuffer[id].rotationY(rot.y) * matBuffer[id].scaling(scaleFactor);
    matBuffer[id] = elternMat * matBuffer[id];
    if (child)
        child->calculateMatrix(matBuffer[id], matBuffer, scaleFactor, kamMat);
    matBuffer[id] = kamMat * matBuffer[id];
}

Bone* Framework::Bone::zFirstSibling() const
{
    return sibling;
}

Bone* Framework::Bone::zFirstChild() const
{
    return child;
}

//! returns a copy of this bone with copies of all child and
//! sibling bones
Bone* Bone::copyBone() const
{
    Bone* ret = new Bone(id);
    ret->pos = pos;
    ret->rot = rot;
    if (sibling) ret->sibling = sibling->copyBone();
    if (child) ret->child = child->copyBone();
    return ret;
}

//! \return the id of this bone
int Bone::getId() const
{
    return id;
}

//! \return the rotation of this bone
Vec3<float> Bone::getRotation() const
{
    return rot;
}

//! \return the position of this bone
Vec3<float> Bone::getPosition() const
{
    return pos;
}

//! \return the radius of this bone
float Bone::getRadius() const
{
    float r = pos.getLength();
    if (sibling) r = MAX(r, sibling->getRadius());
    if (child) r += child->getRadius();
    return r;
}

// Contents of the Skeleton class

// Constructor
Skeleton::Skeleton()
    : ReferenceCounter()
{
    rootBone = 0;
    nextId = 0;
}

// Destructor
Skeleton::~Skeleton()
{
    if (rootBone) delete rootBone;
}

Bone* Skeleton::zBone(Bone* zCurrent, int id) const
{
    while (zCurrent)
    {
        if (zCurrent->getId() == id)
        {
            return zCurrent;
        }
        if (zCurrent->zFirstChild())
        {
            Bone* ret = zBone(zCurrent->zFirstChild(), id);
            if (ret) return ret;
        }
        zCurrent = zCurrent->zFirstSibling();
    }
    return 0;
}

//! add a bone to the sceleton
//! \param pos the position of the bone
//! \param rot the rotation of the bone
//! \param the id of the parent bone where the new bone should be added
//! as a child
//! \return the id of the added bone or -1 if the bone could not be
//! added
int Skeleton::addBone(Vec3<float> pos, Vec3<float> rot, int parentId)
{
    if (parentId == -1)
    {
        if (!this->rootBone)
        {
            this->rootBone = new Bone(nextId++);
            this->rootBone->setPosition(pos);
            this->rootBone->setRotation(rot);
            return this->rootBone->getId();
        }
        else
        {
            Bone* bone = new Bone(nextId++);
            bone->setPosition(pos);
            bone->setRotation(rot);
            this->rootBone->addSiblingBone(bone);
            return bone->getId();
        }
    }
    else
    {
        if (!this->rootBone) return -1;
        Bone* bone = new Bone(nextId++);
        bone->setPosition(pos);
        bone->setRotation(rot);
        if (rootBone->addChildBone(parentId, bone))
        {
            return bone->getId();
        }
        else
        {
            nextId--;
            return -1;
        }
    }
}

//! calculates the matrices of all bones in this sceleton
//! \param modelMatrix the already calculated matrix of the used 3d
//! model \param matBuffer the array to store the calculated matrixes
//! \param scaleFactor the scaling of the object
//! \param camMatrix the view-projection matrix of the used camera
int Skeleton::calculateMatrix(const Mat4<float>& modelMatrix,
    Mat4<float>* matBuffer,
    float scaleFactor,
    const Mat4<float>& kamMatrix)
{
    rootBone->calculateMatrix(modelMatrix, matBuffer, scaleFactor, kamMatrix);
    return nextId;
}

//! \return the radius of the sceleton
float Skeleton::getRadius() const
{
    if (rootBone) return rootBone->getRadius();
    return 0;
}

//! \return the root bone of the sceleton
Bone* Framework::Skeleton::zRootBone() const
{
    return rootBone;
}

//! \return the bone with a specific id
Bone* Framework::Skeleton::zBone(int id) const
{
    if (!rootBone) return 0;
    return zBone(rootBone, id);
}

//! \return a deep copy of the sceleton
Skeleton* Skeleton::copySceleton() const
{
    Skeleton* ret = new Skeleton();
    ret->nextId = nextId;
    if (rootBone) ret->rootBone = rootBone->copyBone();
    return ret;
}

//! \return the next id for a bone ther can be only MAX_KNOCHEN_ANZ
//! bones in a sceleton. if the sceleton is full -1 is returned
int Framework::Skeleton::getNextBoneId() const
{
    return nextId;
}

// Contents of the Polygon3D struct

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

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

// Contents of the Model3DData class

// Constructor
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;
}

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

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

// Deletes all model data
void Model3DData::clearModel()
{
    delete[] vertexList;
    vertexCount = 0;
    vertexList = 0;
    for (Polygon3D* i : *polygons)
        delete i;
    polygons->clear();
    if (skelett) skelett->release();
    skelett = 0;
    radius = 0;
    delete[] indexBuffer;
    indexBuffer = 0;
    indexCount = 0;
}

// Calculates the normals for the vertices of the model
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;
    }
}

//! Creates a buffer for all polygon indices
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;
    }
    if (dxIndexBuffer)
    {
        dxIndexBuffer->setLength((int)(indexCount * sizeof(int)));
        dxIndexBuffer->setData(indexBuffer);
    }
}

// Sets the pointer to a default skeleton
//  s: The skeleton to be used
void Model3DData::setSkeletonZ(Skeleton* s)
{
    if (skelett) skelett->release();
    skelett = s;
}

// Sets a pointer to a list with all vertices of the model
//  vertexList: An array of vertices
//  anz: The number of vertices in the 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;
    }
    if (dxVertexBuffer)
    {
        dxVertexBuffer->setLength((int)(anz * sizeof(Vertex3D)));
        dxVertexBuffer->setData(vertexList);
    }
}

// Adds a polygon to the model
//  polygon: The polygon to be added
void Model3DData::addPolygon(Polygon3D* polygon)
{
    polygons->add(polygon);
    buildIndexBuffer();
}

// Sets the factor by which the ambient light (texture color) is multiplied
//  f: the new factor (from 0 to 1, ambient + specular + diffuse = 1)
void Model3DData::setAmbientFactor(float f)
{
    ambientFactor = f;
}

// Sets the factor by which the light color from light sources is multiplied
//  f: the new factor (from 0 to 1, ambient + specular + diffuse = 1)
void Model3DData::setDiffusFactor(float f)
{
    diffusFactor = f;
}

// Sets the factor by which the reflection from light sources is multiplied
//  f: the new factor (from 0 to 1, ambient + specular + diffuse = 1)
void Model3DData::setSpecularFactor(float f)
{
    specularFactor = f;
}

// Converts a 2D model to 3D
//  model: The 2D model to be converted to 3D
//  z: The z coordinate of all points of the model
void Model3DData::copyModel2D(Model2DData* model, float z)
{
    if (model && model->vLists && model->polygons)
    {
        clearModel();
        int vAnz = 0;
        for (const Polygon2D& p : *model->polygons)
            vAnz += p.vertex->getEntryCount();
        Vertex3D* vertexList = new Vertex3D[vAnz];
        int index = 0;
        for (auto i : *model->vLists)
        {
            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->point->x, k->point->y, z);
                        vertexList[index].tPos = (Vec2<float>)*k->texture;
                        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();
    }
}

// Removes a polygon
//  index: The index of the polygon
void Model3DData::removePolygon(int index)
{
    if (!polygons->has(index)) return;
    delete polygons->get(index);
    polygons->remove(index);
    buildIndexBuffer();
}

// Calculates the bone matrices
//  modelMatrix: The matrix that transforms the skeleton into world space
//  matBuffer: An array of matrices to be filled with bone matrices
//  scaleFactor: The scaling of the model kamMatrix: The combination of
//  the view and projection matrices return: returns the number of used
//  matrices
int Model3DData::kalkulateMatrix(const Mat4<float>& modelMatrix,
    Mat4<float>* matBuffer,
    float scaleFactor,
    const Mat4<float>& kamMatrix) const
{
    if (!skelett) return 0;
    return skelett->calculateMatrix(
        modelMatrix, matBuffer, scaleFactor, kamMatrix);
}

// Returns the number of polygons
int Model3DData::getPolygonCount() const
{
    return polygons->getEntryCount();
}

// Returns a specific polygon
//  index: The index of the polygon
Polygon3D* Model3DData::getPolygon(int index) const
{
    if (!polygons->has(index)) return 0;
    return polygons->get(index);
}

// Returns an iterator to list the polygons
ArrayIterator<Polygon3D*> Model3DData::getPolygons() const
{
    return polygons->begin();
}

// Returns the radius of a sphere that encloses the entire model
float Model3DData::getRadius() const
{
    return radius;
}

// Returns the id of the data if registered in a Model3DList
// (see Framework::zM3DRegister())
int Model3DData::getId() const
{
    return id;
}

// Returns the factor by which the ambient light (texture color) is multiplied
float Model3DData::getAmbientFactor() const
{
    return ambientFactor;
}

// Returns the factor by which the light color from light sources is multiplied
float Model3DData::getDiffusFactor() const
{
    return diffusFactor;
}

// Returns the factor by which the reflection from light sources is multiplied
float Model3DData::getSpecularFactor() const
{
    return specularFactor;
}

// Returns a copy of the skeleton that can be used for animations
Skeleton* Model3DData::copySkeleton() const
{
    return skelett ? skelett->copySceleton() : 0;
}

// Returns the number of vertices
int Model3DData::getVertexCount() const
{
    return vertexCount;
}

// Returns a buffer with all vertices of the model
const Vertex3D* Model3DData::zVertexBuffer() const
{
    return vertexList;
}

//! Returns a reference to the beginning of the index buffer
const int* Model3DData::getIndexBuffer() const
{
    return indexBuffer;
}

//! Returns the number of indices in the index buffer
int Model3DData::getIndexCount() const
{
    return indexCount;
}

//! Returns the index buffer
DXBuffer* Model3DData::zDXIndexBuffer() const
{
    return dxIndexBuffer;
}

//! Returns the vertex buffer
DXBuffer* Model3DData::zDXVertexBuffer() const
{
    return dxVertexBuffer;
}

//! Returns the minimum point of the bounding box of the model
Vec3<float> Model3DData::getMinPos() const
{
    return minPos;
}

//! Returns the maximum point of the bounding box of the model
Vec3<float> Model3DData::getMaxPos() const
{
    return maxPos;
}

// Contents of the Model3DTexture class

// Constructor
Model3DTexture::Model3DTexture()
    : ReferenceCounter()
{
    textures = new Texture*[1];
    textures[0] = 0;
    textureCount = 1;
}

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

// Sets which texture is for which polygon
//  pI: The index of the polygon
//  txt: The texture of the polygon
void Model3DTexture::setPolygonTexture(int pI, Texture* txt)
{
    if (pI >= textureCount)
    {
        Texture** tmp = textures;
        textures = new Texture*[pI + 1];
        memcpy(textures, tmp, sizeof(Texture*) * textureCount);
        memset(textures + textureCount,
            0,
            sizeof(Texture*) * (pI + 1 - textureCount));
        delete[] tmp;
        textureCount = pI + 1;
    }
    if (textures[pI]) textures[pI]->release();
    textures[pI] = txt;
}

// Returns a pointer to the texture of a polygon without increased
// reference counter
//  i: The index of the polygon
Texture* Model3DTexture::zPolygonTexture(int i) const
{
    if (i >= textureCount) return 0;
    return textures[i];
}

// Contents of the Model3D class
// Constructor
Model3D::Model3D()
    : Drawable3D()
{
    model = 0;
    texture = 0;
    skelett = 0;
    ambientFactor = 1.f;
    diffusFactor = 0.f;
    specularFactor = 0.f;
}

// Destructor
Model3D::~Model3D()
{
    if (model) model->release();
    if (texture) texture->release();
    if (skelett) skelett->release();
}

// Sets the model data
//  data: The data
void Model3D::setModelData(Model3DData* data)
{
    if (model) model->release();
    if (skelett) skelett = (Skeleton*)skelett->release();
    model = data;
    if (model)
    {
        skelett = model->copySkeleton();
        this->ambientFactor = model->getAmbientFactor();
        this->specularFactor = model->getSpecularFactor();
        this->diffusFactor = model->getDiffusFactor();
    }
}

// Sets the textures to be used for drawing
//  txt: A list of textures assigned to the different polygons
void Model3D::setModelTextur(Model3DTexture* txt)
{
    if (texture) texture->release();
    texture = txt;
}

// Sets the factor by which the ambient light (texture color) is multiplied
//  f: the new factor (from 0 to 1, ambient + specular + diffuse = 1)
void Framework::Model3D::setAmbientFactor(float f)
{
    this->ambientFactor = f;
}

// Sets the factor by which the light color from light sources is multiplied
//  f: the new factor (from 0 to 1, ambient + specular + diffuse = 1)
void Framework::Model3D::setDiffusFactor(float f)
{
    diffusFactor = f;
}

// Sets the factor by which the ambient light (texture color) is multiplied
//  f: the new factor (from 0 to 1, ambient + specular + diffuse = 1)
void Framework::Model3D::setSpecularFactor(float f)
{
    specularFactor = f;
}

// Calculates the matrices of all bones of the model's skeleton
//  viewProj: The multiplied camera matrices
//  matBuffer: An array of matrices to be filled
//  return: The number of matrices the model requires
int Model3D::calculateMatrices(
    const Mat4<float>& viewProj, Mat4<float>* matBuffer)
{
    int ret = 0;
    if (skelett)
        ret = skelett->calculateMatrix(welt, matBuffer, size, viewProj);
    else if (model)
        ret = model->kalkulateMatrix(welt, matBuffer, size, viewProj);
    if (!ret) return Drawable3D::calculateMatrices(viewProj, matBuffer);
    return ret;
}

// Processes elapsed time
//  tickval: The time in seconds that has passed since the last call
//  return: true if the object has changed, false otherwise.
bool Model3D::tick(double tickval)
{
    radius = model ? model->getRadius() : 0;
    if (skelett)
    {
        radius += skelett->getRadius();
    }
    return Drawable3D::tick(tickval);
}

//! for updating shader data
void Model3D::beforeRender(
    GraphicsApi* api, Shader* zVertexShader, Shader* zPixelShader)
{}

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

// Returns the texture
Model3DTexture* Model3D::getTexture()
{
    return texture ? dynamic_cast<Model3DTexture*>(texture->getThis()) : 0;
}

// Returns the texture (without increased reference counter)
Model3DTexture* Model3D::zTexture()
{
    return texture;
}

// Returns the model data
Model3DData* Model3D::getModelData()
{
    return model ? dynamic_cast<Model3DData*>(model->getThis()) : 0;
}

// Returns the model data (without increased reference counter)
Model3DData* Model3D::zModelData()
{
    return model;
}

// Checks if a ray hits this object
//  point: the start point of the ray in world coordinates
//  dir: the direction of the ray in world coordinates
//  maxSqDist: The maximum squared distance allowed
//  pId: the id of the polygon the intersection belongs to
//  return: the squared distance of the intersection to the ray origin
//  or -1 if no intersection exists
float Model3D::traceRay(
    const Vec3<float>& p, const 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)) // no intersection exists
        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) // Check if the normal points towards
                                          // the ray origin
                    {
                        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;
}

// Calculates the color of the intersection point of a ray
//  point: the start point of the ray in world coordinates
//  dir: the direction of the ray in world coordinates
//  zWorld: the world the ray comes from
//  return: the color of the intersection point
int Model3D::traceRay(
    Vec3<float>& p, Vec3<float>& d, int pId, World3D* zWorld) 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;
                    Image* tex = texture->zPolygonTexture(index)->zImage();
                    if (ht.x >= 0 && ht.y >= 0 && ht.x <= 1 && ht.y <= 1)
                        return tex->getPixel(
                            (int)(ht.x * ((float)tex->getWidth() - 1.f) + 0.5f),
                            (int)(ht.y * ((float)tex->getHeight() - 1.f)
                                  + 0.5f));
                    return 0xFF000000;
                }
                pId--;
            }
        }
    }
    return 0xFF000000;
}

// Returns the id of the data if registered in a Model3DList
// (see Framework::zM3DRegister())
int Model3D::getDataId() const
{
    return model ? model->getId() : -1;
}

// Returns the factor by which the ambient light (texture color) is multiplied
float Model3D::getAmbientFactor() const
{
    return ambientFactor;
}

// Returns the factor by which the light color from light sources is multiplied
float Model3D::getDiffusFactor() const
{
    return diffusFactor;
}

// Returns the factor by which the reflection from light sources is multiplied
float Model3D::getSpecularFactor() const
{
    return specularFactor;
}

// Returns the number of vertices
int Model3D::getVertexCount() const
{
    return model ? model->getVertexCount() : 0;
}

// Returns a buffer with all vertices of the model
const Vertex3D* Model3D::zVertexBuffer() const
{
    return model ? model->zVertexBuffer() : 0;
}

//! Returns true if a specific polygon needs to be rendered
bool Model3D::needRenderPolygon(int index)
{
    return 1;
}

Texture* Model3D::zEffectTexture()
{
    return 0;
}

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