<commit_before>#include <osg/GL>
#include <osg/GLU>

#include <osg/Notify>
#include <osgUtil/Tesselator>

using namespace osg;
using namespace osgUtil;

static Tesselator* s_currentTesselator=0;

Tesselator::Tesselator()
{
    _tobj = 0;
    _errorCode = 0;
}

Tesselator::~Tesselator()
{
    if (_tobj) gluDeleteTess(_tobj);
}

void Tesselator::beginTesselation()
{
    reset();

    if (!_tobj) _tobj = gluNewTess();

    gluTessCallback(_tobj, GLU_TESS_VERTEX_DATA, (GLvoid (CALLBACK*)()) vertexCallback);
    gluTessCallback(_tobj, GLU_TESS_BEGIN_DATA,  (GLvoid (CALLBACK*)()) beginCallback);
    gluTessCallback(_tobj, GLU_TESS_END_DATA,    (GLvoid (CALLBACK*)()) endCallback);
    gluTessCallback(_tobj, GLU_TESS_COMBINE,     (GLvoid (CALLBACK*)()) combineCallback);
    gluTessCallback(_tobj, GLU_TESS_ERROR_DATA,  (GLvoid (CALLBACK*)()) errorCallback);

    gluTessBeginPolygon(_tobj,this);

    s_currentTesselator = this;

}

void Tesselator::beginContour()
{
    if (_tobj)
    {
        gluTessBeginContour(_tobj);
    }
}

void Tesselator::addVertex(osg::Vec3* vertex)
{
    if (_tobj)
    {
        Vec3d* data = new Vec3d;
        _coordData.push_back(data);
        (*data)._v[0]=(*vertex)[0];
        (*data)._v[1]=(*vertex)[1];
        (*data)._v[2]=(*vertex)[2];
        gluTessVertex(_tobj,data->_v,vertex);
    }
}

void Tesselator::endContour()
{
    if (_tobj)
    {
        gluTessEndContour(_tobj);
    }
}

void Tesselator::endTesselation()
{
    if (_tobj)
    {
        gluTessEndPolygon(_tobj);
        gluDeleteTess(_tobj);
        _tobj = 0;

        if (_errorCode!=0)
        {
           const GLubyte *estring = gluErrorString((GLenum)_errorCode);
           osg::notify(osg::WARN)<<"Tessellation Error: "<<estring<< std::endl;
        }
    }
}

void Tesselator::reset()
{
    if (_tobj)
    {
        gluDeleteTess(_tobj);
        _tobj = 0;
    }
    _primList.clear();
    _coordData.clear();
    _newVertexList.clear();
    _errorCode = 0;
}

class InsertNewVertices : public osg::ArrayVisitor
{
    public:

        float _f1,_f2,_f3,_f4;
        unsigned int _i1,_i2,_i3,_i4;

        InsertNewVertices(float f1,unsigned int i1,
                          float f2,unsigned int i2,
                          float f3,unsigned int i3,
                          float f4,unsigned int i4):
                            _f1(f1),_f2(f2),_f3(f3),_f4(f4),
                            _i1(i1),_i2(i2),_i3(i3),_i4(i4){}

        template <class ARRAY,class TYPE>
        void apply_imp(ARRAY& array,TYPE initialValue)
        {
            TYPE val = initialValue;
            if (_f1) val += static_cast<TYPE>(array[_i1] * _f1);
            if (_f2) val += static_cast<TYPE>(array[_i2] * _f2);
            if (_f3) val += static_cast<TYPE>(array[_i3] * _f3);
            if (_f4) val += static_cast<TYPE>(array[_i4] * _f4);

            array.push_back(val);
        }

        virtual void apply(osg::ByteArray& ba) { apply_imp(ba,GLbyte(0)); }
        virtual void apply(osg::ShortArray& ba) { apply_imp(ba,GLshort(0)); }
        virtual void apply(osg::IntArray& ba) { apply_imp(ba,GLint(0)); }
        virtual void apply(osg::UByteArray& ba) { apply_imp(ba,GLubyte(0)); }
        virtual void apply(osg::UShortArray& ba) { apply_imp(ba,GLushort(0)); }
        virtual void apply(osg::UIntArray& ba) { apply_imp(ba,GLuint(0)); }
        virtual void apply(osg::UByte4Array& ba) { apply_imp(ba,UByte4()); }
        virtual void apply(osg::FloatArray& ba) { apply_imp(ba,float(0)); }
        virtual void apply(osg::Vec2Array& ba) { apply_imp(ba,Vec2()); }
        virtual void apply(osg::Vec3Array& ba) { apply_imp(ba,Vec3()); }
        virtual void apply(osg::Vec4Array& ba) { apply_imp(ba,Vec4()); }

};

void Tesselator::retesselatePolygons(osg::Geometry& geom)
{
    Vec3Array* vertices = geom.getVertexArray();
    if (!vertices || vertices->empty() || geom.getPrimitiveList().empty()) return;

    int noPrimitiveAtStart = geom.getPrimitiveList().size();
    for(int primNo=0;primNo<noPrimitiveAtStart;++primNo)
    {
        osg::Primitive* primitive = geom.getPrimitiveList()[primNo].get();
        if (primitive->getMode()==osg::Primitive::POLYGON)
        {
            beginTesselation();
            beginContour();

            switch(primitive->getType())
            {
                case(osg::Primitive::DrawArraysPrimitiveType):
                {
                    osg::DrawArrays* drawArray = static_cast<osg::DrawArrays*>(primitive);
                    unsigned int first = drawArray->getFirst();
                    unsigned int last = first+drawArray->getCount();
                    for(unsigned int i=first;i<last;++i)
                    {
                        addVertex(&((*vertices)[i]));
                    }
                    break;
                }
                case(osg::Primitive::DrawElementsUBytePrimitiveType):
                {
                    osg::DrawElementsUByte* drawElements = static_cast<osg::DrawElementsUByte*>(primitive);
                    for(osg::DrawElementsUByte::iterator indexItr=drawElements->begin();
                        indexItr!=drawElements->end();
                        ++indexItr)
                    {
                        addVertex(&((*vertices)[*indexItr]));
                    }
                    break;
                }
                case(osg::Primitive::DrawElementsUShortPrimitiveType):
                {
                    osg::DrawElementsUShort* drawElements = static_cast<osg::DrawElementsUShort*>(primitive);
                    for(osg::DrawElementsUShort::iterator indexItr=drawElements->begin();
                        indexItr!=drawElements->end();
                        ++indexItr)
                    {
                        addVertex(&((*vertices)[*indexItr]));
                    }
                    break;
                }
                case(osg::Primitive::DrawElementsUIntPrimitiveType):
                {
                    osg::DrawElementsUInt* drawElements = static_cast<osg::DrawElementsUInt*>(primitive);
                    for(osg::DrawElementsUInt::iterator indexItr=drawElements->begin();
                        indexItr!=drawElements->end();
                        ++indexItr)
                    {
                        addVertex(&((*vertices)[*indexItr]));
                    }
                    break;
                }
                default:
                    break;
            }

            endContour();
            endTesselation();

            typedef std::map<osg::Vec3*,unsigned int> VertexPtrToIndexMap;
            VertexPtrToIndexMap vertexPtrToIndexMap;

            // populate the VertexPtrToIndexMap.
            for(unsigned int vi=0;vi<vertices->size();++vi)
            {
                vertexPtrToIndexMap[&((*vertices)[vi])] = vi;
            }

            if (!_newVertexList.empty())
            {

                osg::Vec3Array* normals = NULL;
                if (geom.getNormalBinding()==osg::Geometry::BIND_PER_VERTEX)
                {
                    normals = geom.getNormalArray();
                }

                typedef std::vector<osg::Array*> ArrayList;
                ArrayList arrays;

                if (geom.getColorBinding()==osg::Geometry::BIND_PER_VERTEX)
                {
                    arrays.push_back(geom.getColorArray());
                }

                osg::Geometry::TexCoordArrayList& tcal = geom.getTexCoordArrayList();
                for(osg::Geometry::TexCoordArrayList::iterator tcalItr=tcal.begin();
                    tcalItr!=tcal.end();
                    ++tcalItr)
                {
                    if (tcalItr->valid())
                    {
                        arrays.push_back(tcalItr->get());
                    }
                }

                // now add any new vertices that are required.
                for(NewVertexList::iterator itr=_newVertexList.begin();
                    itr!=_newVertexList.end();
                    ++itr)
                {
                    osg::Vec3* vertex = itr->first;
                    NewVertex& newVertex = itr->second;

                    // assign vertex.
                    vertexPtrToIndexMap[vertex]=vertices->size();
                    vertices->push_back(*vertex);

                    // assign normals
                    if (normals)
                    {
                        osg::Vec3 norm(0.0f,0.0f,0.0f);
                        if (newVertex._v1) norm += (*normals)[vertexPtrToIndexMap[newVertex._v1]] * newVertex._f1;
                        if (newVertex._v2) norm += (*normals)[vertexPtrToIndexMap[newVertex._v2]] * newVertex._f2;
                        if (newVertex._v3) norm += (*normals)[vertexPtrToIndexMap[newVertex._v3]] * newVertex._f3;
                        if (newVertex._v4) norm += (*normals)[vertexPtrToIndexMap[newVertex._v4]] * newVertex._f4;
                        norm.normalize();
                        normals->push_back(norm);
                    }

                    if (!arrays.empty())
                    {
                        InsertNewVertices inv(newVertex._f1,vertexPtrToIndexMap[newVertex._v1],
                                              newVertex._f2,vertexPtrToIndexMap[newVertex._v2],
                                              newVertex._f3,vertexPtrToIndexMap[newVertex._v3],
                                              newVertex._f4,vertexPtrToIndexMap[newVertex._v4]);

                                                  // assign the rest of the attributes.
                        for(ArrayList::iterator aItr=arrays.begin();
                            aItr!=arrays.end();
                            ++aItr)
                        {
                            (*aItr)->accept(inv);
                        }
                    }
                }

            }

            for(PrimList::iterator primItr=_primList.begin();
                primItr!=_primList.end();
                ++primItr)
            {
                Prim* prim = primItr->get();

                osg::DrawElementsUShort* elements = new osg::DrawElementsUShort(prim->_mode);
                for(Prim::VecList::iterator vitr=prim->_vertices.begin();
                    vitr!=prim->_vertices.end();
                    ++vitr)
                {
                    elements->push_back(vertexPtrToIndexMap[*vitr]);
                }

                if (primItr==_primList.begin())
                {
                    // first new primitive so overwrite the previous polygon.
                    geom.getPrimitiveList()[primNo] = elements;
                }
                else
                {
                    // subsequence primtives add to the back of the primitive list.
                    geom.addPrimitive(elements);
                }
            }

        }

    }
}

