#include "NuuoGLRenderer.h"
#include <QtGui>
static const char yv12ToRgb[] =
"!!ARBfp1.0"
"PARAM c[3] = { {1.0, 0.0, 1.13983, 2.03211},"
"{1.0, -0.39465, -0.58060}," 
"{0.5} };"
"TEMP R0;"
"TEMP R1;"
"TEX R0.x, fragment.texcoord[0], texture[0], 2D;"
"TEX R1.y, fragment.texcoord[0], texture[1], 2D;"
"ADD R0.y, R1.y, -c[2].x;"
"TEX R1.z, fragment.texcoord[0], texture[2], 2D;"
"ADD R0.z, R1.z, -c[2].x;"
"DP3 result.color.x, R0, c[0].xyzy;"
"DP3 result.color.y, R0, c[1];"
"DP3 result.color.z, R0, c[0].xwyy;"
"END";

static const char ycrcbToRgb[] = // NUSP will use YCrCb order.
"!!ARBfp1.0"
"PARAM c[3] = { { 0.5, 0.0625 },"
"{ 1.164, 0, 1.596, 2.0179999 },"
"{ 1.164, -0.391, -0.81300002 } };"
"TEMP R0;"
"TEMP R1;"
"TEX R0.x, fragment.texcoord[0], texture[2], 2D;"
"ADD R1.z, R0.x, -c[0].x;"
"TEX R1.x, fragment.texcoord[0], texture[0], 2D;"
"TEX R0.x, fragment.texcoord[0], texture[1], 2D;"
"ADD R1.x, R1, -c[0].y;"
"ADD R1.y, R0.x, -c[0].x;"
"DP3 result.color.x, R1, c[1];"
"DP3 result.color.y, R1, c[2];"
"DP3 result.color.z, R1, c[1].xwyw;"
"END";

static const char ycbcrToRgb[] =
"!!ARBfp1.0"
"PARAM c[3] = { { 0.5, 0.0625 },"
"{ 1.164, 0, 1.596, 2.0179999 },"
"{ 1.164, -0.391, -0.81300002 } };"
"TEMP R0;"
"TEMP R1;"
"TEX R0.x, fragment.texcoord[0], texture[1], 2D;"
"ADD R1.z, R0.x, -c[0].x;"
"TEX R1.x, fragment.texcoord[0], texture[0], 2D;"
"TEX R0.x, fragment.texcoord[0], texture[2], 2D;"
"ADD R1.x, R1, -c[0].y;"
"ADD R1.y, R0.x, -c[0].x;"
"DP3 result.color.x, R1, c[1];"
"DP3 result.color.y, R1, c[2];"
"DP3 result.color.z, R1, c[1].xwyw;"
"END";

static const char echoRgb[] =
"!!ARBfp1.0"
"TEMP R0;"
"TEX R0, fragment.texcoord[0], texture[0], 2D;"
"MUL R0.x, R0.x, 2.0;"
"MOV result.color, R0;"
"END";

#define DEPTH_FROM_WIDTH(wid, fov) (wid)*0.5/tan(fov/2.0/45.0*atan(1.0))
#define NORMAL_UNIT 1.0f

const double gEye_ZPos = 1.0f;
const double gZoom_UpperBound = 3.5f;
const double gZoom_LowerBound = 0.1f;

// Since all GLRenders are put in the same GL context, it's not necessary to call makeCurrent now.
inline void MakeCurrent(NuuoGLRenderer* pGLRender)
{
    Q_ASSERT(pGLRender != NULL);

    QGraphicsView *pGraphicsView = NULL;
    QGLWidget* pGLWidget = NULL;

    QGraphicsScene* pScene = pGLRender->scene();
    Q_ASSERT( pScene != NULL);

    pGraphicsView = *(pScene->views().begin());
    Q_ASSERT( pGraphicsView != NULL);

    pGLWidget = dynamic_cast<QGLWidget*>( pGraphicsView->viewport() );
    Q_ASSERT( pGLWidget != NULL); // The Widget should be GLWidget, otherwise, theses OpenGL operations will be meaningless.
    pGLWidget->makeCurrent();
}

