#include <Border.h>
#include <Camera3D.h>
#include <Cube.h>
#include <DXBuffer.h>
#include <FileSystem.h>
#include <GraphicsApi.h>
#include <Image.h>
#include <M3File.h>
#include <main.h>
#include <Mat4.h>
#include <Model3D.h>
#include <Model3DList.h>
#include <ObjFile.h>
#include <RenderThread.h>
#include <Screen.h>
#include <TextField.h>
#include <Texture.h>
#include <Vec3.h>
#include <Window.h>
#include <World3D.h>

#define ambient  0.4f
#define diffus   0.3f
#define specular 0.3f

using namespace Framework;

void makeQuader(Vertex3D* vertecies,
    int startIndex,
    Vec3<float> lft,
    Vec3<float> rft,
    Vec3<float> lfb,
    Vec3<float> rfb,
    Vec3<float> lbt,
    Vec3<float> rbt,
    Vec3<float> lbb,
    Vec3<float> rbb,
    Model3DData* zModel)
{
    // front
    vertecies[startIndex].pos = lft;
    vertecies[startIndex].tPos = Vec2<float>(0.f, 0.f);
    vertecies[startIndex + 1].pos = rft;
    vertecies[startIndex + 1].tPos = Vec2<float>(1.f, 0.f);
    vertecies[startIndex + 2].pos = lfb;
    vertecies[startIndex + 2].tPos = Vec2<float>(0.f, 1.f);
    vertecies[startIndex + 3].pos = rfb;
    vertecies[startIndex + 3].tPos = Vec2<float>(1.f, 1.f);
    // back
    vertecies[startIndex + 4].pos = rbt;
    vertecies[startIndex + 4].tPos = Vec2<float>(0.0f, 0.0f);
    vertecies[startIndex + 5].pos = lbt;
    vertecies[startIndex + 5].tPos = Vec2<float>(1.0f, 0.0f);
    vertecies[startIndex + 6].pos = rbb;
    vertecies[startIndex + 6].tPos = Vec2<float>(0.0f, 1.0f);
    vertecies[startIndex + 7].pos = lbb;
    vertecies[startIndex + 7].tPos = Vec2<float>(1.0f, 1.0f);
    // left
    vertecies[startIndex + 8].pos = lbt;
    vertecies[startIndex + 8].tPos = Vec2<float>(1.f, 0.f);
    vertecies[startIndex + 9].pos = lft;
    vertecies[startIndex + 9].tPos = Vec2<float>(0.f, 0.f);
    vertecies[startIndex + 10].pos = lbb;
    vertecies[startIndex + 10].tPos = Vec2<float>(1.f, 1.f);
    vertecies[startIndex + 11].pos = lfb;
    vertecies[startIndex + 11].tPos = Vec2<float>(0.f, 1.f);
    // right
    vertecies[startIndex + 12].pos = rft;
    vertecies[startIndex + 12].tPos = Vec2<float>(0.0f, 0.0f);
    vertecies[startIndex + 13].pos = rbt;
    vertecies[startIndex + 13].tPos = Vec2<float>(1.0f, 0.0f);
    vertecies[startIndex + 14].pos = rfb;
    vertecies[startIndex + 14].tPos = Vec2<float>(0.0f, 1.0f);
    vertecies[startIndex + 15].pos = rbb;
    vertecies[startIndex + 15].tPos = Vec2<float>(1.0f, 1.0f);
    // top
    vertecies[startIndex + 16].pos = lbt;
    vertecies[startIndex + 16].tPos = Vec2<float>(0.f, 0.f);
    vertecies[startIndex + 17].pos = rbt;
    vertecies[startIndex + 17].tPos = Vec2<float>(1.f, 0.f);
    vertecies[startIndex + 18].pos = lft;
    vertecies[startIndex + 18].tPos = Vec2<float>(0.f, 1.f);
    vertecies[startIndex + 19].pos = rft;
    vertecies[startIndex + 19].tPos = Vec2<float>(1.f, 1.f);
    // botom
    vertecies[startIndex + 20].pos = lfb;
    vertecies[startIndex + 20].tPos = Vec2<float>(0.0f, 0.0f);
    vertecies[startIndex + 21].pos = rfb;
    vertecies[startIndex + 21].tPos = Vec2<float>(1.0f, 0.0f);
    vertecies[startIndex + 22].pos = lbb;
    vertecies[startIndex + 22].tPos = Vec2<float>(0.0f, 1.0f);
    vertecies[startIndex + 23].pos = rbb;
    vertecies[startIndex + 23].tPos = Vec2<float>(1.0f, 1.0f);

    // front side
    Polygon3D* p = new Polygon3D();
    p->indexAnz = 6;
    p->indexList = new int[p->indexAnz];
    p->indexList[0] = startIndex + 0;
    p->indexList[1] = startIndex + 1;
    p->indexList[2] = startIndex + 2;
    p->indexList[3] = startIndex + 1;
    p->indexList[4] = startIndex + 3;
    p->indexList[5] = startIndex + 2;
    zModel->addPolygon(p);
    // left side
    p = new Polygon3D();
    p->indexAnz = 6;
    p->indexList = new int[p->indexAnz];
    p->indexList[0] = startIndex + 0 + 8;
    p->indexList[1] = startIndex + 1 + 8;
    p->indexList[2] = startIndex + 2 + 8;
    p->indexList[3] = startIndex + 1 + 8;
    p->indexList[4] = startIndex + 3 + 8;
    p->indexList[5] = startIndex + 2 + 8;
    zModel->addPolygon(p);
    // back side
    p = new Polygon3D();
    p->indexAnz = 6;
    p->indexList = new int[p->indexAnz];
    p->indexList[0] = startIndex + 0 + 4;
    p->indexList[1] = startIndex + 1 + 4;
    p->indexList[2] = startIndex + 2 + 4;
    p->indexList[3] = startIndex + 1 + 4;
    p->indexList[4] = startIndex + 3 + 4;
    p->indexList[5] = startIndex + 2 + 4;
    zModel->addPolygon(p);
    // right side
    p = new Polygon3D();
    p->indexAnz = 6;
    p->indexList = new int[p->indexAnz];
    p->indexList[0] = startIndex + 0 + 12;
    p->indexList[1] = startIndex + 1 + 12;
    p->indexList[2] = startIndex + 2 + 12;
    p->indexList[3] = startIndex + 1 + 12;
    p->indexList[4] = startIndex + 3 + 12;
    p->indexList[5] = startIndex + 2 + 12;
    zModel->addPolygon(p);
    // top side
    p = new Polygon3D();
    p->indexAnz = 6;
    p->indexList = new int[p->indexAnz];
    p->indexList[0] = startIndex + 0 + 16;
    p->indexList[1] = startIndex + 1 + 16;
    p->indexList[2] = startIndex + 2 + 16;
    p->indexList[3] = startIndex + 1 + 16;
    p->indexList[4] = startIndex + 3 + 16;
    p->indexList[5] = startIndex + 2 + 16;
    zModel->addPolygon(p);
    // bottom side
    p = new Polygon3D();
    p->indexAnz = 6;
    p->indexList = new int[p->indexAnz];
    p->indexList[0] = startIndex + 0 + 20;
    p->indexList[1] = startIndex + 1 + 20;
    p->indexList[2] = startIndex + 2 + 20;
    p->indexList[3] = startIndex + 1 + 20;
    p->indexList[4] = startIndex + 3 + 20;
    p->indexList[5] = startIndex + 2 + 20;
    zModel->addPolygon(p);
}