void Tesselator::begin(GLenum mode)
{
    _primList.push_back(new Prim(mode));
}

void Tesselator::vertex(osg::Vec3* vertex)
{
    if (!_primList.empty())
    {
        Prim* prim = _primList.back().get();
        prim->_vertices.push_back(vertex);

    }
}

void Tesselator::combine(osg::Vec3* vertex,void* vertex_data[4],GLfloat weight[4])
{
    _newVertexList[vertex]=NewVertex(weight[0],(Vec3*)vertex_data[0],
                                     weight[1],(Vec3*)vertex_data[1],
                                     weight[2],(Vec3*)vertex_data[2],
                                     weight[3],(Vec3*)vertex_data[3]);
}

void Tesselator::end()
{
    // no need to do anything right now...
}

void Tesselator::error(GLenum errorCode)
{
    _errorCode = errorCode;
}

void CALLBACK Tesselator::beginCallback(GLenum which, void* userData)
{
    ((Tesselator*)userData)->begin(which);
}

void CALLBACK Tesselator::endCallback(void* userData)
{
    ((Tesselator*)userData)->end();
}

void CALLBACK Tesselator::vertexCallback(GLvoid *data, void* userData)
{
    ((Tesselator*)userData)->vertex((Vec3*)data);
}

void CALLBACK Tesselator::combineCallback(GLdouble coords[3], void* vertex_data[4],
                              GLfloat weight[4], void** outData,
                              void* /*userData*/)
{
    Vec3* newData = new osg::Vec3(coords[0],coords[1],coords[2]);
    *outData = newData;
    //((Tesselator*)userData)->combine(newData);
    s_currentTesselator->combine(newData,vertex_data,weight);
}

void CALLBACK Tesselator::errorCallback(GLenum errorCode, void* userData)
{
    ((Tesselator*)userData)->error(errorCode);
}
<commit_msg>Fixed the glTessCallback type to GLU_TESS_ERROR_DATA.<commit_after>#include <osg/GL>
#include <osg/GLU>

#include <osg/Notify>
#include <osgUtil/Tesselator>

using namespace osg;
using namespace osgUtil;

Tesselator::Tesselator()
{
    _tobj = 0;
    _errorCode = 0;
}

Tesselator::~Tesselator()
{
    if (_tobj) gluDeleteTess(_tobj);
}

void Tesselator::beginTesselation()
{
    reset();

    if (!_tobj) _tobj = gluNewTess();

    gluTessCallback(_tobj, GLU_TESS_VERTEX_DATA, (GLvoid (CALLBACK*)()) vertexCallback);
    gluTessCallback(_tobj, GLU_TESS_BEGIN_DATA,  (GLvoid (CALLBACK*)()) beginCallback);
    gluTessCallback(_tobj, GLU_TESS_END_DATA,    (GLvoid (CALLBACK*)()) endCallback);
    gluTessCallback(_tobj, GLU_TESS_COMBINE_DATA,(GLvoid (CALLBACK*)()) combineCallback);
    gluTessCallback(_tobj, GLU_TESS_ERROR_DATA,  (GLvoid (CALLBACK*)()) errorCallback);

    gluTessBeginPolygon(_tobj,this);
}

void Tesselator::beginContour()
{
    if (_tobj)
    {
        gluTessBeginContour(_tobj);
    }
}

void Tesselator::addVertex(osg::Vec3* vertex)
{
    if (_tobj)
    {
        Vec3d* data = new Vec3d;
        _coordData.push_back(data);
        (*data)._v[0]=(*vertex)[0];
        (*data)._v[1]=(*vertex)[1];
        (*data)._v[2]=(*vertex)[2];
        gluTessVertex(_tobj,data->_v,vertex);
    }
}

void Tesselator::endContour()
{
    if (_tobj)
    {
        gluTessEndContour(_tobj);
    }
}

void Tesselator::endTesselation()
{
    if (_tobj)
    {
        gluTessEndPolygon(_tobj);
        gluDeleteTess(_tobj);
        _tobj = 0;

        if (_errorCode!=0)
        {
           const GLubyte *estring = gluErrorString((GLenum)_errorCode);
           osg::notify(osg::WARN)<<"Tessellation Error: "<<estring<< std::endl;
        }
    }
}

void Tesselator::reset()
{
    if (_tobj)
    {
        gluDeleteTess(_tobj);
        _tobj = 0;
    }
    _primList.clear();
    _coordData.clear();
    _newVertexList.clear();
    _errorCode = 0;
}

class InsertNewVertices : public osg::ArrayVisitor
{
    public:

        float _f1,_f2,_f3,_f4;
        unsigned int _i1,_i2,_i3,_i4;

        InsertNewVertices(float f1,unsigned int i1,
                          float f2,unsigned int i2,
                          float f3,unsigned int i3,
                          float f4,unsigned int i4):
                            _f1(f1),_f2(f2),_f3(f3),_f4(f4),
                            _i1(i1),_i2(i2),_i3(i3),_i4(i4){}

        template <class ARRAY,class TYPE>
        void apply_imp(ARRAY& array,TYPE initialValue)
        {
            TYPE val = initialValue;
            if (_f1) val += static_cast<TYPE>(array[_i1] * _f1);
            if (_f2) val += static_cast<TYPE>(array[_i2] * _f2);
            if (_f3) val += static_cast<TYPE>(array[_i3] * _f3);
            if (_f4) val += static_cast<TYPE>(array[_i4] * _f4);

            array.push_back(val);
        }

        virtual void apply(osg::ByteArray& ba) { apply_imp(ba,GLbyte(0)); }
        virtual void apply(osg::ShortArray& ba) { apply_imp(ba,GLshort(0)); }
        virtual void apply(osg::IntArray& ba) { apply_imp(ba,GLint(0)); }
        virtual void apply(osg::UByteArray& ba) { apply_imp(ba,GLubyte(0)); }
        virtual void apply(osg::UShortArray& ba) { apply_imp(ba,GLushort(0)); }
        virtual void apply(osg::UIntArray& ba) { apply_imp(ba,GLuint(0)); }
        virtual void apply(osg::UByte4Array& ba) { apply_imp(ba,UByte4()); }
        virtual void apply(osg::FloatArray& ba) { apply_imp(ba,float(0)); }
        virtual void apply(osg::Vec2Array& ba) { apply_imp(ba,Vec2()); }
        virtual void apply(osg::Vec3Array& ba) { apply_imp(ba,Vec3()); }
        virtual void apply(osg::Vec4Array& ba) { apply_imp(ba,Vec4()); }

};

