Search code examples
pythonpython-2.7wxpythonpyopengl

Linux OpenGL code fails, works on Mac & Windows


I have a fairly complex Python based OpenGL code that runs fine on Windows and Mac, but fails with weird banded-spheres on Linux. Views from two angles: enter image description here enter image description here

Here is what the same code plots on a Mac: enter image description here

The problem is not only with spheres, but this is the easiest thing to show. Does this problem suggest anything to anyone with more experience with OpenGL than I?

Thanks for any hints or suggestions.

Here is some sample code that shows this problem

'''Draws a sphere and axis triplet with openGL; rotates with mouse drag.
This works fine on Windows and Mac, but sphere displays strangely on Linux
'''
import sys
import math
import numpy as np
import numpy.linalg as nl
import wx
import wx.glcanvas
import OpenGL.GL as GL
import OpenGL.GLU as GLU
drawingData = {
    'oldxy' : [0, 0],
    'Quaternion' : np.array([ 0.11783419,  0.87355958,  0.09141639,  0.4633053 ]),
    'linecolors': [(np.array([[0, 0, 0], [1, 0, 0]]), [255,   0,   0]),
                   (np.array([[0, 0, 0], [0, 1, 0]]), [  0, 255,   0]),
                   (np.array([[0, 0, 0], [0, 0, 1]]), [  0,   0, 255])],
}

def Q2Mat(Q):
    ''' make rotation matrix from quaternion
    '''
    QN = Q/np.sqrt(np.sum(np.array(Q)**2))
    aa = QN[0]**2
    ab = QN[0]*QN[1]
    ac = QN[0]*QN[2]
    ad = QN[0]*QN[3]
    bb = QN[1]**2
    bc = QN[1]*QN[2]
    bd = QN[1]*QN[3]
    cc = QN[2]**2
    cd = QN[2]*QN[3]
    dd = QN[3]**2
    M = [[aa+bb-cc-dd, 2.*(bc-ad),  2.*(ac+bd)],
        [2*(ad+bc),   aa-bb+cc-dd,  2.*(cd-ab)],
        [2*(bd-ac),    2.*(ab+cd), aa-bb-cc+dd]]
    return np.array(M)

def prodQVQ(Q,V):
    """compute the quaternion vector rotation qvq-1 = v'
    """
    T2 = Q[0]*Q[1]
    T3 = Q[0]*Q[2]
    T4 = Q[0]*Q[3]
    T5 = -Q[1]*Q[1]
    T6 = Q[1]*Q[2]
    T7 = Q[1]*Q[3]
    T8 = -Q[2]*Q[2]
    T9 = Q[2]*Q[3]
    T10 = -Q[3]*Q[3]
    M = np.array([[T8+T10,T6-T4,T3+T7],[T4+T6,T5+T10,T9-T2],[T7-T3,T2+T9,T5+T8]])
    VP = 2.*np.inner(V,M)
    return VP+V

def invQ(Q):
    '''get inverse of quaternion q=r+ai+bj+ck; q* = r-ai-bj-ck
    '''
    return Q*np.array([1,-1,-1,-1])

def AVdeg2Q(A,V):
    ''' convert angle (degrees) & vector to quaternion
        q=r+ai+bj+ck
    '''
    sind = lambda x: math.sin(x*math.pi/180.)
    cosd = lambda x: math.cos(x*math.pi/180.)
    Q = np.zeros(4)
    d = nl.norm(np.array(V))
    if not A:       #== 0.!
        A = 360.
    if d:
        V = V/d
        p = A/2.
        Q[0] = cosd(p)
        Q[1:4] = V*sind(p)
    else:
        Q[3] = 1.
    return Q

def prodQQ(QA,QB):
    ''' Grassman quaternion product, QA,QB quaternions; q=r+ai+bj+ck
    '''
    D = np.zeros(4)
    D[0] = QA[0]*QB[0]-QA[1]*QB[1]-QA[2]*QB[2]-QA[3]*QB[3]
    D[1] = QA[0]*QB[1]+QA[1]*QB[0]+QA[2]*QB[3]-QA[3]*QB[2]
    D[2] = QA[0]*QB[2]-QA[1]*QB[3]+QA[2]*QB[0]+QA[3]*QB[1]
    D[3] = QA[0]*QB[3]+QA[1]*QB[2]-QA[2]*QB[1]+QA[3]*QB[0]
    return D

def RenderUnitVectors(x,y,z):
    'Show the axes'
    GL.glEnable(GL.GL_COLOR_MATERIAL)
    GL.glLineWidth(2)
    GL.glEnable(GL.GL_BLEND)
    GL.glBlendFunc(GL.GL_SRC_ALPHA,GL.GL_ONE_MINUS_SRC_ALPHA)
    GL.glEnable(GL.GL_LINE_SMOOTH)
    GL.glPushMatrix()
    GL.glTranslate(x,y,z)
    GL.glScalef(1,1,1)
    GL.glBegin(GL.GL_LINES)
    for line,color in drawingData['linecolors']:
            GL.glColor3ubv(color)
            GL.glVertex3fv(-line[1]/2.)
            GL.glVertex3fv(line[1]/2.)
    GL.glEnd()
    GL.glPopMatrix()
    GL.glColor4ubv([0,0,0,0])
    GL.glDisable(GL.GL_LINE_SMOOTH)
    GL.glDisable(GL.GL_BLEND)
    GL.glDisable(GL.GL_COLOR_MATERIAL)