void makeQuader(Vertex3D* vertecies,
    int startIndex,
    Vec3<float> pos,
    Vec3<float> size,
    Model3DData* zModel)
{
    float left, right, top, bottom;
    // Calculate the screen coordinates of the left side of the bitmap.
    left = pos.x + size.x;
    // Calculate the screen coordinates of the right side of the bitmap.
    right = pos.x;
    // Calculate the screen coordinates of the top of the bitmap.
    top = pos.z + size.z;
    // Calculate the screen coordinates of the bottom of the bitmap.
    bottom = pos.z;
    float front = pos.y;
    float back = pos.y + size.y;

    makeQuader(vertecies,
        startIndex,
        Vec3<float>(left, front, top),
        Vec3<float>(right, front, top),
        Vec3<float>(left, front, bottom),
        Vec3<float>(right, front, bottom),
        Vec3<float>(left, back, top),
        Vec3<float>(right, back, top),
        Vec3<float>(left, back, bottom),
        Vec3<float>(right, back, bottom),
        zModel);
}

class Block : public Model3D
{
public:
    // Konstruktor
    //  size: Die Größe des Würfels
    Block(GraphicsApi* zApi)
    {
        model = zApi->createModel("block");
        model->setAmbientFactor(ambient);
        model->setDiffusFactor(diffus);
        model->setSpecularFactor(specular);

        Vertex3D* vertecies = new Vertex3D[24];
        for (int i = 0; i < 24; i++)
            vertecies[i].knochenId = 0;
        makeQuader(vertecies,
            0,
            Vec3<float>(-0.5, -0.5, -0.5),
            Vec3<float>(1, 1, 1),
            model);
        model->setVertecies(vertecies, 24);

        model->calculateNormals();

        texture = new Model3DTexture();
        Image* img = new Image();
        img->newImage(10, 10, 0xFFFF5050);
        texture->setPolygonTexture(
            0, zApi->createOrGetTexture("blockA", img)); // front
        img = new Image();
        img->newImage(10, 10, 0xFF5050FF);
        texture->setPolygonTexture(
            1, zApi->createOrGetTexture("blockC", img)); // left
        img = new Image();
        img->newImage(10, 10, 0xFFA02020);
        texture->setPolygonTexture(
            2, zApi->createOrGetTexture("blockB", img)); // back
        img = new Image();
        img->newImage(10, 10, 0xFF2020A0);
        texture->setPolygonTexture(
            3, zApi->createOrGetTexture("blockD", img)); // right
        img = new Image();
        img->newImage(10, 10, 0xFF50FF50);
        texture->setPolygonTexture(
            4, zApi->createOrGetTexture("blockE", img)); // top
        img = new Image();
        img->newImage(10, 10, 0xFF20A020);
        texture->setPolygonTexture(
            5, zApi->createOrGetTexture("blockF", img)); // botom
        rend = 1;
    }
};