void Tesselator::retesselatePolygons(osg::Geometry& geom)
{
    Vec3Array* vertices = geom.getVertexArray();
    if (!vertices || vertices->empty() || geom.getPrimitiveList().empty()) return;

    int noPrimitiveAtStart = geom.getPrimitiveList().size();
    for(int primNo=0;primNo<noPrimitiveAtStart;++primNo)
    {
        osg::Primitive* primitive = geom.getPrimitiveList()[primNo].get();
        if (primitive->getMode()==osg::Primitive::POLYGON)
        {
            beginTesselation();
            beginContour();

            switch(primitive->getType())
            {
                case(osg::Primitive::DrawArraysPrimitiveType):
                {
                    osg::DrawArrays* drawArray = static_cast<osg::DrawArrays*>(primitive);
                    unsigned int first = drawArray->getFirst();
                    unsigned int last = first+drawArray->getCount();
                    for(unsigned int i=first;i<last;++i)
                    {
                        addVertex(&((*vertices)[i]));
                    }
                    break;
                }
                case(osg::Primitive::DrawElementsUBytePrimitiveType):
                {
                    osg::DrawElementsUByte* drawElements = static_cast<osg::DrawElementsUByte*>(primitive);
                    for(osg::DrawElementsUByte::iterator indexItr=drawElements->begin();
                        indexItr!=drawElements->end();
                        ++indexItr)
                    {
                        addVertex(&((*vertices)[*indexItr]));
                    }
                    break;
                }
                case(osg::Primitive::DrawElementsUShortPrimitiveType):
                {
                    osg::DrawElementsUShort* drawElements = static_cast<osg::DrawElementsUShort*>(primitive);
                    for(osg::DrawElementsUShort::iterator indexItr=drawElements->begin();
                        indexItr!=drawElements->end();
                        ++indexItr)
                    {
                        addVertex(&((*vertices)[*indexItr]));
                    }
                    break;
                }
                case(osg::Primitive::DrawElementsUIntPrimitiveType):
                {
                    osg::DrawElementsUInt* drawElements = static_cast<osg::DrawElementsUInt*>(primitive);
                    for(osg::DrawElementsUInt::iterator indexItr=drawElements->begin();
                        indexItr!=drawElements->end();
                        ++indexItr)
                    {
                        addVertex(&((*vertices)[*indexItr]));
                    }
                    break;
                }
                default:
                    break;
            }

            endContour();
            endTesselation();

            typedef std::map<osg::Vec3*,unsigned int> VertexPtrToIndexMap;
            VertexPtrToIndexMap vertexPtrToIndexMap;

            // populate the VertexPtrToIndexMap.
            for(unsigned int vi=0;vi<vertices->size();++vi)
            {
                vertexPtrToIndexMap[&((*vertices)[vi])] = vi;
            }

            if (!_newVertexList.empty())
            {

                osg::Vec3Array* normals = NULL;
                if (geom.getNormalBinding()==osg::Geometry::BIND_PER_VERTEX)
                {
                    normals = geom.getNormalArray();
                }

                typedef std::vector<osg::Array*> ArrayList;
                ArrayList arrays;

                if (geom.getColorBinding()==osg::Geometry::BIND_PER_VERTEX)
                {
                    arrays.push_back(geom.getColorArray());
                }

                osg::Geometry::TexCoordArrayList& tcal = geom.getTexCoordArrayList();
                for(osg::Geometry::TexCoordArrayList::iterator tcalItr=tcal.begin();
                    tcalItr!=tcal.end();
                    ++tcalItr)
                {
                    if (tcalItr->valid())
                    {
                        arrays.push_back(tcalItr->get());
                    }
                }

                // now add any new vertices that are required.
                for(NewVertexList::iterator itr=_newVertexList.begin();
                    itr!=_newVertexList.end();
                    ++itr)
                {
                    osg::Vec3* vertex = itr->first;
                    NewVertex& newVertex = itr->second;

                    // assign vertex.
                    vertexPtrToIndexMap[vertex]=vertices->size();
                    vertices->push_back(*vertex);

                    // assign normals
                    if (normals)
                    {
                        osg::Vec3 norm(0.0f,0.0f,0.0f);
                        if (newVertex._v1) norm += (*normals)[vertexPtrToIndexMap[newVertex._v1]] * newVertex._f1;
                        if (newVertex._v2) norm += (*normals)[vertexPtrToIndexMap[newVertex._v2]] * newVertex._f2;
                        if (newVertex._v3) norm += (*normals)[vertexPtrToIndexMap[newVertex._v3]] * newVertex._f3;
                        if (newVertex._v4) norm += (*normals)[vertexPtrToIndexMap[newVertex._v4]] * newVertex._f4;
                        norm.normalize();
                        normals->push_back(norm);
                    }

                    if (!arrays.empty())
                    {
                        InsertNewVertices inv(newVertex._f1,vertexPtrToIndexMap[newVertex._v1],
                                              newVertex._f2,vertexPtrToIndexMap[newVertex._v2],
                                              newVertex._f3,vertexPtrToIndexMap[newVertex._v3],
                                              newVertex._f4,vertexPtrToIndexMap[newVertex._v4]);

                                                  // assign the rest of the attributes.
                        for(ArrayList::iterator aItr=arrays.begin();
                            aItr!=arrays.end();
                            ++aItr)
                        {
                            (*aItr)->accept(inv);
                        }
                    }
                }

            }

            for(PrimList::iterator primItr=_primList.begin();
                primItr!=_primList.end();
                ++primItr)
            {
                Prim* prim = primItr->get();

                osg::DrawElementsUShort* elements = new osg::DrawElementsUShort(prim->_mode);
                for(Prim::VecList::iterator vitr=prim->_vertices.begin();
                    vitr!=prim->_vertices.end();
                    ++vitr)
                {
                    elements->push_back(vertexPtrToIndexMap[*vitr]);
                }

                if (primItr==_primList.begin())
                {
                    // first new primitive so overwrite the previous polygon.
                    geom.getPrimitiveList()[primNo] = elements;
                }
                else
                {
                    // subsequence primtives add to the back of the primitive list.
                    geom.addPrimitive(elements);
                }
            }

        }

    }
}

void Tesselator::begin(GLenum mode)
{
    _primList.push_back(new Prim(mode));
}

void Tesselator::vertex(osg::Vec3* vertex)
{
    if (!_primList.empty())
    {
        Prim* prim = _primList.back().get();
        prim->_vertices.push_back(vertex);

    }
}

void Tesselator::combine(osg::Vec3* vertex,void* vertex_data[4],GLfloat weight[4])
{
    _newVertexList[vertex]=NewVertex(weight[0],(Vec3*)vertex_data[0],
                                     weight[1],(Vec3*)vertex_data[1],
                                     weight[2],(Vec3*)vertex_data[2],
                                     weight[3],(Vec3*)vertex_data[3]);
}

void Tesselator::end()
{
    // no need to do anything right now...
}

void Tesselator::error(GLenum errorCode)
{
    _errorCode = errorCode;
}

void CALLBACK Tesselator::beginCallback(GLenum which, void* userData)
{
    ((Tesselator*)userData)->begin(which);
}

void CALLBACK Tesselator::endCallback(void* userData)
{
    ((Tesselator*)userData)->end();
}

void CALLBACK Tesselator::vertexCallback(GLvoid *data, void* userData)
{
    ((Tesselator*)userData)->vertex((Vec3*)data);
}

void CALLBACK Tesselator::combineCallback(GLdouble coords[3], void* vertex_data[4],
                              GLfloat weight[4], void** outData,
                              void* userData)
{
    Vec3* newData = new osg::Vec3(coords[0],coords[1],coords[2]);
    *outData = newData;
    ((Tesselator*)userData)->combine(newData,vertex_data,weight);
}

void CALLBACK Tesselator::errorCallback(GLenum errorCode, void* userData)
{
    ((Tesselator*)userData)->error(errorCode);
}
<|endoftext|>


<commit_before>/*
 * THE UNICODE TEST SUITE FOR CINDER: https://github.com/arielm/Unicode
 * COPYRIGHT (C) 2013, ARIEL MALKA ALL RIGHTS RESERVED.
 *
 * THE FOLLOWING SOURCE-CODE IS DISTRIBUTED UNDER THE MODIFIED BSD LICENSE:
 * https://github.com/arielm/Unicode/blob/master/LICENSE.md
 */

#include "cinder/app/AppNative.h"
#include "cinder/Xml.h"
#include "cinder/Utilities.h"

#include "TextLayout.h"
#include "FontManager.h"
#include "LanguageHelper.h"
#include "Test.h"

#include <boost/algorithm/string.hpp>

using namespace std;
using namespace ci;
using namespace app;

const float FONT_SIZE = 32;
const float LINE_TOP = 66;
const float LINE_SPACING = 66;

class Application : public AppNative
{
    LanguageHelper languageHelper;
    FontManager fontManager;

    vector<TextLayout> lineLayouts;

public:
    void prepareSettings(Settings *settings);
    void setup();

    void draw();
    void drawLineLayout(TextLayout &layout, float y, float left, float right);
    void drawHLine(float y);

    TextSpan createRun(const string &text, const string &lang) const;
    string trimText(const string &text) const;

#if defined(CINDER_ANDROID)
    inline Vec2i toPixels(Vec2i s) { return s; }
    inline float toPixels(float s) { return s; }
#endif
};

void Application::prepareSettings(Settings *settings)
{
    settings->setWindowSize(1280, 736);
}

void Application::setup()
{
#if defined(CINDER_ANDROID)
    auto fileName = "SansSerif-android.xml";
#elif defined(CINDER_COCOA_TOUCH)
    auto fileName = "SansSerif-ios.xml";
#elif defined(CINDER_MAC) && 0
    auto fileName = "SansSerif-osx.xml";
#else
    auto fileName = "SansSerif.xml"; // FOR QUICK TESTS ON THE DESKTOP
#endif

    auto virtualFont = fontManager.loadVirtualFont(loadResource(fileName), FONT_SIZE);
    vector<TextSpan> runs;

    XmlTree doc(loadResource("Text.xml"));
    auto rootElement = doc.getChild("Text");

    for (auto &lineElement : rootElement.getChildren())
    {
        auto lang = lineElement->getAttributeValue<string>("lang");
        auto text = trimText(lineElement->getValue());
        runs.emplace_back(createRun(text, lang));
    }

    for (auto run : runs)
    {
        lineLayouts.emplace_back(virtualFont, run);
    }

#if 1
    Test::measureShaping(console(), virtualFont, runs);
#endif

    // ---

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

    glDisable(GL_DEPTH_TEST);
    glDepthMask(GL_FALSE);

#if defined(CINDER_COCOA_TOUCH)
    getSignalSupportedOrientations().connect([] { return InterfaceOrientation::LandscapeRight; });
#endif
}

void Application::draw()
{
    gl::clear(Color::gray(0.5f), false);

    Vec2i windowSize = toPixels(getWindowSize());
    gl::setMatricesWindow(windowSize, true);

    // ---

    float y = LINE_TOP;
    float left = 24;
    float right = windowSize.x - 24;

    for (auto layout : lineLayouts)
    {
        drawLineLayout(layout, y, left, right);
        y += LINE_SPACING;
    }
}

void Application::drawLineLayout(TextLayout &layout, float y, float left, float right)
{
    float x = (layout.direction == HB_DIRECTION_LTR) ? left : (right - layout.advance);

    glColor4f(1, 1, 1, 1);
    layout.draw(Vec2f(x, y));

    glColor4f(1, 0.75f, 0, 0.5f);
    drawHLine(y);
}

void Application::drawHLine(float y)
{
    gl::drawLine(Vec2f(-9999, y), Vec2f(+9999, y));
}

TextSpan Application::createRun(const string &text, const string &lang) const
{
    auto script = languageHelper.getScript(lang);
    auto direction = hb_script_get_horizontal_direction(script);

    return TextSpan(text, script, lang, direction);
}

string Application::trimText(const string &text) const
{
    auto rawLines = split(text, '\n');

    for (auto line : rawLines)
    {
        auto trimmed = boost::algorithm::trim_copy(line);

        if (!trimmed.empty())
        {
            return trimmed;
        }
    }

    return "";
}

CINDER_APP_NATIVE(Application, RendererGl(RendererGl::AA_NONE))
<commit_msg>LayoutCaching: WHEN CREATING TextSpan, INFERRING DIRECTION ONLY WHEN UNDEFINED<commit_after>/*
 * THE UNICODE TEST SUITE FOR CINDER: https://github.com/arielm/Unicode
 * COPYRIGHT (C) 2013, ARIEL MALKA ALL RIGHTS RESERVED.
 *
 * THE FOLLOWING SOURCE-CODE IS DISTRIBUTED UNDER THE MODIFIED BSD LICENSE:
 * https://github.com/arielm/Unicode/blob/master/LICENSE.md
 */

