#include "Model2D.h"

#include "Image.h"
#include "FrameworkMath.h"
#include "Mat3.h"
#include "MouseEvent.h"
#include "Texture2D.h"

using namespace Framework;

// Contents of the Model2DData class from Model2D.h
// Constructor
Model2DData::Model2DData()
    : ReferenceCounter(),
      polygons(0),
      vLists(0),
      minP(0, 0),
      maxP(0, 0)
{}

// Destructor
Model2DData::~Model2DData()
{
    if (polygons)
    {
        int anz = polygons->getEntryCount();
        for (int i = 0; i < anz; i++)
        {
            if (polygons->get(i).name) polygons->get(i).name->release();
            if (polygons->get(i).tCoordinates)
                polygons->get(i).tCoordinates->release();
            if (polygons->get(i).vertex) polygons->get(i).vertex->release();
            if (polygons->get(i).center)
                delete polygons->get(i).center;
        }
        polygons = (Array<Polygon2D>*)polygons->release();
    }
    if (vLists) vLists->release();
}

// privat
bool Model2DData::isPointInside(Vertex p, int polygonId) const
{
    if (p < minP || p > maxP || !polygons) return 0;
    int num = 0;
    auto outListP = outList.begin();
    for (auto polygon = polygons->begin(); polygon;
        polygon++, num++, outListP++)
    {
        if (polygonId >= 0 && num != polygonId) continue;
        int anz = polygon._.vertex->getEntryCount();
        bool c = 0;
        int j = anz - 1;
        for (auto outListPP = outListP->begin(); outListPP; outListPP++)
        {
            Point out = outListPP;
            if (out.x < out.y && j > out.x && j < out.y) j = out.x;
            if (out.x > out.y && (j > out.x || j < out.y)) j = out.x;
        }
        auto point = polygon._.vertex->begin();
        for (int i = 0; i < anz; i++, point++)
        {
            bool cont = 0;
            for (auto outListPP = outListP->begin(); outListPP; outListPP++)
            {
                Point out = outListPP;
                if (out.x < out.y && i > out.x && i < out.y) cont = 1;
                if (out.x > out.y && (i > out.x || i < out.y)) cont = 1;
            }
            if (cont) continue;
            Vertex a = point;
            Vertex b = polygon._.vertex->get(j);
            if (((a.y >= p.y) != (b.y >= p.y))
                && (p.x
                    <= (b.x - a.x) * (p.y - a.y) / (float)(b.y - a.y) + a.x))
                c = !c;
            j = i;
        }
        if (c) return 1;
    }
    return 0;
}

bool Model2DData::isLineInside(Vertex a, Vertex b, int polygonId) const
{
    if (!polygons) return 0;
    int pAnz = polygons->getEntryCount();
    for (int p = 0; p < pAnz; p++)
    {
        if (polygonId >= 0 && p != polygonId) continue;
        int ola = outList.z(p)->getEntryCount();
        int anz = polygons->get(p).vertex->getEntryCount();
        int j = anz - 1;
        for (int k = 0; k < ola; k++)
        {
            Point out = outList.z(p)->get(k);
            if (out.x < out.y && j > out.x && j < out.y) j = out.x;
            if (out.x > out.y && (j > out.x || j < out.y)) j = out.x;
        }
        for (int i = 0; i < anz; i++)
        {
            bool cont = 0;
            for (int k = 0; k < ola; k++)
            {
                Point out = outList.z(p)->get(k);
                if (out.x < out.y && i > out.x && i < out.y) cont = 1;
                if (out.x > out.y && (i > out.x || i < out.y)) cont = 1;
            }
            if (cont) continue;
            Point va = polygons->get(p).vertex->get(i);
            Point vb = polygons->get(p).vertex->get(j);
            if ((Point)a == va && (Point)b == vb) return 1;
            if ((Point)a == vb && (Point)b == va) return 1;
            j = i;
        }
        Vertex len = b - a;
        Vertex speed(len.x > 0 ? 1 : -1.f, len.y > 0 ? 1 : -1.f);
        int mLen = 0;
        if (fabs(len.x) > fabs(len.y))
        {
            mLen = (int)fabs(len.x);
            speed.y = len.y / (float)fabs(len.x);
        }
        else
        {
            mLen = (int)fabs(len.y);
            speed.x = len.x / (float)fabs(len.y);
        }
        int i = 1;
        bool inside = 1;
        for (Vertex vp = speed + a;
            (Point)vp != (Point)(b - speed) && inside && i < mLen - 1;
            vp += speed, i++)
            inside &= isPointInside(vp, p);
        if (inside) return 1;
    }
    return 0;
}