class ObjModelCombiner : public Model3D
{
private:
    Text modelName;
    bool opening = 1;

public:
    ObjModelCombiner(GraphicsApi* zApi, Text path, Text textur, Text modelName)
        : Model3D()
    {
        M3File file("m3/blocks.m3");
        Model3DData* chest = 0;
        Model3DData* cover = 0;
        file.readData();
        if (file.hasModel("chest"))
        {
            chest = file.loadModel(modelName, zApi, "chest");
        }
        else
        {
            chest = zApi->createModel("chest");
            cover = zApi->createModel("cover");
            ObjFile file("obj/chest.obj");
            file.loadModel("chest", chest);
            ObjFile file2("obj/cover.obj");
            file2.loadModel("cover", cover);
            int chestVertexCount = chest->getVertexCount();
            int vertexCount = chestVertexCount + cover->getVertexCount();
            Vertex3D* vertecies = new Vertex3D[vertexCount];
            memcpy(vertecies,
                chest->zVertexBuffer(),
                chest->getVertexCount() * sizeof(Vertex3D));
            memcpy(vertecies + chest->getVertexCount(),
                cover->zVertexBuffer(),
                cover->getVertexCount() * sizeof(Vertex3D));
            for (int i = 0; i < vertexCount; i++)
            {
                vertecies[i].pos /= 2.f;
                float tmp = vertecies[i].pos.z;
                vertecies[i].pos.z = vertecies[i].pos.y;
                vertecies[i].pos.y = tmp;
                vertecies[i].tPos.y = 1.f - vertecies[i].tPos.y;
                vertecies[i].knochenId = 0;
            }
            for (int i = chest->getVertexCount(); i < vertexCount; i++)
            {
                vertecies[i].pos.z -= 0.45f;
                vertecies[i].pos.x -= 0.5f;
                vertecies[i].knochenId = 1;
            }
            chest->setVertecies(vertecies, vertexCount);
            for (int i = 0; i < chest->getPolygonCount(); i++)
            {
                Polygon3D* pol = chest->getPolygon(i);
                for (int j = 0; j < pol->indexAnz; j++)
                {
                    if (j % 3 == 2)
                    {
                        int tmp = pol->indexList[j - 1];
                        pol->indexList[j - 1] = pol->indexList[j];
                        pol->indexList[j] = tmp;
                    }
                }
            }
            for (int i = 0; i < cover->getPolygonCount(); i++)
            {
                Polygon3D* pol = cover->getPolygon(i);
                Polygon3D* res = new Polygon3D();
                res->indexAnz = pol->indexAnz;
                res->indexList = new int[pol->indexAnz];
                for (int j = 0; j < pol->indexAnz; j++)
                {
                    res->indexList[j] = pol->indexList[j] + chestVertexCount;
                    if (j % 3 == 2)
                    {
                        int tmp = res->indexList[j - 1];
                        res->indexList[j - 1] = res->indexList[j];
                        res->indexList[j] = tmp;
                    }
                }
                chest->addPolygon(res);
            }
            Skeleton* skelett = new Skeleton();
            int rootId = skelett->addBone(
                Vec3<float>(0.f, 0.f, 0.f), Vec3<float>(0.f, 0.f, 0.f));
            skelett->addBone(Vec3<float>(0.5f, 0.f, 0.45f),
                Vec3<float>(0.f, 0.f, 0.f),
                rootId);
            chest->setSkeletonZ(skelett);
            chest->updateGPUMemory();
            cover->release();
        }
        chest->setAmbientFactor(ambient);
        chest->setDiffusFactor(diffus);
        chest->setSpecularFactor(specular);
        setModelData(chest);
        LTDBFile imgFile;
        imgFile.setFile(new Text("tex/blocks.ltdb"));
        imgFile.readData(0);
        Image* t0 = imgFile.load(0, new Text("chest.png"));
        Image* t1 = imgFile.load(0, new Text("chestcover.png"));
        Model3DTexture* tex = new Model3DTexture();
        tex->setPolygonTexture(0, zApi->createOrGetTexture("chest.png", t0));
        tex->setPolygonTexture(
            1, zApi->createOrGetTexture("chestcover.png", t1));
        this->texture = tex;
        // adjustCenter();
        rend = 1;
        this->modelName = modelName;
    }

