#include "GL1SceneRenderer.h"
#include <cnoid/SceneDrawables>
#include <cnoid/SceneCameras>
#include <cnoid/SceneLights>
#include <cnoid/SceneEffects>
#include <cnoid/EigenUtil>
#include <cnoid/NullOut>
#include <cnoid/Format>
#include <bitset>
#include <unordered_map>
#include <iostream>
#include <cnoid/gl.h>
#include "GLImageScaler.h"
#include "gettext.h"

using namespace std;
using namespace cnoid;

namespace {

const bool USE_RIGHT_BOTTOM_PIXEL_FOR_PICKING = true;
const bool SHOW_IMAGE_FOR_PICKING = false;

const float MinLineWidthForPicking = 5.0f;

int glVersion = 0;

bool isGLVersionAtLeast(int major, int minor) {
    return glVersion >= major * 10000 + minor;
}

typedef vector<Affine3, Eigen::aligned_allocator<Affine3>> Affine3Array;

struct SgObjectPtrHash {
    std::hash<SgObject*> hash;
    std::size_t operator()(const SgObjectPtr& p) const {
        return hash(p.get());
    }
};

typedef std::unordered_map<SgObjectPtr, ReferencedPtr, SgObjectPtrHash> ResourceMap;


struct TransparentShapeInfo
{
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
    SgShape* shape;
    unsigned int pickId;
    Affine3 V; // view matrix
};
typedef std::shared_ptr<TransparentShapeInfo> TransparentShapeInfoPtr;


/*
  A set of variables associated with a scene node
*/
class VertexResource : public Referenced
{
public:
    GLuint bufferNames[5];
    GLuint numVertices;
    GLuint normalVisualizationSize;
    ScopedConnection updateConnection;

    VertexResource() {
        for(int i=0; i < 5; ++i){
            bufferNames[i] = GL_INVALID_VALUE;
        }
    }
    ~VertexResource() {
        for(int i=0; i < 5; ++i){
            if(bufferNames[i] != GL_INVALID_VALUE){
                glDeleteBuffers(1, &bufferNames[i]);
            }
        }
    }
    GLuint& vertexBufferName() { return bufferNames[0]; }
    GLuint& normalBufferName() { return bufferNames[1]; }
    GLuint& colorBufferName() { return bufferNames[2]; }
    GLuint& texCoordBufferName() { return bufferNames[3]; }
    GLuint& normalVisualizationVertexBufferName(){ return bufferNames[4]; }
};
typedef ref_ptr<VertexResource> VertexResourcePtr;


class TextureResource : public Referenced
{
public:
    bool isBound;
    bool isImageUpdateNeeded;
    GLuint textureName;
    int width;
    int height;
    int numComponents;
    ScopedConnection updateConnection;
        
    TextureResource(){
        isBound = false;
        isImageUpdateNeeded = true;
        width = 0;
        height = 0;
        numComponents = 0;
    }
    bool isSameSizeAs(const Image& image){
        return (width == image.width() && height == image.height() && numComponents == image.numComponents());
    }
            
    ~TextureResource(){
        if(isBound){
            glDeleteTextures(1, &textureName);
        }
    }
};
typedef ref_ptr<TextureResource> TextureResourcePtr;

}

namespace cnoid {

class GL1SceneRenderer::Impl
{
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
        
    GL1SceneRenderer* self;

    SceneRenderer::NodeFunctionSet renderingFunctions;

    Affine3Array Vstack; // stack of the model/view matrices

    typedef vector<Vector4f, Eigen::aligned_allocator<Vector4f>> ColorArray;
        
    struct TemporaryBuf {
        EIGEN_MAKE_ALIGNED_OPERATOR_NEW
        SgVertexArray vertices;
        SgIndexArray triangles;
        SgNormalArray normals;
        ColorArray colors;
        SgTexCoordArray texCoords;
        vector<char> scaledImageBuf;
        bool used;
        TemporaryBuf() : used(false) { }
    };
    std::unique_ptr<TemporaryBuf> tmpbuf;

    Affine3 lastViewMatrix;
    Matrix4 lastProjectionMatrix;
    Affine3 currentModelTransform;

    bool doUnusedResourceCheck;
    bool isCheckingUnusedResources;
    bool hasValidNextResourceMap;
    bool isResourceClearRequested;
    ResourceMap resourceMaps[2];
    ResourceMap* currentResourceMap;
    ResourceMap* nextResourceMap;
    vector<SgObject*> updatedSceneObjects;

    int currentResourceMapIndex;

    int numSystemLights;
    int prevNumLights;
    LightingMode lightingMode;
    int polygonDisplayElements;
    bool defaultLighting;
    bool isHeadLightLightingFromBackEnabled;

    bool isCurrentFogUpdated;
    SgFogPtr prevFog;
    ScopedConnection currentFogConnection;

    GLint maxLights;
    bool defaultSmoothShading;
    bool isTextureEnabled;
    SgMaterialPtr defaultMaterial;
    GLfloat defaultPointSize;
    GLfloat defaultLineWidth;
    GLuint defaultTextureName;
        
    vector<TransparentShapeInfoPtr> transparentShapeInfos;

    // OpenGL states
    enum StateFlag {
        CURRENT_COLOR,
        COLOR_MATERIAL,
        DIFFUSE_COLOR,
        AMBIENT_COLOR,
        EMISSION_COLOR,
        SPECULAR_COLOR,
        SHININESS,
        CULL_FACE,
        CCW,
        LIGHTING,
        LIGHT_MODEL_TWO_SIDE,
        BLEND,
        DEPTH_MASK,
        POINT_SIZE,
        LINE_WIDTH,
        NUM_STATE_FLAGS
    };

    bitset<NUM_STATE_FLAGS> stateFlag;
        
    Vector3f currentColor;
    Vector4f diffuseColor;
    Vector4f ambientColor;
    Vector4f emissionColor;
    Vector4f specularColor;
    float shininess;
    float lastAlpha;
    bool isColorMaterialEnabled;
    bool isCullFaceEnabled;
    bool isCCW;
    bool isLightingEnabled;
    bool isLightModelTwoSide;
    bool isBlendEnabled;
    bool isDepthMaskEnabled;
    float pointSize;
    float lineWidth;

    int backFaceCullingMode;

    bool doNormalVisualization;
    double normalLength;

    bool isCompiling;
    bool isRenderingPickingImage;
    bool isRenderingOutline;

    GLdouble pickX;
    GLdouble pickY;
    SgNodePath currentNodePath;
    vector<shared_ptr<SgNodePath>> pickingNodePathList;
    SgNodePath pickedNodePath;
    Vector3 pickedPoint;

    ostream* os_;
    ostream& os() { return *os_; }

    string glVersionString;
    string glVendorString;
    string glRendererString;

    void renderChildNodes(SgGroup* group){
        for(SgGroup::const_iterator p = group->begin(); p != group->end(); ++p){
            renderingFunctions.dispatch(*p);
        }
    }
    
    Vector4f createColorWithAlpha(const Vector3f& c3){
        Vector4f c4;
        c4.head<3>() = c3;
        c4[3] = lastAlpha;
        return c4;
    }

    Impl(GL1SceneRenderer* self);
    ~Impl();
    void initialize();
    bool initializeGL(GLADloadfunc getProcAddress);
    void beginRendering();
    void beginActualRendering(SgCamera* camera);
    void renderCamera(SgCamera* camera, const Affine3& cameraPosition);
    void renderLights(const Affine3& cameraPosition);
    void renderLight(const SgLight* light, GLint id, const Affine3& T);
    void renderFog();
    void onCurrentFogNodeUdpated();
    void endRendering();
    void doRender();
    bool doPick(int x, int y);
    void setPickColor(unsigned int id);
    void pushPickNode(SgNode* node);
    unsigned int pushPickEndNode(SgNode* node, bool doSetColor);
    void popPickNode();