// non-constant
bool Model2DData::createModell(Array<Polygon2D>* polygons)
{
    removeModell();
    if (!polygons || !polygons->getEntryCount())
    {
        this->polygons = polygons;
        vLists = new RCArray<RCArray<TriangleList<Vertex>>>();
        return 1;
    }
    this->polygons = polygons;
    int pAnz = polygons->getEntryCount();
    vLists = new RCArray<RCArray<TriangleList<Vertex>>>();
    for (int p = 0; p < pAnz; p++)
    {
        Polygon2D pg = polygons->get(p);
        if (!pg.vertex || pg.vertex->getEntryCount() < 3) continue;
        vLists->add(new RCArray<TriangleList<Vertex>>());
        outList.add(new Array<Point>);
        int vAnz = pg.vertex->getEntryCount();
        bool textur = pg.tCoordinates != 0;
        for (int i = 0; i < vAnz && textur; i++)
            textur &= pg.tCoordinates->has(i);
        for (int i = 0; i < vAnz; i++)
        {
            if ((float)maxP.x < fabs(pg.vertex->get(i).x))
            {
                maxP.x = abs((int)pg.vertex->get(i).x) + 1;
                maxP.y = abs((int)pg.vertex->get(i).x) + 1;
            }
            if ((float)maxP.y < fabs(pg.vertex->get(i).y))
            {
                maxP.x = abs((int)pg.vertex->get(i).y) + 1;
                maxP.y = abs((int)pg.vertex->get(i).y) + 1;
            }
        }
        minP = -maxP;
        if (!textur)
        {
            if (pg.tCoordinates) pg.tCoordinates->clear();
        }
        RCArray<RCArray<TriangleList<Vertex>>> lists;
        int lauf = 0;
        while (1)
        {
            lists.add(new RCArray<TriangleList<Vertex>>());
            outList.z(p)->add(Point(0, 0));
            bool fertig = 0;
            Vertex a;
            Vertex b;
            Array<Point> tmpOutList;
            for (int i = 0; i < vAnz; i++)
            {
                bool cont = 0;
                int vorher = i - 1;
                int nachher = i + 1;
                if (nachher >= vAnz) nachher = 0;
                if (vorher < 0) vorher = vAnz - 1;
                int ola = outList.z(p)->getEntryCount();
                for (int j = 0; j < ola; j++)
                {
                    Point out = outList.z(p)->get(j);
                    if (out.x < out.y)
                    {
                        if (nachher > out.x && nachher < out.y) nachher = out.y;
                        if (vorher > out.x && vorher < out.y) vorher = out.x;
                    }
                    if (out.x > out.y)
                    {
                        if (nachher > out.x || nachher < out.y) nachher = out.y;
                        if (vorher > out.x || vorher < out.y) vorher = out.x;
                    }
                    if (out.x < out.y && i > out.x && i < out.y) cont = 1;
                    if (out.x > out.y && (i > out.x || i < out.y)) cont = 1;
                }
                if (cont) continue;
                if (vorher < 0)
                    a = pg.vertex->get(vAnz + vorher);
                else
                    a = pg.vertex->get(vorher);
                if (nachher > vAnz - 1)
                    b = pg.vertex->get(nachher - vAnz + 1);
                else
                    b = pg.vertex->get(nachher);
                if (isLineInside(a, b, p))
                {
                    TriangleList<Vertex>* lowL = new TriangleList<Vertex>();
                    TriangleList<Vertex>* heightL = new TriangleList<Vertex>();
                    lowL->addPoint(new Vertex(pg.vertex->get(i)),
                        textur ? new Vertex(pg.tCoordinates->get(i)) : 0);
                    heightL->addPoint(new Vertex(pg.vertex->get(i)),
                        textur ? new Vertex(pg.tCoordinates->get(i)) : 0);
                    int height = i + 1;
                    int low = i - 1;
                    Point outL(0, 0);
                    Point outH(0, 0);
                    for (int k = 0; k < 2; k++)
                    {
                        bool lowp = !k;
                        while (1)
                        {
                            if (height >= vAnz) height = 0;
                            if (low < 0) low = vAnz - 1;
                            for (int j = 0; j <= lauf; j++)
                            {
                                Point out = outList.z(p)->get(j);
                                if (out.x < out.y)
                                {
                                    if (height > out.x && height < out.y)
                                        height = out.y;
                                    if (low > out.x && low < out.y) low = out.x;
                                }
                                if (out.x > out.y)
                                {
                                    if (height > out.x || height < out.y)
                                        height = out.y;
                                    if (low > out.x || low < out.y) low = out.x;
                                }
                            }
                            Vertex a = pg.vertex->get(height);
                            Vertex b = pg.vertex->get(low);
                            if (low == height)
                            {
                                fertig = 1;
                                outList.z(p)->set(Point(0, 0), lauf);
                                if (!k)
                                    lowL->addPoint(new Vertex(b),
                                        textur ? new Vertex(
                                                     pg.tCoordinates->get(low))
                                               : 0);
                                else
                                    heightL->addPoint(new Vertex(b),
                                        textur ? new Vertex(
                                                     pg.tCoordinates->get(low))
                                               : 0);
                                break;
                            }
                            bool inside = isLineInside(a, b, p);
                            if (inside)
                            {
                                if (!k)
                                    outL = Point(low, height);
                                else
                                    outH = Point(low, height);
                                outList.z(p)->set(Point(low, height), lauf);
                            }
                            if (lowp)
                            {
                                if (!k)
                                    lowL->addPoint(new Vertex(b),
                                        textur ? new Vertex(
                                                     pg.tCoordinates->get(low))
                                               : 0);
                                else
                                    heightL->addPoint(new Vertex(b),
                                        textur ? new Vertex(
                                                     pg.tCoordinates->get(low))
                                               : 0);
                                low--;
                            }
                            else
                            {
                                if (!k)
                                    lowL->addPoint(new Vertex(a),
                                        textur ? new Vertex(pg.tCoordinates->get(
                                                     height))
                                               : 0);
                                else
                                    heightL->addPoint(new Vertex(a),
                                        textur ? new Vertex(pg.tCoordinates->get(
                                                     height))
                                               : 0);
                                height++;
                            }
                            lowp = !lowp;
                            if (!inside)
                            {
                                height = i + 1;
                                low = i - 1;
                                outList.z(p)->set(Point(0, 0), lauf);
                                break;
                            }
                        }
                        if (fertig) break;
                    }
                    if (lowL->getTriangleCount() > heightL->getTriangleCount())
                    {
                        lists.z(lauf)->add(lowL);
                        tmpOutList.add(outL);
                        heightL->release();
                    }
                    else
                    {
                        lists.z(lauf)->add(heightL);
                        tmpOutList.add(outH);
                        lowL->release();
                    }
                }
                else
                    lists.z(lauf)->add(new TriangleList<Vertex>());
                if (fertig) break;
            }
            int maxP = -1;
            int max = 0;
            for (int i = 0; i < vAnz; i++)
            {
                if (lists.z(lauf)->has(i)
                    && lists.z(lauf)->z(i)->getTriangleCount() > max)
                {
                    max = lists.z(lauf)->z(i)->getTriangleCount();
                    maxP = i;
                }
            }
            if (!max || maxP < 0) break;
            vLists->z(p)->add(lists.z(lauf)->get(maxP));
            outList.z(p)->set(tmpOutList.get(maxP), lauf);
            if (fertig) break;
            lauf++;
        }
        outList.z(p)->clear();
    }
    return 1;
}