    void adjustCenter()
    {
        Vec3<float> maxPos = {-INFINITY, -INFINITY, -INFINITY};
        Vec3<float> minPos = {INFINITY, INFINITY, INFINITY};
        for (int i = 0; i < model->getVertexCount(); i++)
        {
            if (model->zVertexBuffer()[i].pos.x < minPos.x)
                minPos.x = model->zVertexBuffer()[i].pos.x;
            if (model->zVertexBuffer()[i].pos.y < minPos.y)
                minPos.y = model->zVertexBuffer()[i].pos.y;
            if (model->zVertexBuffer()[i].pos.z < minPos.z)
                minPos.z = model->zVertexBuffer()[i].pos.z;
            if (model->zVertexBuffer()[i].pos.x > maxPos.x)
                maxPos.x = model->zVertexBuffer()[i].pos.x;
            if (model->zVertexBuffer()[i].pos.y > maxPos.y)
                maxPos.y = model->zVertexBuffer()[i].pos.y;
            if (model->zVertexBuffer()[i].pos.z > maxPos.z)
                maxPos.z = model->zVertexBuffer()[i].pos.z;
        }
        Vec3<float> center = minPos + (maxPos - minPos) / 2;
        if (center != Vec3<float>(0.f, 0.f, 0.f))
        {
            for (int i = 0; i < model->getVertexCount(); i++)
            {
                // !! update of vertex data is not supported by the framework
                // and does not update some stored information on the model like
                // bounding box and radius
                ((Vertex3D*)model->zVertexBuffer())[i].pos -= center;
            }
            model->zDXVertexBuffer()->setChanged();
            model->updateGPUMemory();
        }
    }