    void renderGroup(SgGroup* group);
    void renderSwitchableGroup(SgSwitchableGroup* group);
    void renderTransform(SgTransform* transform);
    void renderShape(SgShape* shape);
    void renderUnpickableGroup(SgUnpickableGroup* group);
    void renderPickableInvisibleGroup(SgPickableInvisibleGroup* group);
    void renderMaterial(const SgMaterial* material);
    bool renderTexture(SgTexture* texture, bool withMaterial);
    void renderTransparentShapes();
    void putMeshData(SgMesh* mesh);
    VertexResource* findOrCreateVertexResource(SgObject* object, bool& out_found);
    void renderMesh(SgMesh* mesh, bool hasTexture);
    void setupMeshResource(SgMesh* mesh, VertexResource* resource, bool hasTexture);
    void renderNormalVisualizationLines(SgMesh* mesh, VertexResource* resource);
    void renderPointSet(SgPointSet* pointSet);
    void setupPointSetResource(SgPointSet* lineSet, VertexResource* resource);
    void renderLineSet(SgLineSet* lineSet);
    void setupLineSetResource(SgLineSet* lineSet, VertexResource* resource);
    void renderPlot(
        SgPlot* plot, GLenum primitiveMode, function<void(VertexResource*)> setupVertexResource);
    void renderPolygonDrawStyle(SgPolygonDrawStyle* style);
    void renderViewportOverlay(SgViewportOverlay* overlay);
    void renderOutlineGroup(SgOutline* outline);
    void clearGLState();
    void setColor(const Vector3f& color);
    void enableColorMaterial(bool on);
    void setDiffuseColor(const Vector4f& color);
    void setAmbientColor(const Vector4f& color);
    void setEmissionColor(const Vector4f& color);
    void setSpecularColor(const Vector4f& color);
    void setShininess(float shininess);
    void enableCullFace(bool on);
    void setFrontCCW(bool on);
    void enableLighting(bool on);
    void setLightModelTwoSide(bool on);
    void enableBlend(bool on);
    void enableDepthMask(bool on);
    void setPointSize(float size);
    void setLineWidth(float width);
    void getCurrentCameraTransform(Affine3& T);
};

}


GL1SceneRenderer::GL1SceneRenderer(SgGroup* sceneRoot)
    : GLSceneRenderer(sceneRoot)
{
    impl = new Impl(this);
    impl->initialize();
}


GL1SceneRenderer::Impl::Impl(GL1SceneRenderer* self)
    : self(self)
{

}


void GL1SceneRenderer::Impl::initialize()
{
    Vstack.reserve(16);
    
    tmpbuf.reset(new TemporaryBuf);

    doUnusedResourceCheck = true;
    currentResourceMapIndex = 0;
    hasValidNextResourceMap = false;
    isResourceClearRequested = false;
    currentResourceMap = &resourceMaps[0];
    nextResourceMap = &resourceMaps[1];

    lastViewMatrix.setIdentity();
    lastProjectionMatrix.setIdentity();

    numSystemLights = 3;
    maxLights = numSystemLights;
    prevNumLights = 0;
    isHeadLightLightingFromBackEnabled = true;

    prevFog = nullptr;

    lightingMode = NormalLighting;
    defaultLighting = true;
    defaultSmoothShading = true;
    defaultMaterial = new SgMaterial;
    isTextureEnabled = true;
    defaultPointSize = 1.0f;
    defaultLineWidth = 1.0f;

    backFaceCullingMode = ENABLE_BACK_FACE_CULLING;
    
    doNormalVisualization = false;
    normalLength = 0.0;

    isCompiling = false;
    isRenderingPickingImage = false;
    isRenderingOutline = false;
    pickedPoint.setZero();

    clearGLState();

    os_ = &nullout();

    renderingFunctions.setFunction<SgGroup>(
        [&](SgGroup* node){ renderGroup(node); });
    renderingFunctions.setFunction<SgTransform>(
        [&](SgTransform* node){ renderTransform(node); });
    renderingFunctions.setFunction<SgSwitchableGroup>(
        [&](SgSwitchableGroup* node){ renderSwitchableGroup(node); });
    renderingFunctions.setFunction<SgUnpickableGroup>(
        [&](SgUnpickableGroup* node){ renderUnpickableGroup(node); });
    renderingFunctions.setFunction<SgPickableInvisibleGroup>(
        [&](SgPickableInvisibleGroup* node){ renderPickableInvisibleGroup(node); });
    renderingFunctions.setFunction<SgShape>(
        [&](SgShape* node){ renderShape(node); });
    renderingFunctions.setFunction<SgPointSet>(
        [&](SgPointSet* node){ renderPointSet(node); });
    renderingFunctions.setFunction<SgLineSet>(
        [&](SgLineSet* node){ renderLineSet(node); });
    renderingFunctions.setFunction<SgPolygonDrawStyle>(
        [&](SgPolygonDrawStyle* style){ renderPolygonDrawStyle(style); });
    renderingFunctions.setFunction<SgViewportOverlay>(
        [&](SgViewportOverlay* node){ renderViewportOverlay(node); });
    renderingFunctions.setFunction<SgOutline>(
        [&](SgOutline* node){ renderOutlineGroup(node); });

    self->applyExtensions();
    renderingFunctions.updateDispatchTable();
}


GL1SceneRenderer::~GL1SceneRenderer()
{
    delete impl;
}


GL1SceneRenderer::Impl::~Impl()
{

}


SceneRenderer::NodeFunctionSet* GL1SceneRenderer::renderingFunctions()
{
    return &impl->renderingFunctions;
}


void GL1SceneRenderer::addNodeDecoration(SgNode* targetNode, NodeDecorationFunction func, int id)
{

}


void GL1SceneRenderer::clearNodeDecorations(int id)
{

}


void GL1SceneRenderer::setOutputStream(std::ostream& os)
{
    GLSceneRenderer::setOutputStream(os);
    impl->os_ = &os;
}


bool GL1SceneRenderer::initializeGL(GLADloadfunc getProcAddress)
{
    return impl->initializeGL(getProcAddress);
}


bool GL1SceneRenderer::Impl::initializeGL(GLADloadfunc getProcAddress)
{
    if(glVersion == 0){
        glVersion = cnoidLoadGL(getProcAddress);
        if(glVersion == 0){
            return false;
        }
    }
    
    glVersionString = (const char*)glGetString(GL_VERSION);
    glVendorString = (const char*)glGetString(GL_VENDOR);
    glRendererString = (const char*)glGetString(GL_RENDERER);
    os() << formatR(_("OpenGL {0} is available for the \"{1}\" view.\n"),
                    glVersionString, self->name());
    os() << formatR(_("Driver profile: {0} {1}."), glVendorString, glRendererString) << endl;
    
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_NORMALIZE);
    setFrontCCW(true);

    glGetIntegerv(GL_MAX_LIGHTS, &maxLights);

    GLfloat defaultAmbient[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, defaultAmbient);
    
    glDisable(GL_FOG);
    isCurrentFogUpdated = false;

    glGenTextures(1, &defaultTextureName);

    return true;
}


void GL1SceneRenderer::setViewport(int x, int y, int width, int height)
{
    glViewport(x, y, width, height);
    GLSceneRenderer::updateViewportInformation(x, y, width, height);
}


void GL1SceneRenderer::updateViewportInformation()
{
    int viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);
    GLSceneRenderer::updateViewportInformation(viewport[0], viewport[1], viewport[2], viewport[3]);
}


void GL1SceneRenderer::flushGL()
{
    glFlush();
}


const std::string& GL1SceneRenderer::glVersionString() const
{
    return impl->glVersionString;
}


const std::string& GL1SceneRenderer::glVendorString() const
{
    return impl->glVendorString;
}


const std::string& GL1SceneRenderer::glRendererString() const
{
    return impl->glRendererString;
}


void GL1SceneRenderer::requestToClearResources()
{
    impl->isResourceClearRequested = true;
}


void GL1SceneRenderer::Impl::beginRendering()
{
    isCheckingUnusedResources = isRenderingPickingImage ? false : doUnusedResourceCheck;

    if(isResourceClearRequested){
        resourceMaps[0].clear();
        resourceMaps[1].clear();
        updatedSceneObjects.clear();
        hasValidNextResourceMap = false;
        isCheckingUnusedResources = false;
        isResourceClearRequested = false;
    }
    if(hasValidNextResourceMap){
        currentResourceMapIndex = 1 - currentResourceMapIndex;
        currentResourceMap = &resourceMaps[currentResourceMapIndex];

        for(auto& object : updatedSceneObjects){
            currentResourceMap->erase(object);
        }
        updatedSceneObjects.clear();

        nextResourceMap = &resourceMaps[1 - currentResourceMapIndex];
        hasValidNextResourceMap = false;
    }

    if(isRenderingPickingImage){
        currentNodePath.clear();
        pickingNodePathList.clear();
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    } else {
        const Vector3f& c = self->backgroundColor();
        glClearColor(c[0], c[1], c[2], 1.0f);
    }
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    self->extractPreprocessedNodes();

    SgCamera* camera = self->currentCamera();
    if(camera){
        beginActualRendering(camera);
    }
}


void GL1SceneRenderer::Impl::beginActualRendering(SgCamera* camera)
{
    const Affine3& cameraPosition = self->currentCameraPosition();
    
    renderCamera(camera, cameraPosition);

    if(isRenderingPickingImage || !defaultLighting){
        enableLighting(false);
    } else {
        if(defaultSmoothShading){
            glShadeModel(GL_SMOOTH);
        } else {
            glShadeModel(GL_FLAT);
        }
        renderLights(cameraPosition);
        renderFog();
    }

    polygonDisplayElements = SgPolygonDrawStyle::Face;
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    
    clearGLState();
    
    setColor(self->defaultColor());
    setPointSize(defaultPointSize);
    //glEnable(GL_POINT_SMOOTH);
    setLineWidth(defaultLineWidth);
    //glEnable(GL_LINE_SMOOTH);
    
    transparentShapeInfos.clear();
}