#include "cinder/app/AppNative.h"
#include "cinder/Xml.h"
#include "cinder/Utilities.h"

#include "TextLayout.h"
#include "FontManager.h"
#include "LanguageHelper.h"
#include "Test.h"

#include <boost/algorithm/string.hpp>

using namespace std;
using namespace ci;
using namespace app;

const float FONT_SIZE = 32;
const float LINE_TOP = 66;
const float LINE_SPACING = 66;

class Application : public AppNative
{
    LanguageHelper languageHelper;
    FontManager fontManager;

    vector<TextLayout> lineLayouts;

public:
    void prepareSettings(Settings *settings);
    void setup();

    void draw();
    void drawLineLayout(TextLayout &layout, float y, float left, float right);
    void drawHLine(float y);

    TextSpan createRun(const string &text, const string &lang, hb_direction_t direction = HB_DIRECTION_INVALID) const;
    string trimText(const string &text) const;

#if defined(CINDER_ANDROID)
    inline Vec2i toPixels(Vec2i s) { return s; }
    inline float toPixels(float s) { return s; }
#endif
};

void Application::prepareSettings(Settings *settings)
{
    settings->setWindowSize(1280, 736);
}

void Application::setup()
{
#if defined(CINDER_ANDROID)
    auto fileName = "SansSerif-android.xml";
#elif defined(CINDER_COCOA_TOUCH)
    auto fileName = "SansSerif-ios.xml";
#elif defined(CINDER_MAC) && 1
    auto fileName = "SansSerif-osx.xml";
#else
    auto fileName = "SansSerif.xml"; // FOR QUICK TESTS ON THE DESKTOP
#endif

    auto virtualFont = fontManager.loadVirtualFont(loadResource(fileName), FONT_SIZE);
    vector<TextSpan> runs;

    XmlTree doc(loadResource("Text.xml"));
    auto rootElement = doc.getChild("Text");

    for (auto &lineElement : rootElement.getChildren())
    {
        auto lang = lineElement->getAttributeValue<string>("lang");
        auto text = trimText(lineElement->getValue());
        runs.emplace_back(createRun(text, lang));
    }

    for (auto run : runs)
    {
        lineLayouts.emplace_back(virtualFont, run);
    }

#if 0
    Test::measureShaping(console(), virtualFont, runs);
#endif

    // ---

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

    glDisable(GL_DEPTH_TEST);
    glDepthMask(GL_FALSE);

#if defined(CINDER_COCOA_TOUCH)
    getSignalSupportedOrientations().connect([] { return InterfaceOrientation::LandscapeRight; });
#endif
}

void Application::draw()
{
    gl::clear(Color::gray(0.5f), false);

    Vec2i windowSize = toPixels(getWindowSize());
    gl::setMatricesWindow(windowSize, true);

    // ---

    float y = LINE_TOP;
    float left = 24;
    float right = windowSize.x - 24;

    for (auto layout : lineLayouts)
    {
        drawLineLayout(layout, y, left, right);
        y += LINE_SPACING;
    }
}

void Application::drawLineLayout(TextLayout &layout, float y, float left, float right)
{
    float x = (layout.direction == HB_DIRECTION_LTR) ? left : (right - layout.advance);

    glColor4f(1, 1, 1, 1);
    layout.draw(Vec2f(x, y));

    glColor4f(1, 0.75f, 0, 0.5f);
    drawHLine(y);
}

void Application::drawHLine(float y)
{
    gl::drawLine(Vec2f(-9999, y), Vec2f(+9999, y));
}

TextSpan Application::createRun(const string &text, const string &lang, hb_direction_t direction) const
{
    auto script = languageHelper.getScript(lang);

    if (direction == HB_DIRECTION_INVALID)
    {
        direction = hb_script_get_horizontal_direction(script);
    }

    return TextSpan(text, script, lang, direction);
}

string Application::trimText(const string &text) const
{
    auto rawLines = split(text, '\n');

    for (auto line : rawLines)
    {
        auto trimmed = boost::algorithm::trim_copy(line);

        if (!trimmed.empty())
        {
            return trimmed;
        }
    }

    return "";
}

CINDER_APP_NATIVE(Application, RendererGl(RendererGl::AA_NONE))
<|endoftext|>


<commit_before>/** Implementation of canonpy.
 */

#include <canonpy.h>

#include <Python.h>

#include <algorithm>
#include <string>
#include <vector>

#include <libcanon/perm.h>

using libcanon::Simple_perm;
using libcanon::Point;
using libcanon::Point_vec;

//
// Perm class
// ==========
//
// Internal functions
// ------------------
//
// These functions are not directly set to Python types but are used
// internally.  They are also used by other parts of this extension.
//

/** Builds a tuple object for a permutation.
 *
 * This function creates a pair, where the first field is a list of integers
 * for the preimage array, and the second field is the accompanied action,
 * which is also encoded as an integer.
 */

static PyObject* build_perm_to_tuple(const Simple_perm& perm)
{

    PyObject* pre_images = NULL;
    PyObject* res = NULL;
    PyObject* acc = NULL;
    size_t size = perm.size();

    pre_images = PyList_New(size);
    if (!pre_images)
        goto error;
    for (size_t i = 0; i < size; ++i) {
        PyObject* curr = Py_BuildValue('n', perm >> i);
        if (curr) {
            PyList_SetItem(pre_images, i, curr);
        } else {
            goto error;
        }
    }

    acc = Py_BuildValue('b', perm.acc());
    if (!acc)
        goto error;

    res = PyTuple_New(2);
    if (!res)
        goto error;

    PyTuple_SET_ITEM(res, 0, pre_images);
    PyTuple_SET_ITEM(res, 1, acc);

    return (PyObject*)res;

error:
    Py_XDECREF(pre_images);
    Py_XDECREF(res);
    Py_XDECREF(acc);
    return NULL;
}

/** Builds a permutation from its construction arguments.
 *
 * An iterable of positive integers for the pre-image array needs to be given
 * as the first argument.  The accompanied action can be optionally given as
 * another integral argument, or by the keyword ``acc``.
 *
 * If the arguments are not valid, an integer exception will be thrown and the
 * Python exception will be set.
 *
 * This function is designed to be compatible with the result from the function
 * `build_perm_to_tuple`.  However, more general input format is accepted.
 */

static Simple_perm make_perm_from_args(PyObject* args, PyObject* kwargs)
{
    PyObject* pre_images;
    char acc = 0;

    // We only need a simple code, actual error is set to the Python stack.
    constexpr int err_code = 1;

    static char* kwlist[] = { "pre_images", "acc", NULL };

    auto args_stat = PyArg_ParseTupleAndKeywords(
        args, kwargs, "O|b", kwlist, &pre_images, &acc, );

    if (!args_stat)
        throw err_code;

    Point_vec pre_images_vec{};
    std::vector<bool> image_set{};

    PyObject* pre_images_iter = PyObject_GetIter(pre_images);
    if (!pre_images_iter)
        throw error;

    // Set this to true and goto the end of the function, then the iterator can
    // be released.
    bool if_err = false;

    PyObject* pre_image_obj;
    while (pre_image_obj = PyIter_Next(pre_images_iter)) {

        if (!PyLong_Check(pre_image_obj)) {
            if_err = true;
            goto exit;
        }
        Point pre_image = PyLong_AsUnsignedLong(pre_image_obj);

        // Release reference right here since its content is already extracted.
        // In this way, the error handling does not need to worry about it any
        // more.
        Py_DECREF(pre_image_obj);

        if (PyErr_Occurred()) {
            if_err = true;
            goto exit;
        }

        pre_images_vec.push_back(pre_image);
        size_t req_size = pre_image + 1;
        if (image_set.size() < req_size)
            image_set.resize(req_size, false);
        if (image_set[pre_image]) {
            std::string err_msg("The image of ");
            err_msg.append(std::to_string(pre_image));
            err_msg.append(" has already been set.");
            PyErr_SetString(PyExc_ValueError, err_msg.c_str());
            if_err = true;
            goto exist;
        } else {
            image_set[pre_image] = true;
        }
    }

    // Non StopIteration error.
    if (PyErr_Occurred()) {
        if_err = true;
        goto exit;
    }

    auto first_not_set = std::find(image_set.begin(), image_set.end(), false);
    if (first_not_set != image_set.end()) {
        std::string err_msg("The image of ");
        err_msg.append(std::to_string(first_not_set - images_set.begin()));
        err_msg(" is not set.");
        if_err = true;
    }

exit:
    Py_DECREF(pre_images_iter);
    if (if_err) {
        throw err_code;
    } else {
        return Simple_perm(std::move(pre_images_vec), acc);
    }
}

//
// Interface functions
// -------------------
//

const static char* perm_getnewargs_doc
    = "Get the arguments for new to construct the Perm.";

static PyObject* perm_getnewargs(Perm_object* self)
{
    // Here we directly use the tuple format of a perm.

    return build_perm_to_tuple(self->perm);
}

/** Gets the size of the permutation domain of a Perm.
 */

static Py_ssize_t perm_length(Perm_object* self)
{
    // The type should be the same, size_t and Py_ssize_t.

    return self->perm.size();
}

/** Gets the pre-image of a point.
 *
 * A new integer object will be built by this function.
 */

static PyObject* perm_item(Perm_object* self, Py_ssize_t i)
{
    if (i < 0 || i >= self->perm.size()) {
        PyErr_SetString(PyExc_IndexError, "Point outside permutation domain");
        return NULL;
    }

    return Py_BuildValue("n", self->perm >> i);
}

/** Gets the accompanied action of a Perm.
 */

static PyObject* perm_get_acc(Perm_object* self, void* closure)
{
    // Note that the accompanied action is a byte in simple perm.

    return Py_BuildValue("b", self->perm.acc());
}

/** Deallocates a perm instance.
 */

static void perm_dealloc(Perm_object* self)
{
    perm.perm.~Simple_perm();

    // For subclassing.
    Py_TYPE(self)->tp_free((PyObject*)self);
}