NuuoGLRenderer::NuuoGLRenderer(int nViews, QGraphicsItem* parent /* = NULL */) : QGraphicsWidget(parent),
m_nViews(nViews), m_initARB(false), m_period(0), m_tick(0), m_transientTick(0), m_displayTick(0), 
m_PlayOrPause(false), m_ForwardOrBackward(false), m_initViewportW(0), m_dZoomFactor(1.0f), m_depth(NORMAL_UNIT), m_ListCameraView(NULL),
m_bPartialDisplay(false), m_bKeepAspectRatio(false), m_bKeepFieldOfView(true), m_bOverview(true), m_eyeTransH(0.0f), m_eyeTransV(0.0f), m_eyePosY(0.0f),
m_eyeRotateH(0.0f), m_eyeRotateV(0.0), m_RGBorYUV(false)
{
    setTransientTick(150);
    setDisplayTick(30);

    m_ListCameraView = new CCameraViewMesh[nViews]; // one view by default.
    m_projMatrix.identity();

    // make sure the initARB() is called in main thread.
    connect(this, SIGNAL(InitARB()), this, SLOT(DoInitARB()), Qt::QueuedConnection);
}
NuuoGLRenderer::~NuuoGLRenderer()
{
    ResetRender();
}

void NuuoGLRenderer::releaseTexture()
{
    if( (m_ListCameraView == NULL) || (m_initARB == false) )
        return;

    MakeCurrent(this);
    for(int t=0; t<m_nViews; t++ )
        glDeleteTextures(3, m_ListCameraView[t].m_textureInfo.texture);
}

void NuuoGLRenderer::ReleaseARB()
{
    if( m_initARB )
    {
        MakeCurrent(this);
        releaseTexture();
        glDeleteProgramsARB(1, &m_program);
    }
    m_initARB = false;
}

void NuuoGLRenderer::DoInitARB()
{
    if( m_initARB )
        return;

    MakeCurrent(this);

    // set 3D perspective
    glEnable(GL_TEXTURE_2D);
    glShadeModel(GL_SMOOTH);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    // ARB needs to be initialized again and locate new textures.
    ReleaseARB();

    // Generate texture objects
    for(int t=0; t<m_nViews; t++ )
        glGenTextures(3, m_ListCameraView[t].m_textureInfo.texture);

    if(m_RGBorYUV)
    {
        m_initARB = true;
        return;
    }

    // setup the program for yuv convertion.
    if (!glGenProgramsARB || !glBindProgramARB || ! glProgramStringARB || !glDeleteProgramsARB )
    {
        qWarning("openGL 1.3 is not supported! NuuoRenderer cannot be initialized successfully\n");
        return;
    }

    glGenProgramsARB(1, &m_program);
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_program);
    glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, (int) strlen(ycbcrToRgb), ycbcrToRgb);
    GLenum errorEnum = glGetError();
    if (errorEnum != GL_NO_ERROR)
    {
        qWarning("Fails to set glProgram. Error code:%d", errorEnum);
        glDeleteProgramsARB(1, &m_program);
    }

    m_initARB = true;
}