void GL1SceneRenderer::Impl::renderCamera(SgCamera* camera, const Affine3& cameraPosition)
{
    // set projection
    if(SgPerspectiveCamera* pers = dynamic_cast<SgPerspectiveCamera*>(camera)){
        double aspectRatio = self->aspectRatio();
        self->getPerspectiveProjectionMatrix(
            self->getEffectiveFovy(pers), aspectRatio, pers->nearClipDistance(), pers->farClipDistance(),
            lastProjectionMatrix);
        
    } else if(SgOrthographicCamera* ortho = dynamic_cast<SgOrthographicCamera*>(camera)){
        GLfloat left, right, bottom, top;
        self->getViewVolume(ortho, left, right, bottom, top);
        self->getOrthographicProjectionMatrix(
            left, right, bottom, top, ortho->nearClipDistance(), ortho->farClipDistance(),
            lastProjectionMatrix);
        
    } else {
        self->getPerspectiveProjectionMatrix(
            radian(40.0), self->aspectRatio(), 0.01, 1.0e4,
            lastProjectionMatrix);
    }

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMultMatrixd(lastProjectionMatrix.data());

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    Vstack.clear();
    Vstack.push_back(cameraPosition.inverse(Eigen::Isometry));
    const Affine3& V = Vstack.back();
    lastViewMatrix = V;
    glLoadMatrixd(V.data());
}


void GL1SceneRenderer::Impl::renderLights(const Affine3& cameraPosition)
{
    enableLighting(true);

    SgLight* headLight = self->headLight();
    if(!headLight->on()){
        glDisable(GL_LIGHT0);
        glDisable(GL_LIGHT1);
    } else {
        renderLight(headLight, GL_LIGHT0, cameraPosition);
        bool isBackLightActive = false;
        if(isHeadLightLightingFromBackEnabled){
            if(SgDirectionalLight* directionalHeadLight = dynamic_cast<SgDirectionalLight*>(headLight)){
                SgDirectionalLight lightFromBack(*directionalHeadLight);
                lightFromBack.setDirection(-directionalHeadLight->direction());
                renderLight(&lightFromBack, GL_LIGHT1, cameraPosition);
                isBackLightActive = true;
            }
        }
        if(!isBackLightActive){
            glDisable(GL_LIGHT1);
        }
    }
    SgLight* worldLight = self->worldLight();
    if(!worldLight->on()){
        glDisable(GL_LIGHT2);
    } else {
        renderLight(worldLight, GL_LIGHT2, self->worldLightTransform()->T());
    }
    
    const int numAdditionalLights =
        std::min(self->numAdditionalLights(), (int)(maxLights - numSystemLights));

    for(int i=0; i < numAdditionalLights; ++i){
        SgLight* light;
        Isometry3 T;
        self->getLightInfo(i, light, T);
        const GLint id = GL_LIGHT0 + numSystemLights + i;
        renderLight(light, id, T);
    }

    for(int i = numAdditionalLights; i < prevNumLights; ++i){
        const GLint lightID = GL_LIGHT0 + numSystemLights + i;
        glDisable(lightID);
    }

    prevNumLights = numAdditionalLights;
}


void GL1SceneRenderer::Impl::renderLight(const SgLight* light, GLint id, const Affine3& T)
{
    bool isValid = false;

    if(light->on()){
        
        if(const SgDirectionalLight* dirLight = dynamic_cast<const SgDirectionalLight*>(light)){

            isValid = true;
            
            Vector4f pos;
            pos << (T.linear() * -dirLight->direction()).cast<float>(), 0.0f;
            glLightfv(id, GL_POSITION, pos.data());
            
            /*
              glLightf(id, GL_CONSTANT_ATTENUATION, 0.0f);
              glLightf(id, GL_LINEAR_ATTENUATION, 0.0f);
              glLightf(id, GL_QUADRATIC_ATTENUATION, 0.0f);
            */
            
        } else if(const SgPointLight* pointLight = dynamic_cast<const SgPointLight*>(light)){
            
            isValid = true;
            
            Vector4f pos;
            pos << T.translation().cast<float>(), 1.0f;
            glLightfv(id, GL_POSITION, pos.data());
            
            glLightf(id, GL_CONSTANT_ATTENUATION, pointLight->constantAttenuation());
            glLightf(id, GL_LINEAR_ATTENUATION, pointLight->linearAttenuation());
            glLightf(id, GL_QUADRATIC_ATTENUATION, pointLight->quadraticAttenuation());
            
            if(const SgSpotLight* spotLight = dynamic_cast<const SgSpotLight*>(pointLight)){
                Vector3f direction = (T.linear() * spotLight->direction()).cast<GLfloat>();
                glLightfv(id, GL_SPOT_DIRECTION, direction.data());
                glLightf(id, GL_SPOT_CUTOFF, degree(spotLight->cutOffAngle()));
                float r = spotLight->cutOffAngle() - spotLight->beamWidth();
                r = std::max(0.0f, std::min((float)PI, r));
                r = (r * 128.0) / PI;
                glLightf(id, GL_SPOT_EXPONENT, r);
                
            } else {
                glLightf(id, GL_SPOT_CUTOFF, 180.0f);
            }
        }
    }
        
    if(!isValid){
        glDisable(id);
        
    } else {
        Vector4f diffuse;
        diffuse << (light->intensity() * light->color()), 1.0f;
        glLightfv(id, GL_DIFFUSE, diffuse.data());
        glLightfv(id, GL_SPECULAR, diffuse.data());
        
        Vector4f ambient;
        ambient << (light->ambientIntensity() * light->color()), 1.0f;
        glLightfv(id, GL_AMBIENT, ambient.data());
        
        glEnable(id);
    }
}


void GL1SceneRenderer::Impl::renderFog()
{
    SgFog* fog = nullptr;
    if(self->isFogEnabled()){
        int n = self->numFogs();
        if(n > 0){
            fog = self->fog(n - 1); // use the last fog
        }
    }
    if(fog != prevFog){
        isCurrentFogUpdated = true;
        if(!fog){
            currentFogConnection.disconnect();
        } else {
            currentFogConnection.reset(
                fog->sigUpdated().connect(
                    std::bind(&GL1SceneRenderer::Impl::onCurrentFogNodeUdpated, this)));
        }
    }

    if(isCurrentFogUpdated){
        if(!fog){
            glDisable(GL_FOG);
        } else {
            glEnable(GL_FOG);
            GLfloat color[4];
            const Vector3f& c = fog->color();
            color[0] = c[0];
            color[1] = c[1];
            color[2] = c[2];
            color[3] = 1.0f;
            glFogfv(GL_FOG_COLOR, color);
            glFogi(GL_FOG_MODE, GL_LINEAR);
            glFogi(GL_FOG_HINT, GL_FASTEST);
            glFogf(GL_FOG_START, 0.0f);
            glFogf(GL_FOG_END, fog->visibilityRange());
        }
    }
    isCurrentFogUpdated = false;
    prevFog = fog;
}


void GL1SceneRenderer::Impl::onCurrentFogNodeUdpated()
{
    isCurrentFogUpdated = true;
}


void GL1SceneRenderer::Impl::endRendering()
{
    if(isCheckingUnusedResources){
        currentResourceMap->clear();
        hasValidNextResourceMap = true;
    }

    if(tmpbuf->used){
        tmpbuf.reset(new TemporaryBuf);
    }
}


void GL1SceneRenderer::doRender()
{
    impl->doRender();
}


void GL1SceneRenderer::Impl::doRender()
{
    if(self->applyNewExtensions()){
        renderingFunctions.updateDispatchTable();
    }
    
    beginRendering();

    renderChildNodes(self->sceneRoot());

    if(!transparentShapeInfos.empty()){
        renderTransparentShapes();
    }

    endRendering();
}


bool GL1SceneRenderer::doPick(int x, int y)
{
    return impl->doPick(x, y);
}