def RenderSphere(x,y,z,radius,color):
    'show a sphere'
    GL.glMaterialfv(GL.GL_FRONT_AND_BACK,GL.GL_DIFFUSE,color)
    GL.glPushMatrix()
    GL.glTranslate(x,y,z)        
    GL.glMultMatrixf(np.eye(4).T)
    GLU.gluSphere(GLU.gluNewQuadric(),radius,20,10)
    GL.glPopMatrix()

class myGLCanvas(wx.Panel):
    def __init__(self, parent, id=-1,dpi=None,**kwargs):
        wx.Panel.__init__(self,parent,id=id,**kwargs)
        if 'win' in sys.platform:           # for Windows (& darwin==Mac) -- already double buffered
            attribs = None
        else:                               # Linux
            attribs = [wx.glcanvas.WX_GL_DOUBLEBUFFER,]
        self.canvas = wx.glcanvas.GLCanvas(self,-1,attribList=attribs,**kwargs)
        self.context = wx.glcanvas.GLContext(self.canvas)
        self.canvas.SetCurrent(self.context)
        sizer=wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.canvas,1,wx.EXPAND)
        self.SetSizer(sizer)
        self.canvas.Bind(wx.EVT_MOTION, self.OnMouseMove)
        self.Draw()
        self.Draw()
        return

    def OnMouseMove(self,event):
        if not event.Dragging():
            drawingData['oldxy'] = list(event.GetPosition())
            return
        # Perform a rotation in x-y space
        oldxy = drawingData['oldxy']
        if not len(oldxy): oldxy = list(event.GetPosition())
        dxy = event.GetPosition()-oldxy
        drawingData['oldxy'] = list(event.GetPosition())
        V = np.array([dxy[1],dxy[0],0.])
        A = 0.25*np.sqrt(dxy[0]**2+dxy[1]**2)
        if not A: return
        # next transform vector back to xtal coordinates via inverse quaternion & make new quaternion
        Q = drawingData['Quaternion']
        V = prodQVQ(invQ(Q),np.inner(np.eye(3),V))
        Q = prodQQ(Q,AVdeg2Q(A,V))
        drawingData['Quaternion'] = Q
        self.Draw()

    def Draw(self):
        GL.glClearColor(0.,0.,0.,0.)
        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
        GL.glInitNames()
        GL.glPushName(0)

        GL.glMatrixMode(GL.GL_PROJECTION)
        GL.glLoadIdentity()
        GL.glViewport(0,0,*self.canvas.GetSize())
        GLU.gluPerspective(20.,self.canvas.GetSize()[0]*1./self.canvas.GetSize()[1],7.5,12.5)
        GLU.gluLookAt(0,0,10,0,0,0,0,1,0)

        # Set Lighting            
        GL.glEnable(GL.GL_DEPTH_TEST)
        GL.glEnable(GL.GL_LIGHTING)
        GL.glEnable(GL.GL_LIGHT0)
        GL.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE,0)
        GL.glLightfv(GL.GL_LIGHT0,GL.GL_AMBIENT,[1,1,1,1])
        GL.glLightfv(GL.GL_LIGHT0,GL.GL_DIFFUSE,[1,1,1,1])

        GL.glMatrixMode(GL.GL_MODELVIEW)
        GL.glLoadIdentity()
        matRot = Q2Mat(drawingData['Quaternion'])
        matRot = np.concatenate((np.concatenate((matRot,[[0],[0],[0]]),axis=1),[[0,0,0,1],]),axis=0)
        GL.glMultMatrixf(matRot.T)
        GL.glMultMatrixf(np.eye(4).T)
        Tx,Ty,Tz = (0.20045985394544949, 0.44135342324377724, 0.40844172594191536)
        GL.glTranslate(-Tx,-Ty,-Tz)
        RenderUnitVectors(Tx,Ty,Tz)
        RenderSphere(0, 0, 0, 0.804, [1.,  1.,  1.])
        self.canvas.SetCurrent(self.context)
        self.canvas.SwapBuffers()

class GUI(wx.App):
    def OnInit(self):
        frame = wx.Frame(None,-1,'ball rendering',wx.DefaultPosition,wx.Size(400,400))
        frame.Show()
        wx.CallAfter(myGLCanvas,frame,size=wx.Size(400,400)) # wait for frame to be displayed
        self.MainLoop()
        return True

if __name__ == '__main__':
    GUI()

Solution

  • You have to specify the number of the bits for the depth buffer, according to the conditions of your hardware, by setting WX_GL_DEPTH_SIZE. The size of the depth buffer should be 16, 24 or 32.

    attribs = [
         wx.glcanvas.WX_GL_RGBA,
         wx.glcanvas.WX_GL_DOUBLEBUFFER,
         wx.glcanvas.WX_GL_DEPTH_SIZE, 16]
    

    See also: