<commit_before>"""This module defines convenient classes for building 3D GUI programs.

- GLProgram takes care of basic user input.
- GLNavigationProgram allows 3D navigation with the mouse.
- GLRealtimeProgram calls a subclass-defined idle() function roughly on a
  constant time step.
"""

from OpenGL.GL import *
from OpenGL.GLU import *
import camera
from ..math import so3,se3,vectorops
from ..robotsim import Viewport
import math
import time

class GLViewport:
    """
    A class describing an OpenGL camera view.
        - x,y: upper left hand corner of the view in the OpenGL canvas, in pixels
        - w,h: width and height of the view, in pixels
        - orthogonal: if true, does an orthogonal projection. (Not supported)
        - camera: an orbit camera (see :class:`orbit`)
        - fov: the camera field of view in x direction
        - clippingplanes: a pair containing the near and far clipping planes
    """
    def __init__(self):
        self.orthogonal = False
        self.x,self.y = 0,0
        self.w,self.h = 640,480
        self.camera = camera.orbit()
        self.camera.dist = 6.0
        #x field of view in degrees
        self.fov = 30
        #near and far clipping planes
        self.clippingplanes = (0.2,20)

    def contains(self,x,y):
        return x >= self.x and y >= self.y and x < self.x + self.w and y < self.y + self.h

    def fit(self,center,radius):
        """Fits the viewport to an object filling a sphere of a certain center
        and radius"""
        self.camera.tgt = center
        self.camera.dist = radius*2
        zmin,zmax = self.clippingplanes
        if radius < self.clippingplanes[0]:
            zmin = radius*0.5
        if radius*3 > self.clippingplanes[1]:
            zmax =radius*3.5
        self.clippingplanes = (zmin,zmax)

    def toViewport(self):
        """Returns a Klampt C++ Viewport() instance corresponding to this view.
        This is used to interface with the Widget classes"""
        vp = Viewport()
        vp.x,vp.y,vp.w,vp.h = self.x,self.y,self.w,self.h
        vp.n,vp.f = self.clippingplanes
        vp.perspective = True
        aspect = float(self.w)/float(self.h)
        rfov = self.fov*math.pi/180.0
        vp.scale = 1.0/(2.0*math.tan(rfov*0.5/aspect)*aspect)
        vp.setRigidTransform(*self.camera.matrix())
        return vp

    def click_ray(self,x,y):
        """Returns a pair of 3-tuples indicating the ray source and direction
        in world coordinates for a screen-coordinate point (x,y)"""
        R,t = self.camera.matrix()
        #from x and y compute ray direction
        u = float(x-(self.x + self.w/2))/self.w
        v = float((self.y + self.h/2) -y)/self.w
        aspect = float(self.w)/float(self.h)
        rfov = self.fov*math.pi/180.0
        scale = 2.0*math.tan(rfov*0.5/aspect)*aspect
        d = (u*scale,v*scale,-1.0)
        d = vectorops.div(d,vectorops.norm(d))
        return (t,so3.apply(R,d))

    def project(self,pt,clip=True):
        """Given a point in world space, returns the (x,y,z) coordinates of the projected
        pixel.  z is given in absolute coordinates, while x,y are given in pixel values.

        If clip=True and the point is out of the viewing volume, then None is returned.
        Otherwise, if the point is exactly at the focal plane then the middle of the viewport
        is returned.
        """
        ploc = se3.apply(se3.inv(self.camera.matrix()),pt)
        if clip:
            if -ploc[2] <= self.clippingplanes[0] or -ploc[2] >= self.clippingplanes[1]:
                return None
        if abs(ploc[2]) < 1e-8:
            return (self.x+self.w/2,self.y+self.h/2)
        #d = (u*scale,v*scale,-1.0)
        #ploc.x = ploc.z*d.x
        #ploc.y = ploc.z*d.y
        aspect = float(self.w)/float(self.h)
        rfov = self.fov*math.pi/180.0
        scale = 2.0*math.tan(rfov*0.5/aspect)*aspect
        u = -ploc[0]/(ploc[2]*scale)
        v = -ploc[1]/(ploc[2]*scale)
        if clip and (abs(u) > 0.5 or abs(v) > 0.5):
            return None
        x = u*self.w + (self.x + self.w/2)
        y = (self.y + self.h/2) - v*self.w
        return (x,y,-ploc[2])

    def setCurrentGL(self):
        """Sets up the view in the current OpenGL context"""
        # Projection
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        aspect = float(self.w)/float(self.h)
        gluPerspective (self.fov/aspect,aspect,self.clippingplanes[0],self.clippingplanes[1])

        # Initialize ModelView matrix
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        
        # View transformation
        mat = se3.homogeneous(se3.inv(self.camera.matrix()))
        cols = zip(*mat)
        pack = sum((list(c) for c in cols),[])
        glMultMatrixf(pack)