/*
  http://stackoverflow.com/questions/4040616/opengl-gl-select-or-manual-collision-detection
  http://content.gpwiki.org/index.php/OpenGL_Selection_Using_Unique_Color_IDs
  http://www.opengl-tutorial.org/miscellaneous/clicking-on-objects/picking-with-an-opengl-hack/
  http://www.codeproject.com/Articles/35139/Interactive-Techniques-in-Three-dimensional-Scenes#_OpenGL_Picking_by
  http://en.wikibooks.org/wiki/OpenGL_Programming/Object_selection
*/
bool GL1SceneRenderer::Impl::doPick(int x, int y)
{
    glPushAttrib(GL_ENABLE_BIT);

    glDisable(GL_BLEND);
    glDisable(GL_MULTISAMPLE);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_DITHER);
    glDisable(GL_FOG);

    GLSceneRenderer::Viewport vp;
    int px, py;

    if(USE_RIGHT_BOTTOM_PIXEL_FOR_PICKING){
        vp = self->viewport();
        glViewport(vp.w - x - 1, -y, vp.w, vp.h);
        px = vp.w - 1;
        py = 0;
    } else {
        px = x;
        py = y;
    }
        
    if(!SHOW_IMAGE_FOR_PICKING){
        glScissor(px, py, 1, 1);
        glEnable(GL_SCISSOR_TEST);
    }
    
    isRenderingPickingImage = true;
    doRender();
    isRenderingPickingImage = false;

    glPopAttrib();

    GLfloat color[4];
    glReadPixels(px, py, 1, 1, GL_RGBA, GL_FLOAT, color);
    if(SHOW_IMAGE_FOR_PICKING){
        color[2] = 0.0f;
    }
    unsigned int id = (color[0] * 255) + ((int)(color[1] * 255) << 8) + ((int)(color[2] * 255) << 16) - 1;

    pickedNodePath.clear();

    if(0 < id && id < pickingNodePathList.size()){
        GLfloat depth;
        glReadPixels(px, py, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
        Vector3 projected;
        if(self->unproject(x, y, depth, pickedPoint)){
            pickedNodePath = *pickingNodePathList[id];
        }
    }

    if(USE_RIGHT_BOTTOM_PIXEL_FOR_PICKING){
        glViewport(vp.x, vp.y, vp.w, vp.h);
    }

    return !pickedNodePath.empty();
}


const SgNodePath& GL1SceneRenderer::pickedNodePath() const
{
    return impl->pickedNodePath;
}


const Vector3& GL1SceneRenderer::pickedPoint() const
{
    return impl->pickedPoint;
}



inline void GL1SceneRenderer::Impl::setPickColor(unsigned int id)
{
    float r = (id & 0xff) / 255.0;
    float g = ((id >> 8) & 0xff) / 255.0;
    float b = ((id >> 16) & 0xff) / 255.0;
    if(SHOW_IMAGE_FOR_PICKING){
        b = 1.0f;
    }
    glColor4f(r, g, b, 1.0f);
    currentColor << r, g, b;
}
        

void GL1SceneRenderer::Impl::pushPickNode(SgNode* node)
{
    if(isRenderingPickingImage && !isCompiling){
        currentNodePath.push_back(node);
    }
}


/**
   @return the index of the current object in picking
*/
unsigned int GL1SceneRenderer::Impl::pushPickEndNode(SgNode* node, bool doSetColor)
{
    if(!isRenderingPickingImage || isCompiling){
        return 0;
    } else {
        unsigned int id = pickingNodePathList.size() + 1;
        currentNodePath.push_back(node);
        pickingNodePathList.push_back(std::make_shared<SgNodePath>(currentNodePath));
        if(doSetColor){
            setPickColor(id);
        }
        return id;
    }
}


void GL1SceneRenderer::Impl::popPickNode()
{
    if(isRenderingPickingImage && !isCompiling){
        currentNodePath.pop_back();
    }
}


void GL1SceneRenderer::renderNode(SgNode* node)
{
    impl->renderingFunctions.dispatch(node);
}


void GL1SceneRenderer::Impl::renderGroup(SgGroup* group)
{
    pushPickNode(group);
    renderChildNodes(group);
    popPickNode();
}


void GL1SceneRenderer::renderCustomGroup(SgGroup* group, const std::function<void()>& traverseFunction)
{
    impl->pushPickNode(group);
    traverseFunction();
    impl->popPickNode();
}


void GL1SceneRenderer::Impl::renderSwitchableGroup(SgSwitchableGroup* group)
{
    if(group->isTurnedOn()){
        renderGroup(group);
    }
}


void GL1SceneRenderer::Impl::renderTransform(SgTransform* transform)
{
    if(!transform->empty()){
        
        Affine3 T;
        transform->getTransform(T);

        Vstack.push_back(Vstack.back() * T);

        glPushMatrix();
        glMultMatrixd(T.data());

        /*
        if(isNotRotationMatrix){
          glPushAttrib(GL_ENABLE_BIT);
          glEnable(GL_NORMALIZE);
        }
        */

        // renderGroup(transform);
        pushPickNode(transform);
        renderChildNodes(transform);
        popPickNode();
    
        /*
        if(isNotRotationMatrix){
          glPopAttrib();
        }
        */
    
        glPopMatrix();
        Vstack.pop_back();
    }
}


void GL1SceneRenderer::renderCustomTransform(SgTransform* transform, const std::function<void()>& traverseFunction)
{
    Affine3 T;
    transform->getTransform(T);
    impl->Vstack.push_back(impl->Vstack.back() * T);
    glPushMatrix();
    glMultMatrixd(T.data());
    impl->pushPickNode(transform);

    traverseFunction();

    impl->popPickNode();
    glPopMatrix();
    impl->Vstack.pop_back();
}    


void GL1SceneRenderer::Impl::renderShape(SgShape* shape)
{
    SgMesh* mesh = shape->mesh();
    if(mesh){
        if(mesh->hasVertices()){
            if(!defaultLighting){
                auto id = pushPickEndNode(shape, true);
                renderMesh(mesh, false);
                popPickNode();
            } else {
                SgMaterial* material = shape->material();
                SgTexture* texture = isTextureEnabled ? shape->texture() : nullptr;
                
                if((material && material->transparency() > 0.0)
                   /* || (texture && texture->constImage().hasAlphaComponent()) */){
                    // A transparent shape is rendered later
                    TransparentShapeInfoPtr info = make_shared_aligned<TransparentShapeInfo>();
                    info->shape = shape;
                    info->V = Vstack.back();
                    info->pickId = pushPickEndNode(shape, false);
                    popPickNode();
                    transparentShapeInfos.push_back(info);
                } else {
                    auto id = pushPickEndNode(shape, true);
                    bool hasTexture = false;
                    if(!isRenderingPickingImage){
                        renderMaterial(material);
                        if(texture && mesh->hasTexCoords()){
                            hasTexture = renderTexture(texture, material);
                        }
                    }
                    renderMesh(mesh, hasTexture);
                    popPickNode();
                }
            }
        }
    }
}


void GL1SceneRenderer::Impl::renderUnpickableGroup(SgUnpickableGroup* group)
{
    if(!isRenderingPickingImage){
        renderGroup(group);
    }
}


void GL1SceneRenderer::Impl::renderPickableInvisibleGroup(SgPickableInvisibleGroup* group)
{
    if(isRenderingPickingImage){
        renderGroup(group);
    }
}


void GL1SceneRenderer::Impl::renderMaterial(const SgMaterial* material)
{
    if(!isLightingEnabled){
        if(material){
            setColor(material->diffuseColor());
        }
        return;
    }
    
    if(!material){
        material = defaultMaterial;
    }
    
    float alpha = 1.0 - material->transparency();

    Vector4f color;
    color << material->diffuseColor(), alpha;
    setDiffuseColor(color);
        
    color.head<3>() *= material->ambientIntensity();
    setAmbientColor(color);

    color << material->emissiveColor(), alpha;
    setEmissionColor(color);
    
    color << material->specularColor(), alpha;
    setSpecularColor(color);
    
    setShininess(std::min(std::max(material->specularExponent(), 0.0f), 128.0f));

    lastAlpha = alpha;
}