void Model2DData::removeModell() // resets the vertex data
{
    if (polygons)
    {
        int anz = polygons->getEntryCount();
        for (int i = 0; i < anz; i++)
        {
            if (polygons->get(i).name) polygons->get(i).name->release();
            if (polygons->get(i).tCoordinates)
                polygons->get(i).tCoordinates->release();
            if (polygons->get(i).vertex) polygons->get(i).vertex->release();
            if (polygons->get(i).center)
                delete polygons->get(i).center;
        }
        polygons = (Array<Polygon2D>*)polygons->release();
    }
    if (vLists)
        vLists = (RCArray<RCArray<TriangleList<Vertex>>>*)vLists->release();
    outList.clear();
    minP = Point(0, 0);
    maxP = Point(0, 0);
}

bool Model2DData::calcHitPoint(Vertex pos,
    Vertex dir,
    const char* polygonName,
    Vertex& hitpoint,
    Vertex& moveSpeed,
    float& rotSpeed) const
{
    if (dir.x == 0 && dir.y == 0) return 0;
    bool ret = 0;
    for (const Polygon2D& polygon : *polygons)
    {
        if (polygon.name->isEqual(polygonName))
        {
            int anz = polygon.vertex->getEntryCount();
            for (int i = 0; i < anz; i++)
            {
                Vertex a = polygon.vertex->get(i);
                Vertex b = polygon.vertex->get((i + 1) % anz);
                b -= a;
                float offset = 0;
                if (dir.y != 0 && dir.x != 0)
                    offset = ((a.y - pos.y) / dir.y - (a.x - pos.x) / dir.x)
                           / (b.x / dir.x
                               - b.y / dir.y); // solve hitpoint equasion
                else if (dir.y == 0)
                {
                    if (b.y == 0) continue;
                    offset = (pos.y - a.y) / b.y;
                }
                else if (dir.x == 0)
                {
                    if (b.x == 0) continue;
                    offset = (pos.x - a.x) / b.x;
                }
                Vertex point = a + (b * offset);
                if (offset >= 0 && offset <= 1)
                {
                    float f = (point.x - pos.x) / dir.x;
                    if (!dir.x) f = (point.y - pos.y) / dir.y;
                    if ((!ret
                            || (hitpoint - pos).getLengthSq()
                                   > (point - pos).getLengthSq())
                        && f > 0)
                    {
                        Vertex normal = b.CW90().normalize();
                        Vertex kNorm = Vertex(dir).normalize();
                        moveSpeed = normal * (normal * kNorm) * dir.getLength();
                        normal
                            = (point - *polygon.center).CW90().normalize();
                        Vertex rotKraft
                            = normal * (normal * kNorm) * dir.getLength();
                        rotSpeed
                            = ((float)sqrt(
                                   rotKraft.getLength()
                                   * (point - *polygon.center).getLength())
                                  / 180.f)
                            * 3.14f * (normal * kNorm);
                        hitpoint = point;
                        if (isnan(moveSpeed.x) || isnan(moveSpeed.y)
                            || isnan(rotSpeed))
                            return 0;
                        ret = 1;
                    }
                }
            }
        }
    }
    return ret;
}