/** Forms the string representation of a Perm object.
 */

static PyObject* perm_repr(Perm_object* self)
{
    const Simple_perm& perm = self->perm;

    std::wstring repr("Perm(");

    size_t size = perm.size();

    if (size > 0) {
        for (size_t i = 0; i < size; ++i) {
            if (i == 0) {
                repr.append('[');
            } else {
                repr.append(", ");
            }
            repr.append(std::to_wstring(perm >> i));
        }
        repr.append(']');

        // Add the accompanied action only when we need.
        char acc = perm.acc();
        if (acc != 0) {
            repr.append(", ");
            repr.append(std::to_wstring(acc));
        }
    }

    // This is used for empty or non-empty permutation.
    repr.append(')');

    return PyUnicode_FromUnicode(repr.data(), repr.size());
}

/** Gets the hash of a perm.
 */

static Py_hash_t perm_hash(Perm_object* self)
{
    // Py_hash_t should be compatible with size_t.

    return self->perm.hash();
}

/** Creates a new Perm object.
 *
 * The actual work is delegated to the Python/Perm interface function.
 */

static PyObject* perm_new(PyTypeObject* type, PyObject* args, PyObject* kwargs)
{
    Perm_object* self;

    // Pay attention to subclassing.
    self = (Perm_object*)type->tp_alloc(type, 0);

    if (!self)
        return NULL;

    Simple_perm perm = get_perm_from_args(args, kwargs);

    if (perm.size() > 0) {
        new (&self->perm) Simple_perm(std::move(perm));
        return (PyObject*)self;
    } else {
        return NULL;
    }
}

//
// Class definition
// ----------------
//

/** Methods for Perm objects.
 */

static PyMethodDef perm_methods[] = {
    { "__getnewargs__", (PyCFunction)perm_getnewargs, METH_NOARGS,
        perm_getnewargs_doc },
    { NULL, NULL } /* sentinel */
};

/** Sequence operations for Perm objects.
 *
 * Here we only support size and pre-image query.
 */

// clang-format off
static PySequenceMethods perm_as_sequence = {
    (lenfunc)perm_length,                       /* sq_length */
    0,                                          /* sq_concat */
    0,                                          /* sq_repeat */
    (ssizeargfunc)perm_item,                    /* sq_item */
    0,                                          /* sq_slice */
    0,                                          /* sq_ass_item */
    0,                                          /* sq_ass_slice */
    0                                           /* sq_contains */
};
// clang-format on

/** Accessors for Perms.
 *
 * The accompanied action query is made here.
 */

// clang-format off
static PyGetSetDef perm_getsets[] = {
    { "acc", (getter)perm_get_acc, NULL, "The accompanied action.", NULL },
    { NULL }
};
// clang-format on

/** Perm type doc string.
  */

static char* perm_doc =
    R"__doc__(Permutation of points with accompanied action.

Permutations can be constructed from an iterable giving the pre-image of the
points and an optional integral value for the accompanied action.  The
accompanied action can be given positionally or by the keyword ``acc``, and it
will be manipulated according to the convention in libcanon.

Querying the length of a Perm object gives the size of the permutation domain,
while indexing it gives the pre-image of the given integral point.  The
accompanied action can be obtained by getting the attribute ``acc``.
Otherwise, this data type is mostly opaque.

)__doc__";

/** Type definition for Perm class.
 */

// clang-format off
static PyTypeObject perm_type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "drudge.canonpy.Perm",
    sizeof(Perm_object),
    0,
    (destructor) perm_dealloc,                  /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    (reprfunc) perm_repr,                       /* tp_repr */
    0,                                          /* tp_as_number */
    &perm_as_sequence,                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    (hashfunc) perm_hash,                       /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    0, /* In main. */                           /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                         /* tp_flags */
    perm_doc,                                   /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    perm_methods,                               /* tp_methods */
    0,                                          /* tp_members */
    perm_getsets,                               /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    0,                                          /* tp_init */
    0,                                          /* tp_alloc */
    perm_new,                                   /* tp_new */
    0,                                          /* tp_free */
};
// clang-format on

//
// Python module definition
// ========================
//

/** Docstring for the canonpy module.
 */

static const char* canonpy_docstring
    = R"__doc__(Canonpy, simple wrapper over libcanon for Python.

This wrapper of libcanon is directly targeted towards usage using drudge.
Here, we have a class `Perm`, which wraps over the `Simple_perm` class in
libcanon, another class `SimsTransv`, which wraps over the `Sims_trasv` class
for `Simple_perm`.  And we also have the function `canon_eldag` to canonicalize
an Eldag.

)__doc__";

/** Methods in the canonpy module.
 */

static PyMethodDef canonpy_methods[] = { { NULL, NULL, 0, NULL } };

/** Executes the initialization of the canonpy module.
 */

static int canopy_exec(PyObject* m)
{
    //
    // Add the class for Perm.
    //

    perm_type.tp_getattro = PyObject_GenericGetAttr;
    if (PyType_Ready(&perm_type) < 0)
        return NULL;
    Py_INCREF(&perm_type);
    PyModule_AddObject(m, "Perm", (PyObject*)&perm_type);

    return 0;
}

/** Slots for for canonpy module definition.
 */

static struct PyModuleDef_Slot canonpy_slots[] = {
    { Py_mod_exec, canonpy_exec }, { 0, NULL },
};

/** Canonpy module definition.
 */

// clang-format off

static struct PyModuleDef canonpy_module = {
    PyModuleDef_HEAD_INIT,
    "drudge.canonpy",
    canonpy_docstring,
    0, // m-size
    canonpy_methods,
    canonpy_slots,
    NULL, // Transverse
    NULL, // Clear
    NULL  // Free
};

// clang-format on

/** The published canonpy function.
 */

PyMODINIT_FUNC PyInit_canonpy(void)
{
    return PyModuleDef_Init(&canonpy_module);
}
<commit_msg>Update Perm new for new Perm/Python interface<commit_after>/** Implementation of canonpy.
 */

#include <canonpy.h>

#include <Python.h>

#include <algorithm>
#include <string>
#include <vector>

#include <libcanon/perm.h>

using libcanon::Simple_perm;
using libcanon::Point;
using libcanon::Point_vec;

//
// Perm class
// ==========
//
// Internal functions
// ------------------
//
// These functions are not directly set to Python types but are used
// internally.  They are also used by other parts of this extension.
//

/** Builds a tuple object for a permutation.
 *
 * This function creates a pair, where the first field is a list of integers
 * for the preimage array, and the second field is the accompanied action,
 * which is also encoded as an integer.
 */

static PyObject* build_perm_to_tuple(const Simple_perm& perm)
{

    PyObject* pre_images = NULL;
    PyObject* res = NULL;
    PyObject* acc = NULL;
    size_t size = perm.size();

    pre_images = PyList_New(size);
    if (!pre_images)
        goto error;
    for (size_t i = 0; i < size; ++i) {
        PyObject* curr = Py_BuildValue('n', perm >> i);
        if (curr) {
            PyList_SetItem(pre_images, i, curr);
        } else {
            goto error;
        }
    }

    acc = Py_BuildValue('b', perm.acc());
    if (!acc)
        goto error;

    res = PyTuple_New(2);
    if (!res)
        goto error;

    PyTuple_SET_ITEM(res, 0, pre_images);
    PyTuple_SET_ITEM(res, 1, acc);

    return (PyObject*)res;

error:
    Py_XDECREF(pre_images);
    Py_XDECREF(res);
    Py_XDECREF(acc);
    return NULL;
}

/** Builds a permutation from its construction arguments.
 *
 * An iterable of positive integers for the pre-image array needs to be given
 * as the first argument.  The accompanied action can be optionally given as
 * another integral argument, or by the keyword ``acc``.
 *
 * If the arguments are not valid, an integer exception will be thrown and the
 * Python exception will be set.
 *
 * This function is designed to be compatible with the result from the function
 * `build_perm_to_tuple`.  However, more general input format is accepted.
 */

static Simple_perm make_perm_from_args(PyObject* args, PyObject* kwargs)
{
    PyObject* pre_images;
    char acc = 0;

    // We only need a simple code, actual error is set to the Python stack.
    constexpr int err_code = 1;

    static char* kwlist[] = { "pre_images", "acc", NULL };

    auto args_stat = PyArg_ParseTupleAndKeywords(
        args, kwargs, "O|b", kwlist, &pre_images, &acc, );

    if (!args_stat)
        throw err_code;

    Point_vec pre_images_vec{};
    std::vector<bool> image_set{};

    PyObject* pre_images_iter = PyObject_GetIter(pre_images);
    if (!pre_images_iter)
        throw error;

    // Set this to true and goto the end of the function, then the iterator can
    // be released.
    bool if_err = false;

    PyObject* pre_image_obj;
    while (pre_image_obj = PyIter_Next(pre_images_iter)) {

        if (!PyLong_Check(pre_image_obj)) {
            if_err = true;
            goto exit;
        }
        Point pre_image = PyLong_AsUnsignedLong(pre_image_obj);

        // Release reference right here since its content is already extracted.
        // In this way, the error handling does not need to worry about it any
        // more.
        Py_DECREF(pre_image_obj);

        if (PyErr_Occurred()) {
            if_err = true;
            goto exit;
        }

        pre_images_vec.push_back(pre_image);
        size_t req_size = pre_image + 1;
        if (image_set.size() < req_size)
            image_set.resize(req_size, false);
        if (image_set[pre_image]) {
            std::string err_msg("The image of ");
            err_msg.append(std::to_string(pre_image));
            err_msg.append(" has already been set.");
            PyErr_SetString(PyExc_ValueError, err_msg.c_str());
            if_err = true;
            goto exist;
        } else {
            image_set[pre_image] = true;
        }
    }

    // Non StopIteration error.
    if (PyErr_Occurred()) {
        if_err = true;
        goto exit;
    }

    auto first_not_set = std::find(image_set.begin(), image_set.end(), false);
    if (first_not_set != image_set.end()) {
        std::string err_msg("The image of ");
        err_msg.append(std::to_string(first_not_set - images_set.begin()));
        err_msg(" is not set.");
        if_err = true;
    }

exit:
    Py_DECREF(pre_images_iter);
    if (if_err) {
        throw err_code;
    } else {
        return Simple_perm(std::move(pre_images_vec), acc);
    }
}