    bool tick(double time) override
    {
        Bone* k = skelett->zBone(1);
        if (opening)
        {
            k->setRotation(
                k->getRotation() + Vec3<float>(0.0f, 0.3f * time, 0.f));
            if (k->getRotation().y > PI / 2)
            {
                opening = !opening;
            }
        }
        else
        {
            k->setRotation(
                k->getRotation() - Vec3<float>(0.0f, 0.3f * time, 0.f));
            if (k->getRotation().y < 0)
            {
                opening = !opening;
            }
        }
        if (getKeyState(VK_OEM_MINUS))
        {
            setSize(size - (size - (size * 0.995)) * (time * 3));
        }
        if (getKeyState(VK_OEM_PLUS))
        {
            setSize(size + (size * 1.005 - size) * (time * 3));
        }
        if (getKeyState(VK_NUMPAD1))
        {
            setRotationX(getRotation().x + time * 0.1);
        }
        if (getKeyState(VK_NUMPAD4))
        {
            setRotationX(getRotation().x - time * 0.1);
        }
        if (getKeyState(VK_NUMPAD2))
        {
            setRotationY(getRotation().y + time * 0.1);
        }
        if (getKeyState(VK_NUMPAD5))
        {
            setRotationY(getRotation().y - time * 0.1);
        }
        if (getKeyState(VK_NUMPAD3))
        {
            setRotationZ(getRotation().z + time * 0.1);
        }
        if (getKeyState(VK_NUMPAD6))
        {
            setRotationZ(getRotation().z - time * 0.1);
        }
        if (getKeyState(T_Enter))
        {
            if (size != 1.f || getRotation() != Vec3<float>(0.f, 0.f, 0.f))
            {
                // aply scale and rotation on model verticies
                Mat4<float> matrix = Mat4<float>::identity()
                                   * Mat4<float>::rotationZ(angle.z)
                                   * Mat4<float>::rotationX(angle.x)
                                   * Mat4<float>::rotationY(angle.y)
                                   * Mat4<float>::scaling(size);
                for (int i = 0; i < model->getVertexCount(); i++)
                {
                    // !! update of vertex data is not supported by the
                    // framework and does not update some stored information on
                    // the model like bounding box and radius
                    ((Vertex3D*)model->zVertexBuffer())[i].pos
                        = matrix * model->zVertexBuffer()[i].pos;
                    ((Vertex3D*)model->zVertexBuffer())[i].normal
                        = (matrix * model->zVertexBuffer()[i].normal)
                              .normalize();
                }
                model->zDXVertexBuffer()->setChanged();
                model->updateGPUMemory();
                size = 1.f;
                setRotation(0.f, 0.f, 0.f);
                adjustCenter();
            }
        }
        if (getKeyState(T_Ende))
        {
            // save model
            M3File file("m3/blocks.m3");
            file.readData();
            file.removeModel(modelName);
            if (file.saveModel(model, modelName))
            {
                std::cout << "erfolgreich gespeichert\n";
            }
            else
            {
                std::cout << "fehler beim speichern\n";
            }
        }
        return Model3D::tick(time);
    }
};

class ObjModel : public Model3D
{
private:
    Text modelName;
    Text m3File;
    bool opening = 1;

public:
    ObjModel(
        GraphicsApi* zApi, Text path, Text textur, Text modelName, Text m3File)
        : Model3D(),
          m3File(m3File)
    {
        M3File file(m3File);
        Model3DData* model = 0;
        file.readData();
        bool adjustCenterNeeded = false;
        if (file.hasModel(modelName))
        {
            model = file.loadModel(modelName, zApi, modelName);
        }
        else
        {
            model = zApi->createModel(modelName);
            ObjFile file(path);
            file.loadModel(modelName, model);
            int vertexCount = model->getVertexCount();
            Vertex3D* v = (Vertex3D*)model->zVertexBuffer();
            for (int i = 0; i < vertexCount; i++)
            {
                v[i].tPos.y = 1.f - v[i].tPos.y;
            }
            adjustCenterNeeded = true;
        }
        model->setAmbientFactor(ambient);
        model->setDiffusFactor(diffus);
        model->setSpecularFactor(specular);
        setModelData(model);
        LTDBFile imgFile;
        Text* p = textur.getTeilText(
            0, textur.positionOf("/", textur.countOf("/") - 1));
        int pLen = p->getLength();
        imgFile.setFile(p);
        imgFile.readData(0);
        Image* t = imgFile.load(0, textur.getTeilText(pLen + 1));
        Model3DTexture* tex = new Model3DTexture();
        tex->setPolygonTexture(0, zApi->createOrGetTexture(textur, t));
        this->texture = tex;
        if (adjustCenterNeeded)
        {
            adjustCenter();
        }
        rend = 1;
        this->modelName = modelName;
    }