bool Model2DData::split(Vertex pos,
    Vertex dir,
    char* polygonName,
    Polygon2D& partA,
    Polygon2D& partB,
    Point& posA,
    Point& posB,
    std::function<double()> random) const
{
    Vertex originalDir = dir;
    bool ret = 0;
    int num = 0;
    for (const Polygon2D& polygon : *polygons)
    {
        if (polygon.name->isEqual(polygonName))
        {
            while (isPointInside(pos, num))
            {
                pos -= dir;
            }
            int anz = polygon.vertex->getEntryCount();
            Vertex startPoint;
            Vertex texturSP;
            int leftI = 0;
            int rightI = 0;
            Vertex txtChpPix(0, 0);
            for (int i = 0; i < anz; i++)
            {
                Vertex a = polygon.vertex->get(i);
                Vertex b = polygon.vertex->get((i + 1) % anz);
                b -= a;
                if ((txtChpPix.x == 0 || txtChpPix.y == 0) && b.x != 0
                    && b.y != 0)
                {
                    Vertex ta = polygon.tCoordinates->get(i);
                    Vertex tb = polygon.tCoordinates->get((i + 1) % anz);
                    tb -= ta;
                    txtChpPix = Vertex(tb.x / b.x, tb.y / b.y);
                }
                float offset = 0;
                if (dir.y != 0 && dir.x != 0)
                    offset = ((a.y - pos.y) / dir.y - (a.x - pos.x) / dir.x)
                           / (b.x / dir.x
                               - b.y / dir.y); // solve hitpoint equasion
                else if (dir.y == 0)
                    offset = (pos.y - a.y) / b.y;
                else if (dir.x == 0)
                    offset = (pos.x - a.x) / b.x;
                Vertex point = a + (b * offset);
                if (offset >= 0 && offset <= 1)
                {
                    if (!ret
                        || (startPoint - pos).getLengthSq()
                               > (point - pos).getLengthSq())
                    {
                        leftI = i;
                        rightI = (i + 1) % anz;
                        startPoint = point;
                        texturSP = polygon.tCoordinates->get(i)
                                 + (polygon.tCoordinates->get((i + 1) % anz)
                                       - polygon.tCoordinates->get(i))
                                       * offset;
                    }
                    ret = 1;
                }
            }
            if (ret)
            {
                partA.transparent = polygon.transparent;
                partA.center = new Vertex(0, 0);
                partA.tCoordinates = new Array<Vertex>();
                partA.name = new Text(polygon.name->getText());
                partA.vertex = new Array<Vertex>();
                partB.transparent = polygon.transparent;
                partB.center = new Vertex(0, 0);
                partB.tCoordinates = new Array<Vertex>();
                partB.name = new Text(polygon.name->getText());
                partB.vertex = new Array<Vertex>();
                *partA.center += startPoint;
                *partB.center += startPoint;
                partA.vertex->add(startPoint);
                partB.vertex->add(startPoint);
                partA.tCoordinates->add(texturSP);
                partB.tCoordinates->add(texturSP);
                int leftIE = 0;
                int rightIE = 0;
                while (1)
                {
                    pos = startPoint;
                    Vertex next = startPoint + dir;
                    Vertex nextT
                        = texturSP
                        + Vertex(dir.x * txtChpPix.x, dir.y * txtChpPix.y);
                    ret = 0;
                    bool needOne = !isPointInside(next);
                    int bestI = -1;
                    float bo1 = 1000;
                    float bo2 = 1000;
                    for (int i = 0; i < anz; i++)
                    {
                        if (i == leftI) continue;
                        Vertex a = polygon.vertex->get(i);
                        Vertex b = polygon.vertex->get((i + 1) % anz);
                        b -= a;
                        float offset1 = 0;
                        if (dir.y != 0 && dir.x != 0)
                            offset1
                                = ((a.y - pos.y) / dir.y
                                      - (a.x - pos.x) / dir.x)
                                / (b.x / dir.x
                                    - b.y / dir.y); // solve hitpoint equasion
                        else if (dir.y == 0)
                            offset1 = (pos.y - a.y) / b.y;
                        else if (dir.x == 0)
                            offset1 = (pos.x - a.x) / b.x;
                        Vertex point = a + (b * offset1);
                        float offset2 = 0;
                        if (dir.x != 0)
                            offset2 = (point.x - pos.x) / dir.x;
                        else
                            offset2 = (point.y - pos.y) / dir.y;
                        if (needOne
                            && MIN(abs(bo1), abs(bo1 - 1))
                                       + MIN(abs(bo2), bo2 - 1)
                                   > MIN(abs(offset1), abs(offset1 - 1))
                                         + MIN(abs(offset2), abs(offset2 - 1)))
                        {
                            bo1 = offset1;
                            bo2 = offset2;
                            bestI = i;
                        }
                        if (offset1 >= 0 && offset1 <= 1 && offset2 >= 0
                            && offset2 <= 1)
                        {
                            if (!ret
                                || (startPoint - pos).getLengthSq()
                                       > (point - pos).getLengthSq())
                            {
                                leftIE = i;
                                rightIE = (i + 1) % anz;
                                startPoint = point;
                                texturSP
                                    = polygon.tCoordinates->get(i)
                                    + (polygon.tCoordinates->get((i + 1) % anz)
                                          - polygon.tCoordinates->get(i))
                                          * offset1;
                            }
                            ret = 1;
                        }
                    }
                    if (needOne && !ret)
                    {
                        Vertex a = polygon.vertex->get(bestI);
                        Vertex b = polygon.vertex->get((bestI + 1) % anz);
                        b -= a;
                        leftIE = bestI;
                        rightIE = (bestI + 1) % anz;
                        startPoint = a + (b * bo1);
                        texturSP = polygon.tCoordinates->get(bestI)
                                 + (polygon.tCoordinates->get((bestI + 1) % anz)
                                       - polygon.tCoordinates->get(bestI))
                                       * bo1;
                        ret = 1;
                    }
                    if (ret) break;
                    *partA.center += next;
                    *partB.center += next;
                    partA.vertex->add(next);
                    partB.vertex->add(next);
                    partA.tCoordinates->add(nextT);
                    partB.tCoordinates->add(nextT);
                    startPoint = next;
                    texturSP = nextT;
                    dir = originalDir.rotation((float)(random() - 0.5));
                }
                *partA.center += startPoint;
                *partB.center += startPoint;
                partA.vertex->add(startPoint);
                partB.vertex->add(startPoint);
                partA.tCoordinates->add(texturSP);
                partB.tCoordinates->add(texturSP);
                for (int i = rightIE; i != leftI; i++)
                {
                    i = i % anz;
                    if (i == leftI) break;
                    *partA.center += polygon.vertex->get(i);
                    partA.vertex->add(polygon.vertex->get(i));
                    partA.tCoordinates->add(polygon.tCoordinates->get(i));
                }
                *partA.center += polygon.vertex->get(leftI);
                partA.vertex->add(polygon.vertex->get(leftI));
                partA.tCoordinates->add(polygon.tCoordinates->get(leftI));
                for (int i = leftIE; i != rightI; i--)
                {
                    if (i < 0) i += anz;
                    if (i == rightI) break;
                    *partB.center += polygon.vertex->get(i);
                    partB.vertex->add(polygon.vertex->get(i));
                    partB.tCoordinates->add(polygon.tCoordinates->get(i));
                }
                *partB.center += polygon.vertex->get(rightI);
                partB.vertex->add(polygon.vertex->get(rightI));
                partB.tCoordinates->add(polygon.tCoordinates->get(rightI));
                *partA.center /= (float)partA.vertex->getEntryCount();
                *partB.center /= (float)partB.vertex->getEntryCount();
                posA = (Point)*partA.center;
                posB = (Point)*partB.center;
                for (int i = 0; i < partA.vertex->getEntryCount(); i++)
                    partA.vertex->set(
                        partA.vertex->get(i) - *partA.center, i);
                for (int i = 0; i < partB.vertex->getEntryCount(); i++)
                    partB.vertex->set(
                        partB.vertex->get(i) - *partB.center, i);
                *partA.center = Vertex(0, 0);
                *partB.center = Vertex(0, 0);
            }
        }
        num++;
    }
    return ret;
}