//
// Interface functions
// -------------------
//

const static char* perm_getnewargs_doc
    = "Get the arguments for new to construct the Perm.";

static PyObject* perm_getnewargs(Perm_object* self)
{
    // Here we directly use the tuple format of a perm.

    return build_perm_to_tuple(self->perm);
}

/** Gets the size of the permutation domain of a Perm.
 */

static Py_ssize_t perm_length(Perm_object* self)
{
    // The type should be the same, size_t and Py_ssize_t.

    return self->perm.size();
}

/** Gets the pre-image of a point.
 *
 * A new integer object will be built by this function.
 */

static PyObject* perm_item(Perm_object* self, Py_ssize_t i)
{
    if (i < 0 || i >= self->perm.size()) {
        PyErr_SetString(PyExc_IndexError, "Point outside permutation domain");
        return NULL;
    }

    return Py_BuildValue("n", self->perm >> i);
}

/** Gets the accompanied action of a Perm.
 */

static PyObject* perm_get_acc(Perm_object* self, void* closure)
{
    // Note that the accompanied action is a byte in simple perm.

    return Py_BuildValue("b", self->perm.acc());
}

/** Deallocates a perm instance.
 */

static void perm_dealloc(Perm_object* self)
{
    perm.perm.~Simple_perm();

    // For subclassing.
    Py_TYPE(self)->tp_free((PyObject*)self);
}

/** Forms the string representation of a Perm object.
 */

static PyObject* perm_repr(Perm_object* self)
{
    const Simple_perm& perm = self->perm;

    std::wstring repr("Perm(");

    size_t size = perm.size();

    if (size > 0) {
        for (size_t i = 0; i < size; ++i) {
            if (i == 0) {
                repr.append('[');
            } else {
                repr.append(", ");
            }
            repr.append(std::to_wstring(perm >> i));
        }
        repr.append(']');

        // Add the accompanied action only when we need.
        char acc = perm.acc();
        if (acc != 0) {
            repr.append(", ");
            repr.append(std::to_wstring(acc));
        }
    }

    // This is used for empty or non-empty permutation.
    repr.append(')');

    return PyUnicode_FromUnicode(repr.data(), repr.size());
}

/** Gets the hash of a perm.
 */

static Py_hash_t perm_hash(Perm_object* self)
{
    // Py_hash_t should be compatible with size_t.

    return self->perm.hash();
}

/** Creates a new Perm object.
 *
 * The actual work is delegated to the Python/Perm interface function.
 */

static PyObject* perm_new(PyTypeObject* type, PyObject* args, PyObject* kwargs)
{
    Perm_object* self;

    // Pay attention to subclassing.
    self = (Perm_object*)type->tp_alloc(type, 0);

    if (!self)
        return NULL;

    try {
        Simple_perm perm = make_perm_from_args(args, kwargs);
    } catch (int) {
        Py_DECREF(self);
        return NULL;
    }

    new (&self->perm) Simple_perm(std::move(perm));
    return (PyObject*)self;
}

//
// Class definition
// ----------------
//

/** Methods for Perm objects.
 */

static PyMethodDef perm_methods[] = {
    { "__getnewargs__", (PyCFunction)perm_getnewargs, METH_NOARGS,
        perm_getnewargs_doc },
    { NULL, NULL } /* sentinel */
};

/** Sequence operations for Perm objects.
 *
 * Here we only support size and pre-image query.
 */

// clang-format off
static PySequenceMethods perm_as_sequence = {
    (lenfunc)perm_length,                       /* sq_length */
    0,                                          /* sq_concat */
    0,                                          /* sq_repeat */
    (ssizeargfunc)perm_item,                    /* sq_item */
    0,                                          /* sq_slice */
    0,                                          /* sq_ass_item */
    0,                                          /* sq_ass_slice */
    0                                           /* sq_contains */
};
// clang-format on

/** Accessors for Perms.
 *
 * The accompanied action query is made here.
 */

// clang-format off
static PyGetSetDef perm_getsets[] = {
    { "acc", (getter)perm_get_acc, NULL, "The accompanied action.", NULL },
    { NULL }
};
// clang-format on

/** Perm type doc string.
  */

static char* perm_doc =
    R"__doc__(Permutation of points with accompanied action.

Permutations can be constructed from an iterable giving the pre-image of the
points and an optional integral value for the accompanied action.  The
accompanied action can be given positionally or by the keyword ``acc``, and it
will be manipulated according to the convention in libcanon.

Querying the length of a Perm object gives the size of the permutation domain,
while indexing it gives the pre-image of the given integral point.  The
accompanied action can be obtained by getting the attribute ``acc``.
Otherwise, this data type is mostly opaque.

)__doc__";

/** Type definition for Perm class.
 */

// clang-format off
static PyTypeObject perm_type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "drudge.canonpy.Perm",
    sizeof(Perm_object),
    0,
    (destructor) perm_dealloc,                  /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    (reprfunc) perm_repr,                       /* tp_repr */
    0,                                          /* tp_as_number */
    &perm_as_sequence,                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    (hashfunc) perm_hash,                       /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    0, /* In main. */                           /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                         /* tp_flags */
    perm_doc,                                   /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    perm_methods,                               /* tp_methods */
    0,                                          /* tp_members */
    perm_getsets,                               /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    0,                                          /* tp_init */
    0,                                          /* tp_alloc */
    perm_new,                                   /* tp_new */
    0,                                          /* tp_free */
};
// clang-format on

//
// Python module definition
// ========================
//

/** Docstring for the canonpy module.
 */

static const char* canonpy_docstring
    = R"__doc__(Canonpy, simple wrapper over libcanon for Python.

This wrapper of libcanon is directly targeted towards usage using drudge.
Here, we have a class `Perm`, which wraps over the `Simple_perm` class in
libcanon, another class `SimsTransv`, which wraps over the `Sims_trasv` class
for `Simple_perm`.  And we also have the function `canon_eldag` to canonicalize
an Eldag.

)__doc__";

/** Methods in the canonpy module.
 */

static PyMethodDef canonpy_methods[] = { { NULL, NULL, 0, NULL } };

/** Executes the initialization of the canonpy module.
 */

static int canopy_exec(PyObject* m)
{
    //
    // Add the class for Perm.
    //

    perm_type.tp_getattro = PyObject_GenericGetAttr;
    if (PyType_Ready(&perm_type) < 0)
        return NULL;
    Py_INCREF(&perm_type);
    PyModule_AddObject(m, "Perm", (PyObject*)&perm_type);

    return 0;
}

/** Slots for for canonpy module definition.
 */

static struct PyModuleDef_Slot canonpy_slots[] = {
    { Py_mod_exec, canonpy_exec }, { 0, NULL },
};

/** Canonpy module definition.
 */

// clang-format off

static struct PyModuleDef canonpy_module = {
    PyModuleDef_HEAD_INIT,
    "drudge.canonpy",
    canonpy_docstring,
    0, // m-size
    canonpy_methods,
    canonpy_slots,
    NULL, // Transverse
    NULL, // Clear
    NULL  // Free
};

// clang-format on

/** The published canonpy function.
 */

PyMODINIT_FUNC PyInit_canonpy(void)
{
    return PyModuleDef_Init(&canonpy_module);
}
<|endoftext|>


<commit_before>#include "stdafx.h"
#include "pathfinding_system.h"

#include "entity_system/world.h"
#include "entity_system/entity.h"

#include "render_system.h"
#include "physics_system.h"

#include "../resources/render_info.h"
#include "../game/body_helper.h"

pathfinding_system::pathfinding_system() : draw_memorised_walls(false), draw_undiscovered(false),
	epsilon_distance_the_same_vertex(3.f) {}