bool GL1SceneRenderer::Impl::renderTexture(SgTexture* texture, bool withMaterial)
{
    SgImage* sgImage = texture->image();
    if(!sgImage || sgImage->empty()){
        return false;
    }

    const Image& image = sgImage->constImage();
    const int width = image.width();
    const int height = image.height();
    bool doLoadTexImage = false;
    bool doReloadTexImage = false;

    TextureResource* resource;
    auto it = currentResourceMap->find(sgImage);
    if(it != currentResourceMap->end()){
        resource = static_cast<TextureResource*>(it->second.get());
    } else {
        resource = new TextureResource;
        it = currentResourceMap->insert(ResourceMap::value_type(sgImage, resource)).first;
        resource->updateConnection =
            sgImage->sigUpdated().connect(
                [resource](const SgUpdate& update){
                    resource->isImageUpdateNeeded = true;
                });
    }
    if(resource->isBound){
        glBindTexture(GL_TEXTURE_2D, resource->textureName);
        if(resource->isImageUpdateNeeded){
            doLoadTexImage = true;
            doReloadTexImage = resource->isSameSizeAs(image);
        }
    } else {
        glGenTextures(1, &resource->textureName);
        glBindTexture(GL_TEXTURE_2D, resource->textureName);
        resource->isBound = true;
        doLoadTexImage = true;
    }
    if(isCheckingUnusedResources){
        nextResourceMap->insert(*it);
    }
    resource->width = width;
    resource->height = height;
    resource->numComponents = image.numComponents();
    resource->isImageUpdateNeeded = false;
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texture->repeatS() ? GL_REPEAT : GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texture->repeatT() ? GL_REPEAT : GL_CLAMP);

    // Set the base color white to output the texture color with the GL_MODULE mode
    setDiffuseColor(Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); 
    //glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // default
    
    //glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, withMaterial ? GL_MODULATE : GL_REPLACE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    if(isCompiling){
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
    } else {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    }

    if(doLoadTexImage){
        GLenum format = GL_RGB;
        switch(image.numComponents()){
        case 1 : format = GL_LUMINANCE; break;
        case 2 : format = GL_LUMINANCE_ALPHA; break;
        case 3 : format = GL_RGB; break;
        case 4 : format = GL_RGBA; break;
        default : return false;
        }
        
        if(image.numComponents() == 3){
            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        } else {
            glPixelStorei(GL_UNPACK_ALIGNMENT, image.numComponents());
        }

        if(!isCompiling && doReloadTexImage){
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,
                            format, GL_UNSIGNED_BYTE, image.pixels());

        } else {
            double w2 = log2(width);
            double h2 = log2(height);
            double pw = ceil(w2);
            double ph = ceil(h2);
            if((pw - w2 == 0.0) && (ph - h2 == 0.0)){
                glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0,
                             format, GL_UNSIGNED_BYTE, image.pixels());
            } else{
                GLsizei potWidth = pow(2.0, pw);
                GLsizei potHeight = pow(2.0, ph);
                tmpbuf->scaledImageBuf.resize(potWidth * potHeight * image.numComponents());
                scaleImageBilinear(width, height, image.numComponents(), image.pixels(),
                                   potWidth, potHeight,
                                   reinterpret_cast<unsigned char*>(&tmpbuf->scaledImageBuf.front()));
                glTexImage2D(GL_TEXTURE_2D, 0, format, potWidth, potHeight, 0,
                             format, GL_UNSIGNED_BYTE, &tmpbuf->scaledImageBuf.front());
                tmpbuf->used = true;
            }
        } 
    }

    if(SgTextureTransform* tt = texture->textureTransform()){
        glMatrixMode(GL_TEXTURE);
        glLoadIdentity();
        glTranslated(-tt->center()[0], -tt->center()[1], 0.0 );
        glScaled(tt->scale()[0], tt->scale()[1], 0.0 );
        glRotated(degree(tt->rotation()), 0.0, 0.0, 1.0 );
        glTranslated(tt->center()[0], tt->center()[1], 0.0 );
        glTranslated(tt->translation()[0], tt->translation()[1], 0.0 );
        glMatrixMode(GL_MODELVIEW);
    } else {
        glMatrixMode(GL_TEXTURE);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
    }

    return true;
}


/**
   \todo sort the shape nodes by the distance from the viewpoint
*/
void GL1SceneRenderer::Impl::renderTransparentShapes()
{
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();;
    
    if(!isRenderingPickingImage){
        enableBlend(true);
    }
    
    const int n = transparentShapeInfos.size();
    for(int i=0; i < n; ++i){
        TransparentShapeInfo& info = *transparentShapeInfos[i];
        glMatrixMode(GL_MODELVIEW);
        glLoadMatrixd(info.V.data());
        SgShape* shape = info.shape;
        bool hasTexture = false;
        if(isRenderingPickingImage){
            setPickColor(info.pickId);
        } else {
            renderMaterial(shape->material());
            SgTexture* texture = isTextureEnabled ? shape->texture() : nullptr;
            if(texture && shape->mesh()->hasTexCoords()){
                hasTexture = renderTexture(texture, shape->material());
            }
        }
        renderMesh(shape->mesh(), hasTexture);
    }

    if(!isRenderingPickingImage){
        enableBlend(false);
    }

    transparentShapeInfos.clear();

    glPopMatrix();
}


void GL1SceneRenderer::Impl::putMeshData(SgMesh* mesh)
{
    if(!mesh->hasColors()){
        return;
    }
        
    if(mesh->hasVertices()){
        cout << "vertices: \n";
        SgVertexArray& vertices = *mesh->vertices();
        for(size_t i=0; i < vertices.size(); ++i){
            const Vector3f& v = vertices[i];
            cout << "(" << v.x() << ", " << v.y() << ", " << v.z() << "), ";
        }
        cout << "\n";
    }
    if(!mesh->triangleVertices().empty()){
        cout << "triangles: \n";
        const int n = mesh->numTriangles();
        for(int i=0; i < n; ++i){
            SgMesh::TriangleRef t = mesh->triangle(i);
            cout << "(" << t[0] << ", " << t[1] << ", " << t[2] << "), ";
        }
        cout << "\n";
    }
    if(mesh->hasNormals()){
        cout << "normals: \n";
        SgNormalArray& normals = *mesh->normals();
        for(size_t i=0; i < normals.size(); ++i){
            const Vector3f& v = normals[i];
            cout << "(" << v.x() << ", " << v.y() << ", " << v.z() << "), ";
        }
        cout << "\n";
        SgIndexArray& indices = mesh->normalIndices();
        if(!indices.empty()){
            cout << "normalIndices: \n";
            for(size_t i=0; i < indices.size(); ++i){
                cout << indices[i] << ", ";
            }
            cout << "\n";
        }
    }
    if(mesh->hasColors()){
        cout << "colors: \n";
        SgColorArray& colors = *mesh->colors();
        for(size_t i=0; i < colors.size(); ++i){
            const Vector3f& c = colors[i];
            cout << "(" << c.x() << ", " << c.y() << ", " << c.z() << "), ";
        }
        cout << "\n";
        SgIndexArray& indices = mesh->colorIndices();
        if(!indices.empty()){
            cout << "colorIndices: \n";
            for(size_t i=0; i < indices.size(); ++i){
                cout << indices[i] << ", ";
            }
            cout << "\n";
        }
    }
    cout << endl;
}


VertexResource* GL1SceneRenderer::Impl::findOrCreateVertexResource(SgObject* object, bool& out_found)
{
    VertexResource* resource;
    auto it = currentResourceMap->find(object);
    if(it != currentResourceMap->end()){
        resource = static_cast<VertexResource*>(it->second.get());
        out_found = true;
    } else {
        resource = new VertexResource;
        it = currentResourceMap->insert(ResourceMap::value_type(object, resource)).first;
        resource->updateConnection =
            object->sigUpdated().connect(
                [this, object](const SgUpdate& update){
                    updatedSceneObjects.push_back(object);
                });
        out_found = false;
    }
    if(isCheckingUnusedResources){
        nextResourceMap->insert(*it);
    }
    return resource;
}
    

void GL1SceneRenderer::Impl::renderMesh(SgMesh* mesh, bool hasTexture)
{
    if(false){
        putMeshData(mesh);
    }

    bool doCullFace;
    switch(backFaceCullingMode){
    case ENABLE_BACK_FACE_CULLING:
        doCullFace = mesh->isSolid();
        break;
    case DISABLE_BACK_FACE_CULLING:
        doCullFace = false;
        break;
    case FORCE_BACK_FACE_CULLING:
    default:
        doCullFace = true;
        break;
    }

    bool found;
    auto resource = findOrCreateVertexResource(mesh, found);
    if(!found){
        setupMeshResource(mesh, resource, hasTexture);
    }

    const GLvoid* offset = 0;

    glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

    enableCullFace(doCullFace);
    setLightModelTwoSide(!doCullFace);

    glEnableClientState(GL_VERTEX_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, resource->vertexBufferName());
    glVertexPointer(3, GL_FLOAT, 0, offset);

    bool isNormalArrayActive = false;
    bool isColorArrayActive = false;
    bool isTextureActive = false;
        
    if(isLightingEnabled){
        if(resource->normalBufferName() != GL_INVALID_VALUE){
            glEnableClientState(GL_NORMAL_ARRAY);
            glBindBuffer(GL_ARRAY_BUFFER, resource->normalBufferName());
            glNormalPointer(GL_FLOAT, 0, offset);
            isNormalArrayActive = true;
        }
        if(resource->colorBufferName() != GL_INVALID_VALUE){
            glEnableClientState(GL_COLOR_ARRAY);
            glBindBuffer(GL_ARRAY_BUFFER, resource->colorBufferName());
            glColorPointer(4, GL_FLOAT, 0, offset);
            glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
            glEnable(GL_COLOR_MATERIAL);
            isColorArrayActive = true;
        }
        if(resource->texCoordBufferName() != GL_INVALID_VALUE){
            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
            glBindBuffer(GL_ARRAY_BUFFER, resource->texCoordBufferName());
            glTexCoordPointer(2, GL_FLOAT, 0, offset);
            glEnable(GL_TEXTURE_2D);
            isTextureActive = true;
        }
    }
    
    glDrawArrays(GL_TRIANGLES, 0, resource->numVertices);

    if(isColorArrayActive){
        glDisable(GL_COLOR_MATERIAL);
        stateFlag.reset(DIFFUSE_COLOR);
        stateFlag.reset(AMBIENT_COLOR);
    }
    if(isTextureActive){
        glDisable(GL_TEXTURE_2D);
    }

    if(doNormalVisualization && isNormalArrayActive && !isRenderingPickingImage){
        glDisableClientState(GL_NORMAL_ARRAY);
        if(isColorArrayActive){
            glDisableClientState(GL_COLOR_ARRAY);
        }
        if(isTextureActive){
            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        }
        renderNormalVisualizationLines(mesh, resource);
    }

    glPopClientAttrib();
}


void GL1SceneRenderer::Impl::setupMeshResource(SgMesh* mesh, VertexResource* resource, bool hasTexture)
{
    SgIndexArray& orgTriangleVertices = mesh->triangleVertices();
    const size_t totalNumVertices = orgTriangleVertices.size();

    SgVertexArray& orgVertices = *mesh->vertices();
    auto vertices = tmpbuf->vertices;
    vertices.clear();
    vertices.reserve(totalNumVertices);
    
    SgNormalArray* pNormals = nullptr;
    if(mesh->hasNormals()){
        pNormals = &tmpbuf->normals;
        pNormals->clear();
        pNormals->reserve(totalNumVertices);
    }
    ColorArray* pColors = nullptr;
    if(mesh->hasColors()){
        pColors = &tmpbuf->colors;
        pColors->clear();
        pColors->reserve(totalNumVertices);
    }
    SgTexCoordArray* pTexCoords = nullptr;
    if(hasTexture){
        pTexCoords = &tmpbuf->texCoords;
        pTexCoords->clear();
        pTexCoords->reserve(totalNumVertices);
    }
    tmpbuf->used = true;

    for(size_t i=0; i < totalNumVertices; ++i){
        const int orgVertexIndex = orgTriangleVertices[i];
        vertices.push_back(orgVertices[orgVertexIndex]);
        if(pNormals){
            if(mesh->normalIndices().empty()){
                pNormals->push_back(mesh->normals()->at(orgVertexIndex));
            } else {
                const int normalIndex = mesh->normalIndices()[i];
                pNormals->push_back(mesh->normals()->at(normalIndex));
            }
        }
        if(pColors){
            if(mesh->colorIndices().empty()){
                pColors->push_back(createColorWithAlpha(mesh->colors()->at(i)));
            } else {
                const int colorIndex = mesh->colorIndices()[i];
                pColors->push_back(createColorWithAlpha(mesh->colors()->at(colorIndex)));
            }
        }
        if(pTexCoords){
            if(mesh->texCoordIndices().empty()){
                pTexCoords->push_back(mesh->texCoords()->at(orgVertexIndex));
            } else {
                const int texCoordIndex = mesh->texCoordIndices()[i];
                pTexCoords->push_back(mesh->texCoords()->at(texCoordIndex));
            }
        }
    }

    glGenBuffers(1, &resource->vertexBufferName());
    glBindBuffer(GL_ARRAY_BUFFER, resource->vertexBufferName());
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vector3f), vertices.data(), GL_STATIC_DRAW);
    resource->numVertices = vertices.size();

    if(pNormals){
        glGenBuffers(1, &resource->normalBufferName());
        glBindBuffer(GL_ARRAY_BUFFER, resource->normalBufferName());
        glBufferData(GL_ARRAY_BUFFER, pNormals->size() * sizeof(Vector3f), pNormals->data(), GL_STATIC_DRAW);
    }
    if(pColors){
        glGenBuffers(1, &resource->colorBufferName());
        glBindBuffer(GL_ARRAY_BUFFER, resource->colorBufferName());
        glBufferData(GL_ARRAY_BUFFER, pColors->size() * sizeof(ColorArray::value_type), pColors->data(), GL_STATIC_DRAW);
    }
    if(pTexCoords){
        glGenBuffers(1, &resource->texCoordBufferName());
        glBindBuffer(GL_ARRAY_BUFFER, resource->texCoordBufferName());
        glBufferData(GL_ARRAY_BUFFER, pTexCoords->size() * sizeof(Vector2f), pTexCoords->data(), GL_STATIC_DRAW);
    }

    glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind the buffer
}


void GL1SceneRenderer::Impl::renderNormalVisualizationLines(SgMesh* mesh, VertexResource* resource)
{
    auto& bufferName = resource->normalVisualizationVertexBufferName();

    if(bufferName != GL_INVALID_VALUE){
        glBindBuffer(GL_ARRAY_BUFFER, bufferName);
        
    } else {
        glGenBuffers(1, &bufferName);
        glBindBuffer(GL_ARRAY_BUFFER, bufferName);

        const auto& indices = mesh->triangleVertices();
        const int numVertices = indices.size();
        auto& lines = tmpbuf->vertices;
        tmpbuf->used = true;
        lines.clear();
        lines.reserve(numVertices * 2);
        for(size_t i=0; i < numVertices; ++i){
            const int orgVertexIndex = indices[i];
            const auto& v = mesh->vertices()->at(orgVertexIndex);
            Vector3f* pNormal;
            if(mesh->normalIndices().empty()){
                pNormal = &mesh->normals()->at(orgVertexIndex);
            } else {
                pNormal = &mesh->normals()->at(mesh->normalIndices()[i]);
            }
            lines.push_back(v);
            lines.push_back(v + (*pNormal) * normalLength);
        }
        resource->normalVisualizationSize = lines.size();
        glBufferData(GL_ARRAY_BUFFER, lines.size() * sizeof(Vector3f), lines.data(), GL_STATIC_DRAW);
    }
        
    enableLighting(false);
    glVertexPointer(3, GL_FLOAT, 0, 0);
    setColor(Vector3f(0.0f, 1.0f, 0.0f));
    glDrawArrays(GL_LINES, 0, resource->normalVisualizationSize);
    enableLighting(true);
}


void GL1SceneRenderer::Impl::renderPointSet(SgPointSet* pointSet)
{
    if(!pointSet->hasVertices()){
        return;
    }
    const double s = pointSet->pointSize();
    if(s > 0.0){
        setPointSize(s);
    }

    renderPlot(
        pointSet, (GLenum)GL_POINTS,
        [this, pointSet](VertexResource* resource){
            setupPointSetResource(pointSet, resource);
        });
    
    if(s > 0.0){
        setPointSize(defaultPointSize);
    }
}


void GL1SceneRenderer::Impl::setupPointSetResource(SgPointSet* pointSet, VertexResource* resource)
{
    const SgVertexArray& vertices = *pointSet->vertices();
    glGenBuffers(1, &resource->vertexBufferName());
    glBindBuffer(GL_ARRAY_BUFFER, resource->vertexBufferName());
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vector3f), vertices.data(), GL_STATIC_DRAW);
    resource->numVertices = vertices.size();
    
    if(pointSet->hasNormals()){
        SgNormalArray* pNormals;
        const auto& normalIndices = pointSet->normalIndices();
        if(normalIndices.empty()){
            pNormals = pointSet->normals();
        } else {
            pNormals = &tmpbuf->normals;
            pNormals->clear();
            pNormals->reserve(normalIndices.size());
            tmpbuf->used = true;
            const auto& orgNormals = *pointSet->normals();
            for(size_t i=0; i < normalIndices.size(); ++i){
                pNormals->push_back(orgNormals[normalIndices[i]]);
            }
        }
        glGenBuffers(1, &resource->normalBufferName());
        glBindBuffer(GL_ARRAY_BUFFER, resource->normalBufferName());
        glBufferData(GL_ARRAY_BUFFER, pNormals->size() * sizeof(Vector3f), pNormals->data(), GL_STATIC_DRAW);
    }

    if(pointSet->hasColors()){
        const auto& colorIndices = pointSet->colorIndices();
        const SgColorArray& orgColors = *pointSet->colors();
        ColorArray& colors = tmpbuf->colors;
        tmpbuf->used = true;
        colors.clear();
        colors.reserve(vertices.size());
        if(colorIndices.empty()){
            for(size_t i=0; i < orgColors.size(); ++i){
                colors.push_back(createColorWithAlpha(orgColors[i]));
            }
        } else {
            for(size_t i=0; i < colorIndices.size(); ++i){
                colors.push_back(createColorWithAlpha(orgColors[colorIndices[i]]));
            }
        }
        glGenBuffers(1, &resource->colorBufferName());
        glBindBuffer(GL_ARRAY_BUFFER, resource->colorBufferName());
        glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(ColorArray::value_type), colors.data(), GL_STATIC_DRAW);
    }
    
    glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind the buffer
}