float Model2DData::getMasse() const
{
    float m = 0;
    for (const Polygon2D& p : *polygons)
    {
        if (p.transparent) continue;
        int anz = p.vertex->getEntryCount();
        if (anz < 3) continue;
        Vertex p1 = p.vertex->get(anz - 1);
        Vertex p2 = p.vertex->get(0);
        m += (p1.y + p2.y) * (p1.x - p2.x);
        for (int i = 1; i < anz; i++)
        {
            p1 = p.vertex->get(i - 1);
            p2 = p.vertex->get(i);
            m += (p1.y + p2.y) * (p1.x - p2.x);
        }
    }
    m *= 0.5f;
    return m;
}

// Contents of the Model2D class from Model2D.h
// Constructor
Model2DObject::Model2DObject()
    : Object2D()
{
    rData = 0;
    texture = new RCArray<Texture2D>();
}

// Destructor
Model2DObject::~Model2DObject()
{
    if (rData) rData->release();
    texture->release();
}

// non-constant
void Model2DObject::setModel(Model2DData* mdl)
{
    if (rData) rData->release();
    rData = mdl;
}

void Model2DObject::setTexture(Texture2D* t)
{
    if (rData)
    {
        texture->clear();
        for (int i = 0; i < rData->polygons->getEntryCount(); i++)
            texture->add(dynamic_cast<Texture2D*>(t->getThis()));
    }
    t->release();
}

void Model2DObject::impuls(Vertex start, Vertex speed, float strength)
{
    start = getObjectPos(start);
    speed = getObjectDir(speed);
    if (rData)
    {
        Vertex resSpeed;
        float resRotSpeed = 0;
        Vertex hp;
        Vertex mSpeed;
        float rSpeed;
        float dist = INFINITY;
        for (const Polygon2D& p : *rData->polygons)
        {
            if (!p.transparent
                && rData->calcHitPoint(
                    start, speed, p.name->getText(), hp, mSpeed, rSpeed))
            {
                float f = (hp.x - start.x) / speed.x;
                if (!speed.x) f = (hp.y - start.y) / speed.y;
                if ((hp - start).getLengthSq() < dist && f > 0)
                {
                    resSpeed = mSpeed.rotation(rotation);
                    resRotSpeed = rSpeed;
                    dist = (hp - start).getLengthSq();
                }
            }
        }
        // TODO loop over all polygons and translate start and speed into object
        // coordinates
        if (dist < INFINITY)
        {
            this->speed += resSpeed * strength;
            this->rSpeed += resRotSpeed * strength;
        }
    }
}

void Model2DObject::setTexture(Texture2D* t, const char* polygonName)
{
    int index = 0;
    for (const Polygon2D& i : *rData->polygons)
    {
        if (i.name->isEqual(polygonName))
            texture->set(dynamic_cast<Texture2D*>(t->getThis()), index);
        index++;
    }
    t->release();
}

void Model2DObject::render(
    Mat3<float>& kamMat, Image& zRObj, const char* kamName)
{
    if (!rData || !rData->polygons || !texture) return;
    int num = 0;
    for (auto p : *rData->vLists)
    {
        Mat3<float> mat = kamMat * getObjectMatrix();
        if (texture->has(num))
        {
            Image* txt = texture->z(num)->zTexture();
            for (auto i = p->begin(); i && txt; i++)
            {
                for (auto j = i->zListe()->begin();
                    j.hasNext() && j.next().hasNext();
                    j++)
                {
                    Vertex a = mat * *j->point;
                    Vertex b = mat * *j.next()->point;
                    Vertex c = mat * *j.next().next()->point;
                    Point ta = (Point)Vertex(
                        j->texture->x * (float)(txt->getWidth() - 1),
                        j->texture->y * (float)(txt->getHeight() - 1));
                    Point tb = (Point)Vertex(
                        j.next()->texture->x * (float)(txt->getWidth() - 1),
                        j.next()->texture->y * (float)(txt->getHeight() - 1));
                    Point tc
                        = (Point)Vertex(j.next().next()->texture->x
                                            * (float)(txt->getWidth() - 1),
                            j.next().next()->texture->y
                                * (float)(txt->getHeight() - 1));
                    zRObj.drawTriangleTextureAlpha(a, b, c, ta, tb, tc, *txt);
                }
            }
        }
        num++;
    }
    /* Draws 2D Mesh
    for( auto *p = &rData->vListen->getIterator(); p && p->set; p = p->next,
    num++ )
    {
        Mat3< float > mat = kamMat * getObjectMatrix();
        for( auto *i = &p->var->getArray(); i && i->set; i = i->next )
        {
            for( auto *j = &i->var->zListe()->getArray(); j->next->next &&
    j->next->next->set; j = j->next )
            {
                Vertex a = mat * *j->var->point;
                Vertex b = mat * *j->next->var->point;
                Vertex c = mat * *j->next->next->var->point;
                zRObj.drawLine( a, b, 0xFFFFFFFF );
                zRObj.drawLine( a, c, 0xFFFFFFFF );
                zRObj.drawLine( b, c, 0xFFFFFFFF );
            }
        }
    }
    */
}