    void adjustCenter()
    {
        Vec3<float> maxPos = {-INFINITY, -INFINITY, -INFINITY};
        Vec3<float> minPos = {INFINITY, INFINITY, INFINITY};
        for (int i = 0; i < model->getVertexCount(); i++)
        {
            if (model->zVertexBuffer()[i].pos.x < minPos.x)
                minPos.x = model->zVertexBuffer()[i].pos.x;
            if (model->zVertexBuffer()[i].pos.y < minPos.y)
                minPos.y = model->zVertexBuffer()[i].pos.y;
            if (model->zVertexBuffer()[i].pos.z < minPos.z)
                minPos.z = model->zVertexBuffer()[i].pos.z;
            if (model->zVertexBuffer()[i].pos.x > maxPos.x)
                maxPos.x = model->zVertexBuffer()[i].pos.x;
            if (model->zVertexBuffer()[i].pos.y > maxPos.y)
                maxPos.y = model->zVertexBuffer()[i].pos.y;
            if (model->zVertexBuffer()[i].pos.z > maxPos.z)
                maxPos.z = model->zVertexBuffer()[i].pos.z;
        }
        Vec3<float> center = minPos + (maxPos - minPos) / 2;
        if (center != Vec3<float>(0.f, 0.f, 0.f))
        {
            for (int i = 0; i < model->getVertexCount(); i++)
            {
                // !! update of vertex data is not supported by the framework
                // and does not update some stored information on the model like
                // bounding box and radius
                ((Vertex3D*)model->zVertexBuffer())[i].pos -= center;
            }
            model->zDXVertexBuffer()->setChanged();
            model->updateGPUMemory();
        }
    }

    bool tick(double time) override
    {
        if (getKeyState(VK_NUMPAD7) || getKeyState(VK_NUMPAD8)
            || getKeyState(VK_NUMPAD9))
        {
            if (getKeyState(VK_OEM_MINUS))
            {
                if (getKeyState(VK_NUMPAD7))
                {
                    setPosition(pos - Vec3<float>(1.f, 0.f, 0.f) * 0.0001);
                }
                if (getKeyState(VK_NUMPAD8))
                {
                    setPosition(pos - Vec3<float>(0.f, 1.f, 0.f) * 0.0001);
                }
                if (getKeyState(VK_NUMPAD9))
                {
                    setPosition(pos - Vec3<float>(0.f, 0.f, 1.f) * 0.0001);
                }
            }
            if (getKeyState(VK_OEM_PLUS))
            {
                if (getKeyState(VK_NUMPAD7))
                {
                    setPosition(pos + Vec3<float>(1.f, 0.f, 0.f) * 0.0001);
                }
                if (getKeyState(VK_NUMPAD8))
                {
                    setPosition(pos + Vec3<float>(0.f, 1.f, 0.f) * 0.0001);
                }
                if (getKeyState(VK_NUMPAD9))
                {
                    setPosition(pos + Vec3<float>(0.f, 0.f, 1.f) * 0.0001);
                }
            }
        }
        else
        {
            if (getKeyState(VK_OEM_MINUS))
            {
                setSize(size - (size - (size * 0.995)) * (time * 3));
            }
            if (getKeyState(VK_OEM_PLUS))
            {
                setSize(size + (size * 1.005 - size) * (time * 3));
            }
        }
        if (getKeyState(VK_NUMPAD1))
        {
            setRotationX(getRotation().x + time * 0.1);
        }
        if (getKeyState(VK_NUMPAD4))
        {
            setRotationX(getRotation().x - time * 0.1);
        }
        if (getKeyState(VK_NUMPAD2))
        {
            setRotationY(getRotation().y + time * 0.1);
        }
        if (getKeyState(VK_NUMPAD5))
        {
            setRotationY(getRotation().y - time * 0.1);
        }
        if (getKeyState(VK_NUMPAD3))
        {
            setRotationZ(getRotation().z + time * 0.1);
        }
        if (getKeyState(VK_NUMPAD6))
        {
            setRotationZ(getRotation().z - time * 0.1);
        }
        if (getKeyState(T_Enter))
        {
            if (size != 1.f || getRotation() != Vec3<float>(0.f, 0.f, 0.f))
            {
                // aply scale and rotation on model verticies
                Mat4<float> matrix = Mat4<float>::identity()
                                   * Mat4<float>::rotationZ(angle.z)
                                   * Mat4<float>::rotationX(angle.x)
                                   * Mat4<float>::rotationY(angle.y)
                                   * Mat4<float>::scaling(size);
                for (int i = 0; i < model->getVertexCount(); i++)
                {
                    // !! update of vertex data is not supported by the
                    // framework and does not update some stored information on
                    // the model like bounding box and radius
                    ((Vertex3D*)model->zVertexBuffer())[i].pos
                        = matrix * model->zVertexBuffer()[i].pos;
                    ((Vertex3D*)model->zVertexBuffer())[i].normal
                        = (matrix * model->zVertexBuffer()[i].normal)
                              .normalize();
                }
                model->zDXVertexBuffer()->setChanged();
                model->updateGPUMemory();
                size = 1.f;
                setRotation(0.f, 0.f, 0.f);
                adjustCenter();
            }
        }
        if (getKeyState(T_BackSpace))
        {
            if (getPos() != Vec3<float>(0.f, 0.f, 0.f))
            {
                // aply scale and rotation on model verticies
                Mat4<float> matrix
                    = Mat4<float>::identity() * Mat4<float>::translation(pos);
                for (int i = 0; i < model->getVertexCount(); i++)
                {
                    // !! update of vertex data is not supported by the
                    // framework and does not update some stored information on
                    // the model like bounding box and radius
                    ((Vertex3D*)model->zVertexBuffer())[i].pos
                        = matrix * model->zVertexBuffer()[i].pos;
                    ((Vertex3D*)model->zVertexBuffer())[i].normal
                        = (matrix * model->zVertexBuffer()[i].normal)
                              .normalize();
                }
                model->zDXVertexBuffer()->setChanged();
                model->updateGPUMemory();
                setPosition(Vec3<float>(0.f, 0.f, 0.f));
            }
        }
        if (getKeyState(T_Ende))
        {
            // save model
            M3File file(m3File);
            file.readData();
            file.removeModel(modelName);
            if (file.saveModel(model, modelName))
            {
                std::cout << "erfolgreich gespeichert\n";
            }
            else
            {
                std::cout << "fehler beim speichern\n";
            }
        }
        return Model3D::tick(time);
    }
};