class GLProgramAction:
    def __init__(self,hook,short_text,key,description=None):
        self.hook = hook
        self.short_text = short_text
        self.key = key
        self.description = description
        if description == None:
            self.description = short_text

class GLProgram:
    """A basic OpenGL visualization, run as part of some _GLBackend.
    For the most part there is a one-to-one correspondence and the
    backend just relays the input / drawing messages

    Assumes that glinit.py has been imported to define _GLBackend.

    Attributes:
        - name: title of the window (only has an effect before calling
          run())
        - window: the QtBackend or GLUTBackend instance
        - view: GLViewport instance.  If this is provided to an empty _GLBackend
          window, the w,h gives a hint to the size of the window.  It is then updated
          by the user and setting the viewport size has no effect on the window.
        - clearColor: the RGBA floating point values of the background color.
        - glutInitialized: true if GLUT has been initialized
    """
    def __init__(self,name="OpenGL Program"):
        global _GLBackend
        self.window = None
        self.name = name
        self.view = GLViewport()
        self.clearColor = [1.0,1.0,1.0,0.0]
        self.actions = []

    def add_action(self,hook,short_text,key,description=None):
        """Defines a new generic GUI action.  The action will be available in a menu in
        Qt or as keyboard commands in GLUT."""
        self.actions.append(GLProgramAction(hook,short_text,key,description))

    def run(self):
        """Starts a new event loop with this object as the main program.
        Note: might not return, in the case of GLUT.
        """
        import visualization
        visualization.run(self)

    def initialize(self):
        """Called after the GL context is initialized, but before main loop.
        May be overridden.  Users should not call this directly!"""
        assert self.window != None
        for a in self.actions:
            self.window.add_action(a.hook,a.short_text,a.key,a.description)
        return True

    def refresh(self):
        """Call this to redraw the screen on the next event loop"""
        self.window.refresh()

    def modifiers(self):
        """Retrieves a list of currently pressed keyboard modifiers.
        Values can be any combination of 'ctrl', 'shift', 'alt'.
        """
        return self.window.modifiers()<commit_msg>GLProgram class sets the window title properly<commit_after>"""This module defines convenient classes for building 3D GUI programs.

- GLProgram takes care of basic user input.
- GLNavigationProgram allows 3D navigation with the mouse.
- GLRealtimeProgram calls a subclass-defined idle() function roughly on a
  constant time step.
"""

from OpenGL.GL import *
from OpenGL.GLU import *
import camera
from ..math import so3,se3,vectorops
from ..robotsim import Viewport
import math
import time