void GL1SceneRenderer::Impl::renderLineSet(SgLineSet* lineSet)
{
    if(!lineSet->hasVertices() || (lineSet->numLines() <= 0)){
        return;
    }

    const double w = lineSet->lineWidth();
    if(w > 0.0){
        setLineWidth(w);
    }
    
    renderPlot(
        lineSet, (GLenum)GL_LINES,
        [this, lineSet](VertexResource* resource){
            setupLineSetResource(lineSet, resource);
        });

    if(w > 0.0){
        setLineWidth(defaultLineWidth);
    }
}


void GL1SceneRenderer::Impl::setupLineSetResource(SgLineSet* lineSet, VertexResource* resource)
{
    const auto& vertexIndices = lineSet->lineVertexIndices();
    const int totalNumVertices = vertexIndices.size();

    const SgVertexArray& orgVertices = *lineSet->vertices();
    SgVertexArray& vertices = tmpbuf->vertices;
    vertices.clear();
    vertices.reserve(totalNumVertices);
    tmpbuf->used = true;

    SgNormalArray* pNormals = nullptr;
    if(lineSet->hasNormals()){
        pNormals = &tmpbuf->normals;
        pNormals->clear();
        pNormals->reserve(totalNumVertices);
    }
    ColorArray* pColors = nullptr;
    if(lineSet->hasColors()){
        pColors = &tmpbuf->colors;
        pColors->clear();
        pColors->reserve(totalNumVertices);
    }

    for(size_t i=0; i < totalNumVertices; ++i){
        const int orgVertexIndex = vertexIndices[i];
        vertices.push_back(orgVertices[orgVertexIndex]);
        if(pNormals){
            if(lineSet->normalIndices().empty()){
                pNormals->push_back(lineSet->normals()->at(orgVertexIndex));
            } else {
                const int normalIndex = lineSet->normalIndices()[i];
                pNormals->push_back(lineSet->normals()->at(normalIndex));
            }
        }
        if(pColors){
            if(lineSet->colorIndices().empty()){
                pColors->push_back(createColorWithAlpha(lineSet->colors()->at(i)));
            } else {
                const int colorIndex = lineSet->colorIndices()[i];
                pColors->push_back(createColorWithAlpha(lineSet->colors()->at(colorIndex)));
            }
        }
    }

    glGenBuffers(1, &resource->vertexBufferName());
    glBindBuffer(GL_ARRAY_BUFFER, resource->vertexBufferName());
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vector3f), vertices.data(), GL_STATIC_DRAW);
    resource->numVertices = vertices.size();

    if(pNormals){
        glGenBuffers(1, &resource->normalBufferName());
        glBindBuffer(GL_ARRAY_BUFFER, resource->normalBufferName());
        glBufferData(GL_ARRAY_BUFFER, pNormals->size() * sizeof(Vector3f), pNormals->data(), GL_STATIC_DRAW);
    }
    if(pColors){
        glGenBuffers(1, &resource->colorBufferName());
        glBindBuffer(GL_ARRAY_BUFFER, resource->colorBufferName());
        glBufferData(GL_ARRAY_BUFFER, pColors->size() * sizeof(ColorArray::value_type), pColors->data(), GL_STATIC_DRAW);
    }
    
    glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind the buffer
}


void GL1SceneRenderer::Impl::renderPlot
(SgPlot* plot, GLenum primitiveMode, function<void(VertexResource*)> setupVertexResource)
{
    bool found;
    auto resource = findOrCreateVertexResource(plot, found);

    if(!found){
        setupVertexResource(resource);
    }

    glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

    const GLvoid* offset = 0;
    glEnableClientState(GL_VERTEX_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, resource->vertexBufferName());
    glVertexPointer(3, GL_FLOAT, 0, offset);

    bool hasNormalArray = (resource->normalBufferName() != GL_INVALID_VALUE);
    bool doLighting = hasNormalArray && isLightingEnabled;
    SgMaterial* material = plot->material() ? plot->material() : defaultMaterial.get();

    if(!doLighting){
        enableLighting(false);
        lastAlpha = 1.0;
        if(!plot->hasColors()){
            setColor(material->diffuseColor() + material->emissiveColor());
        }
    } else {
        enableCullFace(false);
        setLightModelTwoSide(true);
        renderMaterial(material);
        glEnableClientState(GL_NORMAL_ARRAY);
        glBindBuffer(GL_ARRAY_BUFFER, resource->normalBufferName());
        glNormalPointer(GL_FLOAT, 0, offset);
    }

    bool isColorMaterialEnabled = false;
    if(resource->colorBufferName() != GL_INVALID_VALUE && !isRenderingPickingImage){
        glEnableClientState(GL_COLOR_ARRAY);
        glBindBuffer(GL_ARRAY_BUFFER, resource->colorBufferName());
        glColorPointer(4, GL_FLOAT, 0, offset);
        if(doLighting){
            glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
            glEnable(GL_COLOR_MATERIAL);
            isColorMaterialEnabled = true;
        }
    }

    auto id = pushPickEndNode(plot, true);
    glDrawArrays(primitiveMode, 0, resource->numVertices);
    popPickNode();

    if(!doLighting){
        enableLighting(true);
    } else if(isColorMaterialEnabled){
        glDisable(GL_COLOR_MATERIAL);
        stateFlag.reset(CURRENT_COLOR);
    }

    glPopClientAttrib();
}


void GL1SceneRenderer::Impl::renderPolygonDrawStyle(SgPolygonDrawStyle* style)
{
    int prevElements = polygonDisplayElements;
    
    polygonDisplayElements = style->polygonElements();
    if(polygonDisplayElements & SgPolygonDrawStyle::Vertex){
        glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);        
    } else if(polygonDisplayElements & SgPolygonDrawStyle::Edge){
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    } else {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

    renderGroup(style);

    if(prevElements & SgPolygonDrawStyle::Vertex){
        glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);        
    } else if(prevElements & SgPolygonDrawStyle::Edge){
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    } else {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }
    
    polygonDisplayElements = prevElements;
}


void GL1SceneRenderer::Impl::renderViewportOverlay(SgViewportOverlay* overlay)
{
    if(isRenderingPickingImage){
        return;
    }

    const bool wasLightingEnabled = isLightingEnabled;
    enableLighting(false);
            
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    SgViewportOverlay::ViewVolume vv;
    auto& vp = self->viewport();
    overlay->calcViewVolume(vp.w, vp.h, vv);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(vv.left, vv.right, vv.bottom, vv.top, vv.zNear, vv.zFar);

    renderGroup(overlay);
    
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    if(wasLightingEnabled){
        enableLighting(true);
    }
}


void GL1SceneRenderer::Impl::renderOutlineGroup(SgOutline* outline)
{
    glClearStencil(0);
    glClear(GL_STENCIL_BUFFER_BIT);

    glEnable(GL_STENCIL_TEST);

    glStencilFunc(GL_ALWAYS, 1, -1);
    glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);

    renderChildNodes(outline);

    glStencilFunc(GL_NOTEQUAL, 1, -1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

    glPushAttrib(GL_POLYGON_BIT);
    glPolygonMode(GL_FRONT, GL_LINE);
    setColor(outline->color());
    enableColorMaterial(true);

    int orgLineWidth = lineWidth;
    setLineWidth(outline->lineWidth() * 2 + 2);

    bool wasRenderingOutlineGroup = isRenderingOutline;
    isRenderingOutline = true;
    
    renderChildNodes(outline);

    isRenderingOutline = wasRenderingOutlineGroup;

    setLineWidth(orgLineWidth);
    enableColorMaterial(false);
    glPopAttrib();
    glDisable(GL_STENCIL_TEST);
    clearGLState();
}


bool GL1SceneRenderer::isRenderingPickingImage() const
{
    return impl->isRenderingPickingImage;
}


void GL1SceneRenderer::Impl::clearGLState()
{
    stateFlag.reset();
    
    //! \todo get the current state from a GL context
    diffuseColor << 0.0f, 0.0f, 0.0f, 0.0f;
    ambientColor << 0.0f, 0.0f, 0.0f, 0.0f;
    emissionColor << 0.0f, 0.0f, 0.0f, 0.0f;
    specularColor << 0.0f, 0.0f, 0.0f, 0.0f;
    shininess = 0.0f;
    lastAlpha = 1.0f;
    isColorMaterialEnabled = false;
    isCullFaceEnabled = false;
    isCCW = false;
    isLightingEnabled = true;
    isLightModelTwoSide = false;
    isBlendEnabled = true;
    isDepthMaskEnabled = true;

    pointSize = defaultPointSize;
    lineWidth = defaultLineWidth;
}


void GL1SceneRenderer::Impl::setColor(const Vector3f& color)
{
    if(!isRenderingPickingImage){
        if(!stateFlag[CURRENT_COLOR] || color != currentColor){
            glColor4f(color[0], color[1], color[2], 1.0f);
            currentColor = color;
            stateFlag.set(CURRENT_COLOR);
        }
    }
}


void GL1SceneRenderer::setColor(const Vector3f& color)
{
    impl->setColor(color);
}