void pathfinding_system::process_entities(world& owner) {
	/* prepare epsilons to be used later, just to make the notation more clear */
	const float epsilon_distance_visible_point_sq = epsilon_distance_visible_point * epsilon_distance_visible_point;
	const float epsilon_distance_the_same_vertex_sq = epsilon_distance_the_same_vertex * epsilon_distance_the_same_vertex;

	/* we'll need a reference to physics system for raycasting */
	physics_system& physics = owner.get_system<physics_system>();
	/* we'll need a reference to render system for debug drawing */
	render_system& render = owner.get_system<render_system>();

	for (auto it : targets) {
		/* get necessary components */
		auto& visibility = it->get<components::visibility>();
		auto& pathfinding = it->get<components::pathfinding>();
		auto& transform = it->get<components::transform>().current;
		auto body = it->get<components::physics>().body;

		/* check if we request pathfinding at the moment */
		if (!pathfinding.session_stack.empty()) {
			/* get visibility information */
			auto& vision = visibility.get_layer(components::visibility::DYNAMIC_PATHFINDING);
			//vision.ignore_discontinuities_shorter_than = pathfinding.session().temporary_ignore_discontinuities_shorter_than;

			/* save all fully visible vertices as discovered */
			for (auto& visible_vertex : vision.vertex_hits) {
				bool this_visible_vertex_is_already_memorised = false;

				for (auto& memorised_discovered : pathfinding.session().discovered_vertices) {
					/* if a similiar discovered vertex exists */
					if ((memorised_discovered.location - visible_vertex).length_sq() < epsilon_distance_the_same_vertex_sq) {
						this_visible_vertex_is_already_memorised = true;
						/* overwrite the location just in case */
						memorised_discovered.location = visible_vertex;
						break;
					}
				}

				/* save if unique */
				if (!this_visible_vertex_is_already_memorised) {
					components::pathfinding::pathfinding_session::navigation_vertex vert;
					vert.location = visible_vertex;
					pathfinding.session().discovered_vertices.push_back(vert);
				}
			}

			/* save all new discontinuities from visibility */
			for (auto& disc : vision.discontinuities) {
				bool this_discontinuity_is_already_memorised = false;

				for (auto& memorised_undiscovered : pathfinding.session().undiscovered_vertices) {
					/* if a discontinuity with the same closer vertex already exists */
					if ((memorised_undiscovered.location - disc.points.first).length_sq() < epsilon_distance_the_same_vertex_sq) {
						this_discontinuity_is_already_memorised = true;
						memorised_undiscovered.location = disc.points.first;
						break;
					}
				}

				for (auto& memorised_discovered : pathfinding.session().discovered_vertices) {
					/* if a discontinuity with the same closer vertex already exists */
					if ((memorised_discovered.location - disc.points.first).length_sq() < epsilon_distance_the_same_vertex_sq) {
						this_discontinuity_is_already_memorised = true;
						memorised_discovered.location = disc.points.first;
						break;
					}
				}

				/* if it is unique, push it */
				if (!this_discontinuity_is_already_memorised) {
					components::pathfinding::pathfinding_session::navigation_vertex vert;
					vert.location = disc.points.first;

					/* get the associated edge to prepare a relevant sensor */
					auto associated_edge = vision.edges[disc.edge_index];

					/* get the direction the sensor will be going to */
					vec2<> sensor_direction;

					/* if the first vertex of the edge matches the location */
					if (associated_edge.first.compare(vert.location))
						sensor_direction = associated_edge.first - associated_edge.second;
					/* if it is the second one */
					else if (associated_edge.second.compare(vert.location))
						sensor_direction = associated_edge.second - associated_edge.first;
					/* should never happen */
					else assert(0);

					sensor_direction.normalize();

					/* rotate a bit to avoid non-reachable sensors */
					float rotation = pathfinding.rotate_navpoints;
					if (disc.winding == disc.LEFT) rotation = -rotation;
					sensor_direction.rotate(rotation, vec2<>(0, 0));

					vert.sensor = vert.location + sensor_direction * pathfinding.target_offset;
					pathfinding.session().undiscovered_vertices.push_back(vert);
				}
			}

			/* mark vertices whose sensors distance from the the body is less than distance_navpoint_hit as visited */

			/* prepare body polygon to test for overlaps */
			b2PolygonShape body_poly;
			auto verts = topdown::get_transformed_shape_verts(*reinterpret_cast<entity*>(body->GetUserData()));
			body_poly.Set(verts.data(), verts.size());

			/* for every undiscovered navigation point */
			auto& undiscs = pathfinding.session().undiscovered_vertices;
			undiscs.erase(std::remove_if(undiscs.begin(), undiscs.end(), [&body, &pathfinding, &body_poly, epsilon_distance_the_same_vertex_sq](const components::pathfinding::pathfinding_session::navigation_vertex& nav){
				/* check again for duplicates, shouldn't happen very often */
				for (auto& memorised_discovered : pathfinding.session().discovered_vertices)
					/* if a similiar discovered vertex exists */
					if ((memorised_discovered.location - nav.location).length_sq() < epsilon_distance_the_same_vertex_sq)
						return true;
				return false;
			}), undiscs.end());

			/* now for the actual pathfinding routine */

			/* helpful lambda */
			auto& is_point_visible = [&physics, epsilon_distance_visible_point_sq](vec2<> from, vec2<> point, b2Filter& filter){
				auto line_of_sight = physics.ray_cast_px(from, point, &filter);
				return (!line_of_sight.hit || (line_of_sight.intersection - point).length_sq() < epsilon_distance_visible_point_sq);
			};

			/* we are sure here that session stack has at least 1 session
				we drop secondary sessions whose targets are visible
			*/
			if (pathfinding.session_stack.size() >= 2) {
				for (auto old_session = pathfinding.session_stack.begin(); old_session != pathfinding.session_stack.end(); ++old_session) {
					/*  check if there's a line of sight to any of the old targets
					if there's a line of sight to "navigate_to" it will be visible as target to the newer session
					and we either way also handle the current session's target so nothing is missing here
					*/

					/* if we're exploring, we have no target in the first session */
					if (pathfinding.is_exploring && old_session == pathfinding.session_stack.begin())
						continue;

					if (body->TestPoint((*old_session).target * PIXELS_TO_METERSf) ||
						is_point_visible(transform.pos, (*old_session).target, vision.filter)) {
							/* if there is, roll back to this session */
							pathfinding.session() = (*old_session);

							/* if it is the first session, we don't want to erase it since we still need to reach the target */
							if (old_session == pathfinding.session_stack.begin())
								++old_session;

							/* drop unnecessary sessions */
							pathfinding.session_stack.erase(old_session, pathfinding.session_stack.end());
							break;
					}
				}
			}

			/* if we're exploring, we have no target in the first session */
			if (!pathfinding.is_exploring && pathfinding.session_stack.size() == 1) {
				/* if the target is inside body, it's already found */
				if (body->TestPoint(pathfinding.session().target * PIXELS_TO_METERSf)) {
					/* done, target found */
					pathfinding.clear_pathfinding_info();
					continue;
				}

				/* check if there's a line of sight */
				if (is_point_visible(transform.pos, pathfinding.session().target, vision.filter)) {
					/* if there is, navigate directly to target */
					pathfinding.session().navigate_to = pathfinding.session().target;
					continue;
				}
			}

			/* if it is the last session but there's no line of sight,
				or it is not the last session but it was not dropped from the loop which means there's no line of sight to target,
				pick the best navigation candidate
			*/

			auto& vertices = pathfinding.session().undiscovered_vertices;

			if (draw_undiscovered) {
				for (auto& disc : vertices)
					render.lines.push_back(render_system::debug_line(disc.location, disc.sensor, graphics::pixel_32(0, 127, 255, 255)));

				for (auto& disc : pathfinding.session().discovered_vertices)
					//if(disc.sensor.non_zero())
					render.lines.push_back(render_system::debug_line(disc.location, disc.location + vec2<>(0, pathfinding.target_offset), graphics::pixel_32(0, 255, 0, 255)));
			}

			if (!vertices.empty()) {
				/* find discontinuity that is closest to the target */
				auto& local_minimum_discontinuity = (*std::min_element(vertices.begin(), vertices.end(),
					[&pathfinding, &transform](const components::pathfinding::pathfinding_session::navigation_vertex& a,
					const components::pathfinding::pathfinding_session::navigation_vertex& b) {

						/* if we're exploring, we have no target in the first session */
						if (pathfinding.is_exploring && pathfinding.session_stack.size() == 1)
							return (a.location - transform.pos).length_sq() < (b.location - transform.pos).length_sq();

						auto dist_a = (a.location - pathfinding.session().target).length_sq() + (a.location - transform.pos).length_sq();
						auto dist_b = (b.location - pathfinding.session().target).length_sq() + (b.location - transform.pos).length_sq();
						return dist_a < dist_b;
				}));

				/* extract the closer vertex, condition to faciliate debug */
				if (local_minimum_discontinuity.sensor != pathfinding.session().navigate_to)
					pathfinding.session().navigate_to = local_minimum_discontinuity.sensor;

				bool rays_hit = false;
				/* extract all transformed vertices of the subject's original model, false means we want pixels */
				auto& subject_verts = topdown::get_transformed_shape_verts(*it, false);
				subject_verts.push_back(transform.pos);

				for (auto& subject_vert : subject_verts) {
					if (
						is_point_visible(subject_vert, local_minimum_discontinuity.location, vision.filter) ||
						is_point_visible(subject_vert, local_minimum_discontinuity.sensor, vision.filter)
						) {
						rays_hit = true;
					}
				}

				/* if we can see it, navigate there */
				if (body->TestPoint(local_minimum_discontinuity.location * PIXELS_TO_METERSf) ||
					body->TestPoint(local_minimum_discontinuity.sensor * PIXELS_TO_METERSf) ||
					rays_hit
					) {
				}
				/* else start new navigation session */
				else {
					if (pathfinding.enable_backtracking) {
						vec2<> new_target = pathfinding.session().navigate_to;
						pathfinding.session_stack.push_back(components::pathfinding::pathfinding_session());
						pathfinding.session().target = new_target;
						pathfinding.session().temporary_ignore_discontinuities_shorter_than = pathfinding.starting_ignore_discontinuities_shorter_than;
					}
				}

			}
			else {
				/* something went wrong, let's begin again */
				//if (pathfinding.session_stack.size() == 1) {
					pathfinding.session().discovered_vertices.clear();
					pathfinding.session().undiscovered_vertices.clear();
					//pathfinding.session().temporary_ignore_discontinuities_shorter_than /= 1.5f;
				//}
				//pathfinding.session_stack.resize(1);
			}
		}
	}
}<commit_msg>clearing pathfinding info on line of sight with target warning: may cause some pathology, see issue #107<commit_after>#include "stdafx.h"
#include "pathfinding_system.h"

#include "entity_system/world.h"
#include "entity_system/entity.h"

#include "render_system.h"
#include "physics_system.h"

#include "../resources/render_info.h"
#include "../game/body_helper.h"

pathfinding_system::pathfinding_system() : draw_memorised_walls(false), draw_undiscovered(false),
	epsilon_distance_the_same_vertex(3.f) {}