// constant
bool Model2DObject::isPointInside(Vertex p, bool ignoreTransparent) const
{
    if (!rData) return 0;
    p -= position;
    if (p < Mat3<float>::scaling(size) * rData->minP
        || p > Mat3<float>::scaling(size) * rData->maxP || !rData->polygons)
        return 0;
    Mat3<float> mat
        = Mat3<float>::rotation(-rotation) * Mat3<float>::scaling(1 / size);
    p = mat * p;
    for (const Polygon2D& polygon : *rData->polygons)
    {
        if (polygon.transparent && !ignoreTransparent) continue;
        bool c = 0;
        for (auto point = polygon.vertex->begin(); point; point++)
        {
            Vertex a;
            if (point.next())
                a = point.next();
            else
                a = polygon.vertex->get(0);
            Vertex b = point;
            if (((a.y >= p.y) != (b.y >= p.y))
                && (p.x
                    <= (b.x - a.x) * (p.y - a.y) / (float)(b.y - a.y) + a.x))
                c = !c;
        }
        if (c) return 1;
    }
    return 0;
}

bool Model2DObject::isLineInside(
    Vertex a, Vertex b, bool ignoreTransparent) const
{
    if (!rData || !rData->polygons) return 0;
    int pAnz = rData->polygons->getEntryCount();
    for (int p = 0; p < pAnz; p++)
    {
        if (rData->polygons->get(p).transparent && !ignoreTransparent) continue;
        Mat3<float> mat
            = Mat3<float>::rotation(rotation) * Mat3<float>::scaling(size);
        int anz = rData->polygons->get(p).vertex->getEntryCount();
        int j = anz - 1;
        for (int i = 0; i < anz; i++)
        {
            Point va = mat * rData->polygons->get(p).vertex->get(i);
            Point vb = mat * rData->polygons->get(p).vertex->get(j);
            if ((Point)a == position + va && (Point)b == position + vb)
                return 1;
            if ((Point)a == position + vb && (Point)b == position + va)
                return 1;
            j = i;
        }
        Vertex len = b - a;
        Vertex speed(len.x > 0 ? 1 : -1.f, len.y > 0 ? 1 : -1.f);
        int mLen = 0;
        if (fabs(len.x) > fabs(len.y))
        {
            mLen = (int)fabs(len.x);
            speed.y = len.y / (float)fabs(len.x);
        }
        else
        {
            mLen = (int)fabs(len.y);
            speed.x = len.x / (float)fabs(len.y);
        }
        int i = 1;
        bool inside = 1;
        for (Vertex vp = speed + a;
            (Point)vp != (Point)(b - speed) && inside && i < mLen - 1;
            vp += speed, i++)
            inside &= isPointInside(vp, ignoreTransparent);
        if (inside) return 1;
    }
    return 0;
}

bool Model2DObject::isModelInside(
    const Object2D* zObj, Vertex* sp, bool end, bool ignoreTransparent) const
{
    if (!end)
    {
        if (!getBoundingBox().collidesWith(zObj->getBoundingBox())) return 0;
    }
    Mat3<float> mat = getObjectMatrix();
    for (const Polygon2D& polygon : *rData->polygons)
    {
        if (polygon.transparent && !ignoreTransparent) continue;
        int anz = polygon.vertex->getEntryCount();
        for (int i = 0; i < anz; i++)
        {
            if (zObj->isPointInside(
                    mat * polygon.vertex->get(i), ignoreTransparent))
            {
                if (sp) *sp = mat * polygon.vertex->get(i);
                return 1;
            }
        }
    }
    if (end) return 0;
    return zObj->isModelInside(this, sp, 1, ignoreTransparent);
}

Rect2<float> Model2DObject::getBoundingBox() const
{
    if (rData)
        return Rect2<float>{(Vertex)rData->minP * size + position,
            (Vertex)rData->maxP * size + position};
    return Rect2<float>();
}

bool Model2DObject::calcHitPoint(Vertex pos, Vertex dir, Vertex& hitpoint) const
{
    pos = getObjectPos(pos);
    dir = getObjectDir(dir);
    Vertex ms;
    Vertex hp;
    float rs;
    float dist = INFINITY;
    if (rData)
    {
        for (const Polygon2D& p : *rData->polygons)
        {
            if (!p.transparent
                && rData->calcHitPoint(pos, dir, p.name->getText(), hp, ms, rs))
            {
                float f = (hp.x - pos.x) / dir.x;
                if (!speed.x) f = (hp.y - pos.y) / dir.y;
                if ((hp - pos).getLengthSq() < dist && f > 0)
                {
                    hitpoint = getObjectMatrix() * hp;
                    dist = (hp - pos).getLengthSq();
                }
            }
        }
        if (dist < INFINITY) return 1;
    }
    return 0;
}

float Model2DObject::getAirResistance() const
{
    if (!rData) return 0;
    float angle = speed.angle(Vertex(1, 0));
    float faktor = -1;
    if (getRotation() > PI) faktor = -faktor;
    if (getRotation() < -PI) faktor = -faktor;
    Mat3<float> m = Mat3<float>::rotation(rotation + faktor * angle)
                  * Mat3<float>::scaling(size);
    float yMin = INFINITY;
    float yMax = -INFINITY;
    for (Polygon2D p : *rData->polygons)
    {
        if (p.transparent) continue;
        for (Vertex point : *p.vertex)
        {
            Vertex v = m * point;
            if (v.y > yMax) yMax = v.y;
            if (v.y < yMin) yMin = v.y;
        }
    }
    if (yMin != INFINITY)
    {
        return yMax - yMin;
    }
    return 0;
}

float Model2DObject::getMass() const
{
    if (!rData) return 0;
    return abs(rData->getMasse() * size * size);
}

// Returns the texture of the first polygon
Texture2D* Model2DObject::getTexture() const
{
    return texture->get(0);
}

// Returns the texture of a polygon
//  polygonName: The name of the polygon
Texture2D* Model2DObject::getTexture(const char* polygonName) const
{
    int index = 0;
    for (const Polygon2D& p : *rData->polygons)
    {
        if (p.name->isEqual(polygonName)) return texture->get(index);
        index++;
    }
    return 0;
}

// Returns the texture of the first polygon without increased reference counter
Texture2D* Model2DObject::zTexture() const
{
    return texture->z(0);
}

// Returns the texture of a polygon without increased reference counter
//  polygonName: The name of the polygon
Texture2D* Model2DObject::zTexture(const char* polygonName) const
{
    int index = 0;
    for (const Polygon2D& p : *rData->polygons)
    {
        if (p.name->isEqual(polygonName)) return texture->z(index);
        index++;
    }
    return 0;
}

Model2DData* Model2DObject::getModel() const
{
    return rData ? dynamic_cast<Model2DData*>(rData->getThis()) : 0;
}

Model2DData* Model2DObject::zModel() const
{
    return rData;
}

// Contents of the Model2D class from Model2D.h
// Constructor
Model2D::Model2D()
    : Drawable()
{
    color = 0;
    style = 0;
    rData = 0;
    rotation = 0;
    size = 1;
    textur = new RCArray<Texture2D>;
}

// Destructor
Model2D::~Model2D()
{
    if (rData) rData->release();
    textur->release();
}

// non-constant
void Model2D::setModel(Model2DData* mdl)
{
    if (rData) rData->release();
    rData = mdl;
}

void Model2D::setRotation(float rotation)
{
    this->rotation = rotation;
    while (this->rotation > PI * 2)
        this->rotation -= (float)PI * 2;
    while (this->rotation < 0)
        this->rotation += (float)PI * 2;
    rend = 1;
}

void Model2D::addRotation(float rotation)
{
    this->rotation += rotation;
    while (this->rotation > PI * 2)
        this->rotation -= (float)PI * 2;
    while (this->rotation < 0)
        this->rotation += (float)PI * 2;
    rend = 1;
}

void Model2D::setSize(float size)
{
    this->size = size;
    rend = 1;
}

void Model2D::addSize(float size)
{
    this->size += size;
    rend = 1;
}

void Model2D::setTexture(Texture2D* t)
{
    if (rData)
    {
        for (int i = 0; i < rData->polygons->getEntryCount(); i++)
            textur->set(dynamic_cast<Texture2D*>(t->getThis()), i);
    }
    t->release();
}

void Model2D::setTexture(Texture2D* t, const char* polygonName)
{
    int index = 0;
    for (const Polygon2D& p : *rData->polygons)
    {
        if (p.name->isEqual(polygonName))
            textur->set(dynamic_cast<Texture2D*>(t->getThis()), index);
        index++;
    }
    t->release();
}

void Model2D::setColor(int f)
{
    color = f;
    rend = 1;
}

bool Model2D::tick(double tickVal)
{
    return Drawable::tick(tickVal);
}

void Model2D::render(Image& zRObj)
{
    if (!rData || hasStyleNot(Model2D::Style::Visible) || !rData->polygons)
        return;
    Drawable::render(zRObj);
    int num = 0;
    for (auto p : *rData->vLists)
    {
        Mat3<float> mat = Mat3<float>::translation(pos)
                        * Mat3<float>::rotation(rotation)
                        * Mat3<float>::scaling(size);
        if (hasStyle(Model2D::Style::Texture))
        {
            if (!textur || !textur->z(num) || !textur->z(num)->zTexture()
                || !rData->polygons->get(num).tCoordinates)
            {
                for (auto i : *p)
                {
                    for (auto j = i->zListe()->begin();
                        j.hasNext() && j.next().hasNext();
                        j++)
                    {
                        Vertex a = mat * *j->point;
                        Vertex b = mat * *j.next()->point;
                        Vertex c = mat * *j.next().next()->point;
                        if (hasStyle(Model2D::Style::Alpha))
                            zRObj.drawTriangleAlpha(a, b, c, color);
                        else
                            zRObj.drawTriangle(a, b, c, color);
                    }
                }
            }
            else
            {
                Image* txt = textur->z(num)->zTexture();
                for (auto i : *p)
                {
                    for (auto j = i->zListe()->begin();
                        j.hasNext() && j.next().hasNext();
                        j++)
                    {
                        Vertex a = mat * *j->point;
                        Vertex b = mat * *j.next()->point;
                        Vertex c = mat * *j.next().next()->point;
                        Point ta = (Point)Vertex(
                            j->texture->x * (float)(txt->getWidth() - 1),
                            j->texture->y * (float)(txt->getHeight() - 1));
                        Point tb = (Point)Vertex(
                            j.next()->texture->x * (float)(txt->getWidth() - 1),
                            j.next()->texture->y
                                * (float)(txt->getHeight() - 1));
                        Point tc
                            = (Point)Vertex(j.next().next()->texture->x
                                                * (float)(txt->getWidth() - 1),
                                j.next().next()->texture->y
                                    * (float)(txt->getHeight() - 1));
                        if (hasStyle(Model2D::Style::Alpha))
                            zRObj.drawTriangleTextureAlpha(
                                a, b, c, ta, tb, tc, *txt);
                        else
                            zRObj.drawTriangleTexture(a, b, c, ta, tb, tc, *txt);
                    }
                }
            }
        }
        if (hasStyle(Model2D::Style::Mesh))
        {
            for (auto i : *p)
            {
                for (auto j = i->zListe()->begin();
                    j.hasNext() && j.next().hasNext();
                    j++)
                {
                    Vertex a = mat * *j->point;
                    Vertex b = mat * *j.next()->point;
                    Vertex c = mat * *j.next().next()->point;
                    if (hasStyle(Model2D::Style::Alpha))
                    {
                        zRObj.drawLineAlpha(a, b, color);
                        zRObj.drawLineAlpha(b, c, color);
                        zRObj.drawLineAlpha(c, a, color);
                    }
                    else
                    {
                        zRObj.drawLine(a, b, color);
                        zRObj.drawLine(b, c, color);
                        zRObj.drawLine(c, a, color);
                    }
                }
            }
        }
        if (hasStyle(Model2D::Style::Border))
        {
            auto beg = rData->polygons->get(num).vertex->begin();
            if (beg)
            {
                Vertex letzter;
                for (auto e = beg; e && e.hasNext(); e++)
                {
                    if (hasStyle(Model2D::Style::Alpha))
                        zRObj.drawLineAlpha(
                            mat * e._, mat * e.next()._, color);
                    else
                        zRObj.drawLine(mat * e._, mat * e.next()._, color);
                    letzter = e.next();
                }
                if (beg.hasNext())
                {
                    if (hasStyle(Model2D::Style::Alpha))
                        zRObj.drawLineAlpha(mat * letzter, mat * beg._, color);
                    else
                        zRObj.drawLine(mat * letzter, mat * beg._, color);
                }
            }
        }
        num++;
    }
}