void GL1SceneRenderer::Impl::enableColorMaterial(bool on)
{
    if(!isRenderingPickingImage){
        if(!stateFlag[COLOR_MATERIAL] || isColorMaterialEnabled != on){
            if(on){
                glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
                glEnable(GL_COLOR_MATERIAL);
            } else {
                glDisable(GL_COLOR_MATERIAL);
            }
            isColorMaterialEnabled = on;
            stateFlag.set(COLOR_MATERIAL);
        }
    }
}


void GL1SceneRenderer::Impl::setDiffuseColor(const Vector4f& color)
{
    if(!stateFlag[DIFFUSE_COLOR] || diffuseColor != color){
        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color.data());
        diffuseColor = color;
        stateFlag.set(DIFFUSE_COLOR);
    }
}


void GL1SceneRenderer::Impl::setAmbientColor(const Vector4f& color)
{
    if(!stateFlag[AMBIENT_COLOR] || ambientColor != color){
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color.data());
        ambientColor = color;
        stateFlag.set(AMBIENT_COLOR);
    }
}


void GL1SceneRenderer::Impl::setEmissionColor(const Vector4f& color)
{
    if(!stateFlag[EMISSION_COLOR] || emissionColor != color){
        glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, color.data());
        emissionColor = color;
        stateFlag.set(EMISSION_COLOR);
    }
}


void GL1SceneRenderer::Impl::setSpecularColor(const Vector4f& color)
{
    if(!stateFlag[SPECULAR_COLOR] || specularColor != color){
        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, color.data());
        specularColor = color;
        stateFlag.set(SPECULAR_COLOR);
    }
}


void GL1SceneRenderer::Impl::setShininess(float s)
{
    if(!stateFlag[SHININESS] || shininess != s){
        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, s);
        shininess = s;
        stateFlag.set(SHININESS);
    }
}


void GL1SceneRenderer::Impl::enableCullFace(bool on)
{
    if(!stateFlag[CULL_FACE] || isCullFaceEnabled != on){
        if(on){
            glEnable(GL_CULL_FACE);
            //glCullFace(GL_BACK);
        } else {
            glDisable(GL_CULL_FACE);
        }
        isCullFaceEnabled = on;
        stateFlag.set(CULL_FACE);
    }
}


void GL1SceneRenderer::Impl::setFrontCCW(bool on)
{
    if(!stateFlag[CCW] || isCCW != on){
        if(on){
            glFrontFace(GL_CCW);
        } else {
            glFrontFace(GL_CW);
        }
        isCCW = on;
        stateFlag.set(CCW);
    }
}


void GL1SceneRenderer::Impl::enableLighting(bool on)
{
    if(isRenderingPickingImage || !defaultLighting){
        on = false;
    }
    if(!stateFlag[LIGHTING] || isLightingEnabled != on){
        if(on){
            glEnable(GL_LIGHTING);
        } else {
            glDisable(GL_LIGHTING);
        }
        isLightingEnabled = on;
        stateFlag.set(LIGHTING);
    }
}


void GL1SceneRenderer::Impl::setLightModelTwoSide(bool on)
{
    if(!stateFlag[LIGHT_MODEL_TWO_SIDE] || isLightModelTwoSide != on){
        glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, on ? GL_TRUE : GL_FALSE);
        isLightModelTwoSide = on;
        stateFlag.set(LIGHT_MODEL_TWO_SIDE);
    }
}


void GL1SceneRenderer::setHeadLightLightingFromBackEnabled(bool on)
{
    impl->isHeadLightLightingFromBackEnabled = on;
}


void GL1SceneRenderer::Impl::enableBlend(bool on)
{
    if(isRenderingPickingImage){
        return;
    }
    if(!stateFlag[BLEND] || isBlendEnabled != on){
        if(on){
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            enableDepthMask(false);
        } else {
            glDisable(GL_BLEND);
            enableDepthMask(true);
        }
        isBlendEnabled = on;
        stateFlag.set(BLEND);
    }
}


void GL1SceneRenderer::Impl::enableDepthMask(bool on)
{
    if(!stateFlag[DEPTH_MASK] || isDepthMaskEnabled != on){
        glDepthMask(on);
        isDepthMaskEnabled = on;
        stateFlag.set(DEPTH_MASK);
    }
}


void GL1SceneRenderer::Impl::setPointSize(float size)
{
    if(!stateFlag[POINT_SIZE] || pointSize != size){
        if(isRenderingPickingImage){
            glPointSize(std::max(size, MinLineWidthForPicking));
        } else {
            glPointSize(size);
        }
        pointSize = size;
        stateFlag.set(POINT_SIZE);
    }
}


void GL1SceneRenderer::Impl::setLineWidth(float width)
{
    if(!stateFlag[LINE_WIDTH] || lineWidth != width){
        if(isRenderingPickingImage){
            glLineWidth(std::max(width, MinLineWidthForPicking));
        } else {
            glLineWidth(width);
        }
        lineWidth = width;
        stateFlag.set(LINE_WIDTH);
    }
}


void GL1SceneRenderer::setLightingMode(LightingMode mode)
{
    if(mode != impl->lightingMode){
        impl->lightingMode = mode;
        if(mode == NormalLighting || mode == MinimumLighting){
            impl->defaultLighting = true;
        } else {
            impl->defaultLighting = false;
        }
        requestToClearResources();
    }
}


GLSceneRenderer::LightingMode GL1SceneRenderer::lightingMode() const
{
    return impl->lightingMode;
}


void GL1SceneRenderer::setDefaultSmoothShading(bool on)
{
    impl->defaultSmoothShading = on;
}


SgMaterial* GL1SceneRenderer::defaultMaterial()
{
    return impl->defaultMaterial;
}


void GL1SceneRenderer::enableTexture(bool on)
{
    if(on != impl->isTextureEnabled){
        impl->isTextureEnabled = on;
        requestToClearResources();
    }
}


void GL1SceneRenderer::setMaterialAmbientNormalizationEnabled(bool /* on */)
{
    // This feature is not supported in GL1SceneRenderer
}


void GL1SceneRenderer::setDefaultPointSize(double size)
{
    if(size != impl->defaultPointSize){
        impl->defaultPointSize = size;
        requestToClearResources();
    }
}


void GL1SceneRenderer::setDefaultLineWidth(double width)
{
    if(width != impl->defaultLineWidth){
        impl->defaultLineWidth = width;
        requestToClearResources();
    }
}


void GL1SceneRenderer::setNormalVisualizationEnabled(bool on)
{
    if(on != impl->doNormalVisualization){
        impl->doNormalVisualization = on;
        requestToClearResources();
    }        
}


void GL1SceneRenderer::setNormalVisualizationLength(double length)
{
    if(length != impl->normalLength){
        impl->normalLength = length;
        requestToClearResources();
    }        
}


void GL1SceneRenderer::enableUnusedResourceCheck(bool on)
{
    if(!on){
        impl->nextResourceMap->clear();
    }
    impl->doUnusedResourceCheck = on;
}


const Affine3& GL1SceneRenderer::currentModelTransform() const
{
    impl->currentModelTransform = impl->lastViewMatrix.inverse() * impl->Vstack.back();
    return impl->currentModelTransform;
}


const Matrix4& GL1SceneRenderer::projectionMatrix() const
{
    return impl->lastProjectionMatrix;
}


const Matrix4& GL1SceneRenderer::viewProjectionMatrix() const
{
    static Matrix4 PV;
    PV = impl->lastProjectionMatrix * impl->lastViewMatrix.matrix();
    return PV;
}


Vector3 GL1SceneRenderer::project(const Vector3& p) const
{
    Matrix4 PV = impl->lastProjectionMatrix * impl->lastViewMatrix.matrix();
    Vector4 p2 = PV * Vector4(p.x(), p.y(), p.z(), 1.0);
    Vector3 p3 = p2.head<3>() / p2.w();
    auto& v = viewport();
    double x = ((p3.x() + 1.0) / 2.0) * v.w + v.x;
    double y = ((p3.y() + 1.0) / 2.0) * v.h + v.y;
    return Vector3(x, y, p3.z());
}


double GL1SceneRenderer::projectedPixelSizeRatio(const Vector3& position) const
{
    Vector3 p2 = impl->lastViewMatrix * position;
    Vector4 p3(1.0, 0.0, p2.z(), 1.0);
    Vector4 q = impl->lastProjectionMatrix * p3;
    double r = (q.x() / q[3]) * viewport().w / 2.0;
    if(r < 0.0){
        r = 0.0;
    }
    return r;
}


void GL1SceneRenderer::setBackFaceCullingMode(int mode)
{
    if(mode != impl->backFaceCullingMode){
        impl->backFaceCullingMode = mode;
        requestToClearResources();
    }
}


int GL1SceneRenderer::backFaceCullingMode() const
{
    return impl->backFaceCullingMode;
}