void NuuoGLRenderer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget )
{
    MakeCurrent(this);
    QGraphicsView *view = *scene()->views().begin();
 
    if( painter->paintEngine() /*&& painter->paintEngine()->type() == QPaintEngine::OpenGL*/)
    {
        QSizeF widget_size = size();
        qreal htest = view->sceneRect().height();

        if( (widget_size.width() <= 0) || (widget_size.height() <= 0) )
            return;

        painter->beginNativePainting();
        GLdouble viewport[4];

        glGetDoublev(GL_VIEWPORT, viewport); 
        glViewport( scenePos().x(), htest-scenePos().y()-widget_size.height(), widget_size.width() , widget_size.height() );
        
        glEnable(GL_CULL_FACE);
        glDisable(GL_BLEND);

        if (m_initARB || m_RGBorYUV)
        {
            if( m_initViewportW == 0 )
            {
                m_initViewportW = widget_size.width();
                SetPerspProjection(widget_size.width() , widget_size.height());   
            }

            drawQuads(m_tick);
        }
        
        if(m_bOverview && (m_dZoomFactor < 1.0))
        {
            // When use zoom in the video, show whole picture in the corner. (thumbnail)
            double tmpX, tmpY;
            double tmpZoom = m_dZoomFactor;
            GetViewPosition(&tmpX, &tmpY);
            m_eyeTransH = m_eyeTransV = 0.0f;
            m_dZoomFactor = 1.0;
            SetPerspProjection(widget_size.width()/4 , widget_size.height()/4);

            glViewport( scenePos().x(), htest-scenePos().y()-widget_size.height(), widget_size.width()/4 , widget_size.height()/4 );

            glEnable(GL_CULL_FACE);
            glDisable(GL_BLEND);

            drawQuads(m_tick);

            // restore the perspective projection
            m_eyeTransH = tmpX;
            m_eyeTransV = tmpY;
            m_dZoomFactor = tmpZoom;
            SetPerspProjection(widget_size.width() , widget_size.height());
        }

        glEnable(GL_BLEND);
        glDisable(GL_CULL_FACE);

        // reset Viewport  
        glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
        painter->endNativePainting();
    }
    else
    {
        qWarning("PaintEngine is either NULL or not using openGL");
    }
}

void NuuoGLRenderer::resizeEvent(QGraphicsSceneResizeEvent* event)
{
    // Adjust the perspective project according to the new size.
    SetPerspProjection(event->newSize().width(), event->newSize().height());

    if(m_bKeepFieldOfView)
        CheckAspectRatio();
}

void NuuoGLRenderer::SetPerspProjection(float fViewportW, float fViewportH)
{
    if(scene() == NULL)
        return;

    if( (fViewportW<=0) || (fViewportH<=0) || (m_initViewportW<=0) )
        return;

    double ViewportRatio = fViewportW/fViewportH;
    double dResizeRatio = (m_bKeepFieldOfView==false)? fViewportW/m_initViewportW : 1.0f;  // resize factor
    double iVisibleRange = (double)(1 * NORMAL_UNIT) * dResizeRatio * m_dZoomFactor; // how many camera views are shown in the window. (only 1 camera view currently)

    //    dNearWidth     Visible Range
    //   ------------ = --------------    => we want to get a suitable dNearWidth to let users can watch iCameraCount camera views
    //       dNear           depth           (Please refer: svn\Orion\Doc\PerspProjection.PNG)
    double dNear = m_depth - NORMAL_UNIT * 0.5;
    double dFar = 5.0f;
    double dNearWidth = (dNear * iVisibleRange)/(m_depth+gEye_ZPos);        // depth = distance between eye and polygon.

    double dRight = dNearWidth/2;
    double dLeft  = 0 - dRight;
    double dTop = (m_bKeepFieldOfView==false)? dRight/ViewportRatio : dRight;
    double dBottom = 0 - dTop;

    MakeCurrent(this);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();

    glLoadIdentity();
    glFrustum(dLeft, dRight, dBottom, dTop, dNear, dFar);
    glGetFloatv(GL_PROJECTION_MATRIX, m_projMatrix.values);

    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}

void NuuoGLRenderer::SetPerspProjection()
{
    QSizeF widget_size = size();
    SetPerspProjection(widget_size.width(), widget_size.height());
}

void NuuoGLRenderer::CheckBoundary()
{
    Q_ASSERT( m_ListCameraView != NULL );

    if(m_bKeepFieldOfView == false)
        return; // not necessary to check boundary

    double dVisibleRangeH_half = (double)(1 * NORMAL_UNIT) * m_dZoomFactor / 2;
    double dVisibleRangeV_half = (double)(1 * NORMAL_UNIT) * m_dZoomFactor / 2;

    // Check horizontal boundary
    if( m_eyeTransH > (0.5-dVisibleRangeH_half) )
        m_eyeTransH = (0.5-dVisibleRangeH_half);
    else if( m_eyeTransH < (-0.5+dVisibleRangeH_half) )
        m_eyeTransH = (-0.5+dVisibleRangeH_half);

    // Check Vertical boundary
    if( m_eyeTransV > (0.5-dVisibleRangeV_half) )
        m_eyeTransV = (0.5-dVisibleRangeV_half);
    else if( m_eyeTransV < (-0.5+dVisibleRangeV_half) )
        m_eyeTransV = (-0.5+dVisibleRangeV_half);
}

void NuuoGLRenderer::CheckAspectRatio()
{
    QSizeF widget_size = size();

    if(m_bKeepAspectRatio)
        m_ListCameraView[0].AdjustAspectRatio(widget_size.width()/widget_size.height(), 1.467); //*** temp
    else
        m_ListCameraView[0].AdjustAspectRatio(1.0, 1.0);
}

void NuuoGLRenderer::BindTexture(int viewIdx)
{
    MakeCurrent(this);
    if (m_RGBorYUV)
    {
        if( m_ListCameraView[viewIdx].m_textureInfo.bDirty )
        {
            m_BufferMutex.lock();
            int width = m_ListCameraView[viewIdx].m_textureInfo.iWidth;
            int height = m_ListCameraView[viewIdx].m_textureInfo.iHeight;
            uchar* pBuf = m_ListCameraView[viewIdx].m_textureInfo.buf;
            GLenum fmt= m_ListCameraView[viewIdx].m_textureInfo.fmt;
            glBindTexture(GL_TEXTURE_2D, m_ListCameraView[viewIdx].m_textureInfo.texture[0]);
            glTexImage2D(GL_TEXTURE_2D, 0, fmt, width, height, 0, fmt,  GL_UNSIGNED_BYTE, pBuf);

            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 
            m_ListCameraView[viewIdx].m_textureInfo.bDirty = false;
            m_BufferMutex.unlock();
        }
        else
        {
            glBindTexture(GL_TEXTURE_2D, m_ListCameraView[viewIdx].m_textureInfo.texture[0]);
        }
        return;
    }
    else
    {
        uchar* yuvPtr;
        if( m_ListCameraView[viewIdx].m_textureInfo.bDirty )
        {
            m_BufferMutex.lock();
            int imgW = m_ListCameraView[viewIdx].m_textureInfo.iWidth;
            int imgH = m_ListCameraView[viewIdx].m_textureInfo.iHeight;
            yuvPtr = m_ListCameraView[viewIdx].m_textureInfo.buf;  // to keep the buffer reference non-zero and the memory available.
            

            float borderColor[4] = {0.f, 0.f, 0.f, 0.f};
            GLuint textIdx[3] = {GL_TEXTURE0, GL_TEXTURE2, GL_TEXTURE1};
            int w = 0, h = 0;
            for(int t=0; t<3; ++t)
            {
                w = (t==0) ? imgW : imgW/2;
                h = (t==0) ? imgH : imgH/2;
                glActiveTexture(textIdx[t]);
                glBindTexture(GL_TEXTURE_2D, m_ListCameraView[viewIdx].m_textureInfo.texture[t]);
                glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, yuvPtr);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); 
                if (t == 0)
                    borderColor[0] = 0.f;
                else
                    borderColor[0] = 0.5f;
                glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
                yuvPtr += (w * h);
            }
            glActiveTexture(GL_TEXTURE0); 
            m_ListCameraView[viewIdx].m_textureInfo.bDirty = false;
            m_BufferMutex.unlock();
        }
        else
        {
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, m_ListCameraView[viewIdx].m_textureInfo.texture[0]);
            glActiveTexture(GL_TEXTURE1);
            glBindTexture(GL_TEXTURE_2D, m_ListCameraView[viewIdx].m_textureInfo.texture[2]);
            glActiveTexture(GL_TEXTURE2);
            glBindTexture(GL_TEXTURE_2D, m_ListCameraView[viewIdx].m_textureInfo.texture[1]);
            glActiveTexture(GL_TEXTURE0); 
        }
    }

}

void NuuoGLRenderer::drawQuads(int tick)
{
    MakeCurrent(this);
    int   v, nv, time;
    qreal halfDepth, spacing;

    halfDepth  = NORMAL_UNIT * 0.5;

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);   

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMultMatrixf(m_projMatrix.values);

    glMatrixMode(GL_MODELVIEW); 
    glPushMatrix();
    glLoadIdentity();

    glTranslatef(m_eyeTransH, m_eyeTransV, 0.0);
    glRotatef(m_eyeRotateH*100, 0.0, 1.0, 0.0);
    glRotatef(m_eyeRotateV*100, 1.0, 0.0, 0.0);
    gluLookAt(0, m_eyePosY, gEye_ZPos, 0, m_eyePosY, -2, 0, 1, 0);
    //qDebug("eyepos(eyepos: 0, %lf, 0), eyeRef(%lf, %lf, -1)", m_eyePosY, m_eyeRefPosX, m_eyePosY);

#ifdef _DEBUG
    // Show axes for debug
    glTranslatef(0, 0, 0);
    glBegin(GL_LINES);
    {
        glColor3f(1.0f, 0.0f, 0.0f);
        glVertex3d(0, 0, -m_depth);
        glVertex3d(1.0f, 0, -m_depth);

        glColor3f(0.0f, 1.0f, 0.0f);
        glVertex3d(0, 0, -m_depth);
        glVertex3d(0, 1.0f, -m_depth);

        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3d(0, 0, -m_depth);
        glVertex3d(0, 0, 0);

        glColor3f(1.f, 1.f, 1.f);
    }
    glEnd();
#endif

    if (!m_RGBorYUV)
    {
        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_program);
        glEnable(GL_FRAGMENT_PROGRAM_ARB);
    }

    glEnable(GL_TEXTURE_2D);
    if( m_bPartialDisplay )
    {
        // load only the front texture
        v = m_tick / m_period;    
        time = m_tick - v * m_period - m_displayTick;

        if (time < 0) // display mode
        {
			BindTexture(v);
            // Move to visible place.
            glTranslated(0, 0, -halfDepth-m_depth);
            glVertexPointer(3, GL_FLOAT,  0, m_ListCameraView[v].m_VertexArr);   
            glTexCoordPointer(2, GL_FLOAT, 0, m_ListCameraView[v].m_TextureArr);
            glDrawArrays(GL_QUADS, 0, 4);  

        }
        else // transient mode
        {
            spacing = 360.0/m_nViews;
            // during the transient, the front-view would be decided according to the current angle (v)
            // only the front-view and the following view would be plotted
            // the same rule can be applied to n-view
            for(nv=0; nv<2; ++nv)
            {
                v = (v+nv) % m_nViews;

                glPushMatrix(); 
				BindTexture(v);

                // Use halfDepth as radius to rotate objects and then move to visible place.
                glTranslated(0, 0, -halfDepth-m_depth);
                if (!m_ForwardOrBackward)
                {
                    glRotated(spacing * (nv - (double)time/m_transientTick), 0, -1, 0);  
                }
                else
                {
                    glRotated(spacing * (nv - (double)time/m_transientTick), 0, 1, 0);  
                }

                glVertexPointer(3, GL_FLOAT, 0, m_ListCameraView[v].m_VertexArr);   
                glTexCoordPointer(2, GL_FLOAT, 0, m_ListCameraView[v].m_TextureArr);
                glDrawArrays(GL_QUADS, 0, 4);  
                glPopMatrix();
            }// end of "for(nv=0; nv<2; ++nv)"
        }// end of "if(time<0)"
    }
    else // m_bPartialDisplay == false
    {
        for(nv=0; nv<m_nViews; ++nv)
        {
            if( m_ListCameraView[nv].m_textureInfo.m_bTextureArrived == false )
            {
                // There is no video frame for this camera view arrived yet, so not to draw this view currently.
                continue;
            }

            glPushMatrix(); 
            BindTexture(nv);

            // Move to visible place.
            glTranslated(0, 0, -halfDepth-m_depth);

            // Apply the input matrix (got from Panorama configuration)
            glTranslated(0, 0, halfDepth);
            glMultMatrixf(m_ListCameraView[nv].m_TransMatrix.values);
            glTranslated(0, 0, -halfDepth);

            for(int f=0; f<NumOfFace; f++)
            {
                glTexCoordPointer(2, GL_FLOAT, 0, m_ListCameraView[nv].m_TextureArr[f]);
                glVertexPointer(3, GL_FLOAT,  0, m_ListCameraView[nv].m_VertexArr[f]);   
                glDrawArrays(GL_QUADS, 0, 4);
            }

            glPopMatrix();
        }// end of "for(nv=0; nv<m_nViews; ++nv)"
    }
    if (!m_RGBorYUV)
    {
        glDisable(GL_FRAGMENT_PROGRAM_ARB);
    }
    glBindTexture(GL_TEXTURE_2D, 0);
    glPopMatrix();
    glDisable(GL_TEXTURE_2D);

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);    
}

void NuuoGLRenderer::OnBGRArrival(int width, int height, int widthPerLine, uchar* pBuf, int viewIndex /* = 0 */)
{
    if( (viewIndex < 0) || (viewIndex >= m_nViews) )
    {
        qWarning("GL Texture [%d] hasn't been initialized", viewIndex);
        return;
    }

    if( !m_initARB )
    {
        emit InitARB();
    }

    uchar *rgbBuf = NULL;
    int rgbBufSize = width*height*3;
    rgbBuf = new uchar[rgbBufSize];
    memcpy(rgbBuf,pBuf,rgbBufSize);

    m_BufferMutex.lock();
    m_ListCameraView[viewIndex].m_textureInfo.iWidth = width;
    m_ListCameraView[viewIndex].m_textureInfo.iHeight = height;
    if(m_ListCameraView[viewIndex].m_textureInfo.buf != NULL)
    {
         delete[] m_ListCameraView[viewIndex].m_textureInfo.buf;
         m_ListCameraView[viewIndex].m_textureInfo.buf = NULL;
    }
    m_ListCameraView[viewIndex].m_textureInfo.buf = rgbBuf;
    m_ListCameraView[viewIndex].m_textureInfo.m_bTextureArrived = true;
    m_ListCameraView[viewIndex].m_textureInfo.bDirty = true;

    if (widthPerLine == 3 * width)
        m_ListCameraView[viewIndex].m_textureInfo.fmt = GL_RGB;
    else if (widthPerLine == 4 * width)
        m_ListCameraView[viewIndex].m_textureInfo.fmt = GL_RGBA;
    else if (widthPerLine == width)
        m_ListCameraView[viewIndex].m_textureInfo.fmt = GL_LUMINANCE;

    m_RGBorYUV = true;
    m_BufferMutex.unlock();
}
void NuuoGLRenderer::OnImageArrival(int imageWidth, int imageHeight, int imageNumbytes, uchar* yuv, int viewIndex /* = 0 */)
// void NuuoGLRenderer::OnImageArrival(int imageWidth, int imageHeight, int imageNumbytes, 
//                                     SysUtils::RefcntPointer<unsigned char> yuvbuf, int viewIndex /* = 0 */)
{
    if (imageWidth*imageHeight*3/2 != imageNumbytes)
    {
        qWarning("The input image size does not match YV12 format\n");
        return;
    }   

    if (viewIndex < 0 || viewIndex >= m_nViews)
    {
        qWarning("The target GL texture [%d] hasn't been initialized", viewIndex);
        return;
    }

    if(m_ListCameraView[viewIndex].IsRatioAdjusted() == false)
    {
        // We can get the correct image aspect ratio only when the 1st video frame came.
        if(m_bKeepFieldOfView == false)
        {
            // correct the aspect ratio by adjust video polygon geometry
            m_ListCameraView[viewIndex].AdjustGeometry(((double)imageHeight)/imageWidth);
        }
        else if(m_bKeepAspectRatio)
        {
            // correct the aspect ratio by adjust texture mapping
            QSizeF widget_size = size();
            m_ListCameraView[viewIndex].AdjustAspectRatio(widget_size.width()/widget_size.height(), ((double)imageWidth/imageHeight));
        }
    }

    if( !m_initARB )
        emit InitARB();

    m_BufferMutex.lock();
    m_ListCameraView[viewIndex].m_textureInfo.iWidth = imageWidth;
    m_ListCameraView[viewIndex].m_textureInfo.iHeight = imageHeight;
    if(m_ListCameraView[viewIndex].m_textureInfo.buf != NULL)
        delete m_ListCameraView[viewIndex].m_textureInfo.buf;
    m_ListCameraView[viewIndex].m_textureInfo.buf = (uchar*)malloc(imageNumbytes+64);
    memcpy(m_ListCameraView[viewIndex].m_textureInfo.buf,yuv,imageNumbytes);
    //m_ListCameraView[viewIndex].m_textureInfo.yuv = yuv;
    

    m_ListCameraView[viewIndex].m_textureInfo.m_bTextureArrived = true;
    m_ListCameraView[viewIndex].m_textureInfo.bDirty = true;
    m_BufferMutex.unlock();

    m_RGBorYUV = false;
}
void NuuoGLRenderer::animate()
{
    if (!m_PlayOrPause)
    {
        m_tick++;
        m_tick %= (m_period * m_nViews);
    }    
}

int NuuoGLRenderer::viewCount()
{
    return m_nViews;
}
void NuuoGLRenderer::play()
{
    m_PlayOrPause = false;
}
void NuuoGLRenderer::pause()
{
    m_PlayOrPause = true;
}
void NuuoGLRenderer::forward()
{
    m_ForwardOrBackward = false;
}

void NuuoGLRenderer::backward()
{
    m_ForwardOrBackward = true;
}

void  NuuoGLRenderer::setTransientTick(int count)
{
    m_period -= m_transientTick;
    m_period += count;
    m_transientTick = count;
}
int   NuuoGLRenderer::transientTick()
{
    return m_transientTick;
}
void  NuuoGLRenderer::setDisplayTick(int count)
{
    m_period -= m_displayTick;
    m_period += count;
    m_displayTick = count;
}
int   NuuoGLRenderer::displayTick()
{
    return m_displayTick;
}

void NuuoGLRenderer::SetNumOfViews(int nViews)
{
    if( (nViews < 0) || (nViews == m_nViews) )
        return;

    ResetRender();

    if( nViews )
        m_ListCameraView = new CCameraViewMesh[nViews];

    m_nViews = nViews;
}

void NuuoGLRenderer::SetupCameraViews(int viewIdx, float* TransMatrix, float* HomoMatrix)
{
    if( TransMatrix == NULL )
        return;

    if( (viewIdx < 0) || (viewIdx >= m_nViews) )
        return;

	Q_ASSERT( m_ListCameraView != NULL );	// the number of views should be assigned first.
    memcpy(m_ListCameraView[viewIdx].m_TransMatrix.values, TransMatrix, 16*sizeof(float));

    if(HomoMatrix)
    {
        memcpy(m_ListCameraView[viewIdx].m_HomoMatrix.values, HomoMatrix, 9*sizeof(float));
    }

    update();
}