class GLViewport:
    """
    A class describing an OpenGL camera view.
        - x,y: upper left hand corner of the view in the OpenGL canvas, in pixels
        - w,h: width and height of the view, in pixels
        - orthogonal: if true, does an orthogonal projection. (Not supported)
        - camera: an orbit camera (see :class:`orbit`)
        - fov: the camera field of view in x direction
        - clippingplanes: a pair containing the near and far clipping planes
    """
    def __init__(self):
        self.orthogonal = False
        self.x,self.y = 0,0
        self.w,self.h = 640,480
        self.camera = camera.orbit()
        self.camera.dist = 6.0
        #x field of view in degrees
        self.fov = 30
        #near and far clipping planes
        self.clippingplanes = (0.2,20)

    def contains(self,x,y):
        return x >= self.x and y >= self.y and x < self.x + self.w and y < self.y + self.h

    def fit(self,center,radius):
        """Fits the viewport to an object filling a sphere of a certain center
        and radius"""
        self.camera.tgt = center
        self.camera.dist = radius*2
        zmin,zmax = self.clippingplanes
        if radius < self.clippingplanes[0]:
            zmin = radius*0.5
        if radius*3 > self.clippingplanes[1]:
            zmax =radius*3.5
        self.clippingplanes = (zmin,zmax)

    def toViewport(self):
        """Returns a Klampt C++ Viewport() instance corresponding to this view.
        This is used to interface with the Widget classes"""
        vp = Viewport()
        vp.x,vp.y,vp.w,vp.h = self.x,self.y,self.w,self.h
        vp.n,vp.f = self.clippingplanes
        vp.perspective = True
        aspect = float(self.w)/float(self.h)
        rfov = self.fov*math.pi/180.0
        vp.scale = 1.0/(2.0*math.tan(rfov*0.5/aspect)*aspect)
        vp.setRigidTransform(*self.camera.matrix())
        return vp

    def click_ray(self,x,y):
        """Returns a pair of 3-tuples indicating the ray source and direction
        in world coordinates for a screen-coordinate point (x,y)"""
        R,t = self.camera.matrix()
        #from x and y compute ray direction
        u = float(x-(self.x + self.w/2))/self.w
        v = float((self.y + self.h/2) -y)/self.w
        aspect = float(self.w)/float(self.h)
        rfov = self.fov*math.pi/180.0
        scale = 2.0*math.tan(rfov*0.5/aspect)*aspect
        d = (u*scale,v*scale,-1.0)
        d = vectorops.div(d,vectorops.norm(d))
        return (t,so3.apply(R,d))

    def project(self,pt,clip=True):
        """Given a point in world space, returns the (x,y,z) coordinates of the projected
        pixel.  z is given in absolute coordinates, while x,y are given in pixel values.

        If clip=True and the point is out of the viewing volume, then None is returned.
        Otherwise, if the point is exactly at the focal plane then the middle of the viewport
        is returned.
        """
        ploc = se3.apply(se3.inv(self.camera.matrix()),pt)
        if clip:
            if -ploc[2] <= self.clippingplanes[0] or -ploc[2] >= self.clippingplanes[1]:
                return None
        if abs(ploc[2]) < 1e-8:
            return (self.x+self.w/2,self.y+self.h/2)
        #d = (u*scale,v*scale,-1.0)
        #ploc.x = ploc.z*d.x
        #ploc.y = ploc.z*d.y
        aspect = float(self.w)/float(self.h)
        rfov = self.fov*math.pi/180.0
        scale = 2.0*math.tan(rfov*0.5/aspect)*aspect
        u = -ploc[0]/(ploc[2]*scale)
        v = -ploc[1]/(ploc[2]*scale)
        if clip and (abs(u) > 0.5 or abs(v) > 0.5):
            return None
        x = u*self.w + (self.x + self.w/2)
        y = (self.y + self.h/2) - v*self.w
        return (x,y,-ploc[2])

    def setCurrentGL(self):
        """Sets up the view in the current OpenGL context"""
        # Projection
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        aspect = float(self.w)/float(self.h)
        gluPerspective (self.fov/aspect,aspect,self.clippingplanes[0],self.clippingplanes[1])

        # Initialize ModelView matrix
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        
        # View transformation
        mat = se3.homogeneous(se3.inv(self.camera.matrix()))
        cols = zip(*mat)
        pack = sum((list(c) for c in cols),[])
        glMultMatrixf(pack)

class GLProgramAction:
    def __init__(self,hook,short_text,key,description=None):
        self.hook = hook
        self.short_text = short_text
        self.key = key
        self.description = description
        if description == None:
            self.description = short_text

class GLProgram:
    """A basic OpenGL visualization, run as part of some _GLBackend.
    For the most part there is a one-to-one correspondence and the
    backend just relays the input / drawing messages

    Assumes that glinit.py has been imported to define _GLBackend.

    Attributes:
        - name: title of the window (only has an effect before calling
          run())
        - window: the QtBackend or GLUTBackend instance
        - view: GLViewport instance.  If this is provided to an empty _GLBackend
          window, the w,h gives a hint to the size of the window.  It is then updated
          by the user and setting the viewport size has no effect on the window.
        - clearColor: the RGBA floating point values of the background color.
        - glutInitialized: true if GLUT has been initialized
    """
    def __init__(self,name="OpenGL Program"):
        global _GLBackend
        self.window = None
        self.name = name
        self.view = GLViewport()
        self.clearColor = [1.0,1.0,1.0,0.0]
        self.actions = []

    def add_action(self,hook,short_text,key,description=None):
        """Defines a new generic GUI action.  The action will be available in a menu in
        Qt or as keyboard commands in GLUT."""
        self.actions.append(GLProgramAction(hook,short_text,key,description))

    def run(self):
        """Starts a new event loop with this object as the main program.
        Note: might not return, in the case of GLUT.
        """
        import visualization
        visualization.setWindowTitle(self.name)
        visualization.run(self)

    def initialize(self):
        """Called after the GL context is initialized, but before main loop.
        May be overridden.  Users should not call this directly!"""
        assert self.window != None
        for a in self.actions:
            self.window.add_action(a.hook,a.short_text,a.key,a.description)
        return True

    def refresh(self):
        """Call this to redraw the screen on the next event loop"""
        self.window.refresh()

    def modifiers(self):
        """Retrieves a list of currently pressed keyboard modifiers.
        Values can be any combination of 'ctrl', 'shift', 'alt'.
        """
        return self.window.modifiers()
{'fraction_non_alphanumeric': 0.10539265942177568, 'fraction_numerical': 0.01312282140660242, 'mean_word_length': 2.2719141323792487, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6175920', 'n_tokens_mistral': 4607, 'n_tokens_neox': 4464, 'n_words': 1412}