// Aktion beim schließen des Fensters
void schließen(void* p, void* f)
{
    // Stoppe Nachrichten Loop
    StopMessageLoop(((NativeWindow*)f)->getWindowHandle());
}

int __stdcall Framework::Start(Startparam p)
{
    // Fenster erstellen
    WNDCLASS wc = F_Normal(p.hinst);
    wc.lpszClassName = "3D Creator";

    NativeWindow* window = new NativeWindow();
    window->create(WS_OVERLAPPEDWINDOW, wc);
    window->setPreCloseAction(schließen);
    window->setDisplayMode(SW_SHOWNORMAL);
    window->setSize(1300, 820);
    window->setPosition(Point(100, 100));

    // 3D Umgebung erstellen
    Screen3D* screen = new Screen3D(
        dynamic_cast<NativeWindow*>(window->getThis()), DIRECTX11);
    window->setScreen(dynamic_cast<Screen*>(screen->getThis()));
    screen->setFillColor(0);
    screen->setTestRend(0);

    World3D* world = new World3D();

    ObjModel* mdl = new ObjModel(screen->zGraphicsApi(),
        "obj/vines.obj",
        "tex/plants.ltdb/vines.png",
        "vines",
        "m3/plants.m3");
    mdl->setPosition(0, 0, 0);
    world->addDrawable(mdl);
    Block* blk = new Block(screen->zGraphicsApi());
    blk->setPosition(0, 0, -1);
    world->addDrawable(blk);

    world->addDiffuseLight(
        {Vec3<float>(-0.5f, -0.5f, -0.5f), Vec3<float>(1.f, 1.f, 1.f)});

    Cam3D* camA = new Cam3D(); // look at front
    camA->setScreenPosition(10, 10);
    camA->setScreenSize(385, 385);
    camA->setPosition(Vec3<float>(0, -10, 0));
    camA->setRotation({-(float)PI / 2.f, -(float)PI, 0});
    camA->setWorld(dynamic_cast<World3D*>(world->getThis()));
    screen->addKamera(camA);

    Cam3D* camB = new Cam3D(); // look at left
    camB->setMovementSpeed(1);
    camB->setScreenPosition(10, 405);
    camB->setScreenSize(385, 385);
    camB->setPosition(Vec3<float>(10, 0, 0));
    camB->setRotation({-PI / 2, (float)PI, (float)PI / 2.f});
    camB->setWorld(dynamic_cast<World3D*>(world->getThis()));
    screen->addKamera(camB);

    Cam3D* camC = new Cam3D();
    camC->setScreenPosition(405, 10);
    camC->setScreenSize(385, 385);
    camC->setPosition(Vec3<float>(0, 0, 10));
    camC->setRotation({-(float)PI, 0, 0});
    camC->setWorld(dynamic_cast<World3D*>(world->getThis()));
    screen->addKamera(camC);

    Cam3D* camD = new Cam3D();
    camD->setStyle(
        Cam3D::Style::Movable | Cam3D::Style::Rotatable | Cam3D::Style::Tick);
    camD->setScreenPosition(405, 405);
    camD->setScreenSize(385, 385);
    camD->setPosition(Vec3<float>(0, -10, 0));
    camD->setRotation({-(float)PI / 2.f, -(float)PI, 0});
    camD->setWorld(dynamic_cast<World3D*>(world->getThis()));
    screen->addKamera(camD);

    LTDSFile fontFile;
    fontFile.setPfad(new Text("normal.ltds"));
    fontFile.readData();
    Font* font = fontFile.loadFont();
    TextField* camPos = new TextField();
    camPos->setFontZ(font);
    camPos->setPosition(810, 10);
    camPos->setSize(200, 200);
    camPos->setText("Kamera Position");
    camPos->setStyle(TextField::Style::Text);
    screen->addMember(camPos);

    LBorder* camAR = new LBorder();
    camAR->setPosition(10, 10);
    camAR->setColor(0xFFFFFFFF);
    camAR->setBorderWidth(1);
    camAR->setSize(385, 385);
    screen->addMember(camAR);
    LBorder* camBR = new LBorder();
    camBR->setPosition(404, 10);
    camBR->setColor(0xFFFFFFFF);
    camBR->setBorderWidth(1);
    camBR->setSize(385, 385);
    screen->addMember(camBR);
    LBorder* camCR = new LBorder();
    camCR->setPosition(10, 405);
    camCR->setColor(0xFFFFFFFF);
    camCR->setBorderWidth(1);
    camCR->setSize(385, 385);
    screen->addMember(camCR);
    LBorder* camDR = new LBorder();
    camDR->setPosition(405, 405);
    camDR->setColor(0xFFFFFFFF);
    camDR->setBorderWidth(1);
    camDR->setSize(385, 385);
    screen->addMember(camDR);

    // Render Loop starten
    RenderTh* r = new RenderTh();
    r->setMaxFps(10000);
    r->setScreen(dynamic_cast<Screen*>(screen->getThis()));
    r->setTickFunktion([screen, camD, camA, camB, camC, camPos](
                           void*, void*, double t) {
        if (Framework::getKeyState('1'))
        {
            camA->setStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                           | Cam3D::Style::Tick);
            camB->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
            camC->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
            camD->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
        }
        if (Framework::getKeyState('2'))
        {
            camA->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
            camB->setStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                           | Cam3D::Style::Tick);
            camC->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
            camD->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
        }
        if (Framework::getKeyState('3'))
        {
            camA->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
            camB->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
            camC->setStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                           | Cam3D::Style::Tick);
            camD->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
        }
        if (Framework::getKeyState('4'))
        {
            camA->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
            camB->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
            camC->removeStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                              | Cam3D::Style::Tick);
            camD->setStyle(Cam3D::Style::Movable | Cam3D::Style::Rotatable
                           | Cam3D::Style::Tick);
        }
        Text txt = Text("A: ") + camA->getWorldPosition().x + ", "
                 + camA->getWorldPosition().y + ", "
                 + camA->getWorldPosition().z + "\n";
        txt += Text("B: ") + camB->getWorldPosition().x + ", "
             + camB->getWorldPosition().y + ", " + camB->getWorldPosition().z
             + "\n";
        txt += Text("C: ") + camC->getWorldPosition().x + ", "
             + camC->getWorldPosition().y + ", " + camC->getWorldPosition().z
             + "\n";
        txt += Text("D: ") + camD->getWorldPosition().x + ", "
             + camD->getWorldPosition().y + ", " + camD->getWorldPosition().z
             + "\n";
        txt.append() << "top: hellgrün\n"
                     << "bottom: dunkelgrün\n"
                     << "front: helrot\n"
                     << "back: dunkelrot\n"
                     << "left: hellblau\n"
                     << "right: dunkelblau";
        camPos->setText(txt);
    });
    r->beginn();

    // Nachrichten Loop starten und warten
    StartMessageLoop();

    // Speicher Freigeben
    r->terminate();
    r->release();
    screen->release();
    window->setScreen(0);
    window->destroy();
    window->release();
    world->release();

    return 0;
}