// constant
float Model2D::getRotation() const
{
    return rotation;
}

float Model2D::getSize() const
{
    return size;
}

// Returns whether a point is contained in the model
//  p: The point
bool Model2D::isPointInside(int x, int y) const
{
    return isPointInside(Vertex((float)x, (float)y));
}

bool Model2D::isPointInside(Vertex p) const
{
    if (!rData) return 0;
    p -= pos;
    if (p < Mat3<float>::scaling(size) * rData->minP
        || p > Mat3<float>::scaling(size) * rData->maxP || !rData->polygons)
        return 0;
    for (const Polygon2D& polygon : *rData->polygons)
    {
        if (polygon.transparent) continue;
        Mat3<float> mat
            = Mat3<float>::rotation(rotation) * Mat3<float>::scaling(size);
        int anz = polygon.vertex->getEntryCount();
        bool c = 0;
        int j = anz - 1;
        for (int i = 0; i < anz; i++)
        {
            Vertex a = mat * polygon.vertex->get(i);
            Vertex b = mat * polygon.vertex->get(j);
            if (((a.y >= p.y) != (b.y >= p.y))
                && (p.x
                    <= (b.x - a.x) * (p.y - a.y) / (float)(b.y - a.y) + a.x))
                c = !c;
            j = i;
        }
        if (c) return 1;
    }
    return 0;
}

bool Model2D::isLineInside(Vertex a, Vertex b) const
{
    if (!rData || !rData->polygons) return 0;
    int pAnz = rData->polygons->getEntryCount();
    for (int p = 0; p < pAnz; p++)
    {
        if (rData->polygons->get(p).transparent) continue;
        Mat3<float> mat
            = Mat3<float>::rotation(rotation) * Mat3<float>::scaling(size);
        int anz = rData->polygons->get(p).vertex->getEntryCount();
        int j = anz - 1;
        for (int i = 0; i < anz; i++)
        {
            Point va = mat * rData->polygons->get(p).vertex->get(i);
            Point vb = mat * rData->polygons->get(p).vertex->get(j);
            if ((Point)a == pos + va && (Point)b == pos + vb) return 1;
            if ((Point)a == pos + vb && (Point)b == pos + va) return 1;
            j = i;
        }
        Vertex len = b - a;
        Vertex speed(len.x > 0 ? 1 : -1.f, len.y > 0 ? 1 : -1.f);
        int mLen = 0;
        if (fabs(len.x) > fabs(len.y))
        {
            mLen = (int)fabs(len.x);
            speed.y = len.y / (float)fabs(len.x);
        }
        else
        {
            mLen = (int)fabs(len.y);
            speed.x = len.x / (float)fabs(len.y);
        }
        int i = 1;
        bool inside = 1;
        for (Vertex vp = speed + a;
            (Point)vp != (Point)(b - speed) && inside && i < mLen - 1;
            vp += speed, i++)
            inside &= isPointInside(vp);
        if (inside) return 1;
    }
    return 0;
}

bool Model2D::isModelInside(const Model2D* zMdl, bool end) const
{
    if (!end)
    {
        Vertex min = (Vertex)rData->minP * size + pos;
        Vertex max = (Vertex)rData->maxP * size + pos;
        Vertex min2 = (Vertex)zMdl->zModel()->minP * zMdl->getSize()
                    + zMdl->getPosition();
        Vertex max2 = (Vertex)zMdl->zModel()->maxP * zMdl->getSize()
                    + zMdl->getPosition();
        if (max.x < min2.x || min.x > max2.x || max.y < min2.y
            || min.y > max2.y)
            return 0;
    }
    Mat3<float> mat = Mat3<float>::translation(pos)
                    * Mat3<float>::rotation(rotation)
                    * Mat3<float>::scaling(size);
    for (const Polygon2D& polygon : *rData->polygons)
    {
        if (polygon.transparent) continue;
        int anz = polygon.vertex->getEntryCount();
        for (int i = 0; i < anz; i++)
        {
            if (zMdl->isPointInside(mat * polygon.vertex->get(i))) return 1;
        }
    }
    if (end) return 0;
    return zMdl->isModelInside(this, 1);
}

Model2DData* Model2D::getModel() const
{
    return rData ? dynamic_cast<Model2DData*>(rData->getThis()) : 0;
}

Model2DData* Model2D::zModel() const
{
    return rData;
}