void pathfinding_system::process_entities(world& owner) {
	/* prepare epsilons to be used later, just to make the notation more clear */
	const float epsilon_distance_visible_point_sq = epsilon_distance_visible_point * epsilon_distance_visible_point;
	const float epsilon_distance_the_same_vertex_sq = epsilon_distance_the_same_vertex * epsilon_distance_the_same_vertex;

	/* we'll need a reference to physics system for raycasting */
	physics_system& physics = owner.get_system<physics_system>();
	/* we'll need a reference to render system for debug drawing */
	render_system& render = owner.get_system<render_system>();

	for (auto it : targets) {
		/* get necessary components */
		auto& visibility = it->get<components::visibility>();
		auto& pathfinding = it->get<components::pathfinding>();
		auto& transform = it->get<components::transform>().current;
		auto body = it->get<components::physics>().body;

		/* check if we request pathfinding at the moment */
		if (!pathfinding.session_stack.empty()) {
			/* get visibility information */
			auto& vision = visibility.get_layer(components::visibility::DYNAMIC_PATHFINDING);
			//vision.ignore_discontinuities_shorter_than = pathfinding.session().temporary_ignore_discontinuities_shorter_than;

			/* save all fully visible vertices as discovered */
			for (auto& visible_vertex : vision.vertex_hits) {
				bool this_visible_vertex_is_already_memorised = false;

				for (auto& memorised_discovered : pathfinding.session().discovered_vertices) {
					/* if a similiar discovered vertex exists */
					if ((memorised_discovered.location - visible_vertex).length_sq() < epsilon_distance_the_same_vertex_sq) {
						this_visible_vertex_is_already_memorised = true;
						/* overwrite the location just in case */
						memorised_discovered.location = visible_vertex;
						break;
					}
				}

				/* save if unique */
				if (!this_visible_vertex_is_already_memorised) {
					components::pathfinding::pathfinding_session::navigation_vertex vert;
					vert.location = visible_vertex;
					pathfinding.session().discovered_vertices.push_back(vert);
				}
			}

			/* save all new discontinuities from visibility */
			for (auto& disc : vision.discontinuities) {
				bool this_discontinuity_is_already_memorised = false;

				for (auto& memorised_undiscovered : pathfinding.session().undiscovered_vertices) {
					/* if a discontinuity with the same closer vertex already exists */
					if ((memorised_undiscovered.location - disc.points.first).length_sq() < epsilon_distance_the_same_vertex_sq) {
						this_discontinuity_is_already_memorised = true;
						memorised_undiscovered.location = disc.points.first;
						break;
					}
				}

				for (auto& memorised_discovered : pathfinding.session().discovered_vertices) {
					/* if a discontinuity with the same closer vertex already exists */
					if ((memorised_discovered.location - disc.points.first).length_sq() < epsilon_distance_the_same_vertex_sq) {
						this_discontinuity_is_already_memorised = true;
						memorised_discovered.location = disc.points.first;
						break;
					}
				}

				/* if it is unique, push it */
				if (!this_discontinuity_is_already_memorised) {
					components::pathfinding::pathfinding_session::navigation_vertex vert;
					vert.location = disc.points.first;

					/* get the associated edge to prepare a relevant sensor */
					auto associated_edge = vision.edges[disc.edge_index];

					/* get the direction the sensor will be going to */
					vec2<> sensor_direction;

					/* if the first vertex of the edge matches the location */
					if (associated_edge.first.compare(vert.location))
						sensor_direction = associated_edge.first - associated_edge.second;
					/* if it is the second one */
					else if (associated_edge.second.compare(vert.location))
						sensor_direction = associated_edge.second - associated_edge.first;
					/* should never happen */
					else assert(0);

					sensor_direction.normalize();

					/* rotate a bit to avoid non-reachable sensors */
					float rotation = pathfinding.rotate_navpoints;
					if (disc.winding == disc.LEFT) rotation = -rotation;
					sensor_direction.rotate(rotation, vec2<>(0, 0));

					vert.sensor = vert.location + sensor_direction * pathfinding.target_offset;
					pathfinding.session().undiscovered_vertices.push_back(vert);
				}
			}

			/* mark vertices whose sensors distance from the the body is less than distance_navpoint_hit as visited */

			/* prepare body polygon to test for overlaps */
			b2PolygonShape body_poly;
			auto verts = topdown::get_transformed_shape_verts(*reinterpret_cast<entity*>(body->GetUserData()));
			body_poly.Set(verts.data(), verts.size());

			/* for every undiscovered navigation point */
			auto& undiscs = pathfinding.session().undiscovered_vertices;
			undiscs.erase(std::remove_if(undiscs.begin(), undiscs.end(), [&body, &pathfinding, &body_poly, epsilon_distance_the_same_vertex_sq](const components::pathfinding::pathfinding_session::navigation_vertex& nav){
				/* check again for duplicates, shouldn't happen very often */
				for (auto& memorised_discovered : pathfinding.session().discovered_vertices)
					/* if a similiar discovered vertex exists */
					if ((memorised_discovered.location - nav.location).length_sq() < epsilon_distance_the_same_vertex_sq)
						return true;
				return false;
			}), undiscs.end());

			/* now for the actual pathfinding routine */

			/* helpful lambda */
			auto& is_point_visible = [&physics, epsilon_distance_visible_point_sq](vec2<> from, vec2<> point, b2Filter& filter){
				auto line_of_sight = physics.ray_cast_px(from, point, &filter);
				return (!line_of_sight.hit || (line_of_sight.intersection - point).length_sq() < epsilon_distance_visible_point_sq);
			};

			/* we are sure here that session stack has at least 1 session
				we drop secondary sessions whose targets are visible
			*/
			if (pathfinding.session_stack.size() >= 2) {
				for (auto old_session = pathfinding.session_stack.begin(); old_session != pathfinding.session_stack.end(); ++old_session) {
					/*  check if there's a line of sight to any of the old targets
					if there's a line of sight to "navigate_to" it will be visible as target to the newer session
					and we either way also handle the current session's target so nothing is missing here
					*/

					/* if we're exploring, we have no target in the first session */
					if (pathfinding.is_exploring && old_session == pathfinding.session_stack.begin())
						continue;

					if (body->TestPoint((*old_session).target * PIXELS_TO_METERSf) ||
						is_point_visible(transform.pos, (*old_session).target, vision.filter)) {
							/* if there is, roll back to this session */
							pathfinding.session() = (*old_session);

							/* if it is the first session, we don't want to erase it since we still need to reach the target */
							if (old_session == pathfinding.session_stack.begin())
								++old_session;

							/* drop unnecessary sessions */
							pathfinding.session_stack.erase(old_session, pathfinding.session_stack.end());
							break;
					}
				}
			}

			/* if we're exploring, we have no target in the first session */
			if (!pathfinding.is_exploring && pathfinding.session_stack.size() == 1) {
				/* if the target is inside body, it's already found */
				if (body->TestPoint(pathfinding.session().target * PIXELS_TO_METERSf)) {
					/* done, target found */
					pathfinding.clear_pathfinding_info();
					continue;
				}

				/* check if there's a line of sight */
				if (is_point_visible(transform.pos, pathfinding.session().target, vision.filter)) {
					/* if there is, navigate directly to target */

					pathfinding.session().discovered_vertices.clear();
					pathfinding.session().undiscovered_vertices.clear();

					pathfinding.session().navigate_to = pathfinding.session().target;
					continue;
				}
			}

			/* if it is the last session but there's no line of sight,
				or it is not the last session but it was not dropped from the loop which means there's no line of sight to target,
				pick the best navigation candidate
			*/

			auto& vertices = pathfinding.session().undiscovered_vertices;

			if (draw_undiscovered) {
				for (auto& disc : vertices)
					render.lines.push_back(render_system::debug_line(disc.location, disc.sensor, graphics::pixel_32(0, 127, 255, 255)));

				for (auto& disc : pathfinding.session().discovered_vertices)
					//if(disc.sensor.non_zero())
					render.lines.push_back(render_system::debug_line(disc.location, disc.location + vec2<>(0, pathfinding.target_offset), graphics::pixel_32(0, 255, 0, 255)));
			}

			if (!vertices.empty()) {
				/* find discontinuity that is closest to the target */
				auto& local_minimum_discontinuity = (*std::min_element(vertices.begin(), vertices.end(),
					[&pathfinding, &transform](const components::pathfinding::pathfinding_session::navigation_vertex& a,
					const components::pathfinding::pathfinding_session::navigation_vertex& b) {

						/* if we're exploring, we have no target in the first session */
						if (pathfinding.is_exploring && pathfinding.session_stack.size() == 1)
							return (a.location - transform.pos).length_sq() < (b.location - transform.pos).length_sq();

						auto dist_a = (a.location - pathfinding.session().target).length_sq() + (a.location - transform.pos).length_sq();
						auto dist_b = (b.location - pathfinding.session().target).length_sq() + (b.location - transform.pos).length_sq();
						return dist_a < dist_b;
				}));

				/* extract the closer vertex, condition to faciliate debug */
				if (local_minimum_discontinuity.sensor != pathfinding.session().navigate_to)
					pathfinding.session().navigate_to = local_minimum_discontinuity.sensor;

				bool rays_hit = false;
				/* extract all transformed vertices of the subject's original model, false means we want pixels */
				auto& subject_verts = topdown::get_transformed_shape_verts(*it, false);
				subject_verts.push_back(transform.pos);

				for (auto& subject_vert : subject_verts) {
					if (
						is_point_visible(subject_vert, local_minimum_discontinuity.location, vision.filter) ||
						is_point_visible(subject_vert, local_minimum_discontinuity.sensor, vision.filter)
						) {
						rays_hit = true;
					}
				}

				/* if we can see it, navigate there */
				if (body->TestPoint(local_minimum_discontinuity.location * PIXELS_TO_METERSf) ||
					body->TestPoint(local_minimum_discontinuity.sensor * PIXELS_TO_METERSf) ||
					rays_hit
					) {
				}
				/* else start new navigation session */
				else {
					if (pathfinding.enable_backtracking) {
						vec2<> new_target = pathfinding.session().navigate_to;
						pathfinding.session_stack.push_back(components::pathfinding::pathfinding_session());
						pathfinding.session().target = new_target;
						pathfinding.session().temporary_ignore_discontinuities_shorter_than = pathfinding.starting_ignore_discontinuities_shorter_than;
					}
				}

			}
			else {
				/* something went wrong, let's begin again */
				//if (pathfinding.session_stack.size() == 1) {
					pathfinding.session().discovered_vertices.clear();
					pathfinding.session().undiscovered_vertices.clear();
					//pathfinding.session().temporary_ignore_discontinuities_shorter_than /= 1.5f;
				//}
				//pathfinding.session_stack.resize(1);
			}
		}
	}
}<|endoftext|>