void NuuoGLRenderer::EnableKeepAspectRatio(bool bEnable)
{
    m_bKeepAspectRatio = bEnable;

    if( (m_ListCameraView == NULL) || 
        (m_ListCameraView[0].m_textureInfo.iWidth == 0) || 
        (m_ListCameraView[0].m_textureInfo.iHeight == 0) )
        return; // the texture info is unknown yet, adjust the geometry until the 1st video frame comes.

    CheckAspectRatio();
}

//////////////////////////////////////////////////////////////////////////
// IDisplaySetting
void NuuoGLRenderer::SetViewRotate(double dHOffset, double dVOffset)
{
    if( (dHOffset < -1) || ( dVOffset > 1) )
    {
        qWarning("Please normalize the movement.\n");
        return;
    }

    m_eyeRotateH -= dHOffset;
    m_eyeRotateV += dVOffset;

    update();
}

void NuuoGLRenderer::SetViewTranslate(double dHOffset, double dVOffset, bool boundaryCheck)
{
     if( m_dZoomFactor >= 1.0f )
         return;

    m_eyeTransH += dHOffset;
    m_eyeTransV += dVOffset;

    if( boundaryCheck )
        CheckBoundary();
    
    update();
}

void NuuoGLRenderer::SetViewZoom(double dZoomOffset, bool boundaryCheck)
{
    m_dZoomFactor += dZoomOffset;
    if( m_dZoomFactor < gZoom_LowerBound )
        m_dZoomFactor = gZoom_LowerBound;
    if( boundaryCheck )
    {
        if( m_dZoomFactor > 1.0 )
            m_dZoomFactor = 1.0;

        CheckBoundary();
    }

    // since m_dZoomFactor will affect projection matrix, so estimate the matrix when m_dZoomFactor is changed.
    SetPerspProjection();
    update();
}

void NuuoGLRenderer::GetViewPosition(double* pPosX, double* pPosY)
{
    if( pPosX )
        *pPosX = m_eyeTransH;
    if( pPosY )
        *pPosY = m_eyeTransV;
}

void NuuoGLRenderer::GetZoomFactor(double* pdZoomFactor)
{
    if( pdZoomFactor )
    {
        *pdZoomFactor = 1.0f/m_dZoomFactor;
    }
}

void NuuoGLRenderer::ResetViewPosition()
{
    m_eyeTransH = m_eyeTransV = 0.0f;
}

void NuuoGLRenderer::ResetViewMovement()
{
    ResetViewPosition();
    m_eyeRotateH = m_eyeRotateV = 0.0f;

    m_dZoomFactor = 1.0f;

    // since m_dZoomFactor will affect projection matrix, so estimate the matrix when m_dZoomFactor is changed.
    SetPerspProjection();
    update();
}

void NuuoGLRenderer::SetEyeRotation(double dHorizontal, double dVertical)
{
    m_eyeRotateH = dHorizontal;
    m_eyeRotateV = dVertical;
}

void NuuoGLRenderer::GetEyeRotation(double* pdHorizontal, double* pdVertical)
{
    if( pdHorizontal )
        *pdHorizontal = m_eyeRotateH;

    if( pdVertical )
        *pdVertical = m_eyeRotateV;
}

void NuuoGLRenderer::ResetRender()
{
    // Release the previous allocated texture.
    releaseTexture();

    // ARB needs to be initialized again and locate new textures.
    if( m_initARB )
    {
        MakeCurrent(this);
        glDeleteProgramsARB(1, &m_program);
    }
    m_initARB = false;

    // Reset parameters for the GL rendering
    m_initViewportW = 0;
    
    ResetViewMovement();

    // Release matrix list
    if( m_ListCameraView )
    {
        delete[] m_ListCameraView;
        m_ListCameraView = NULL;
        m_nViews = 0;
    }

//     m_bKeepAspectRatio = false;
//     m_bKeepFieldOfView = true;
}
