Example #1
0
class RadarWidget(QGLWidget):
    vcount_circle = 36
    width = height = 600
    viewport = (0, 0, width, height)
    panlat = 0.0
    panlon = 0.0
    zoom = 1.0
    ar = 1.0
    flat_earth = 1.0
    wraplon = int(-999)
    wrapdir = int(0)
    max_texture_size = 0

    do_text = True
    invalid_count = 0

    def __init__(self, navdb, shareWidget=None):
        super(RadarWidget, self).__init__(shareWidget=shareWidget)
        self.setAttribute(Qt.WA_AcceptTouchEvents, True)
        self.grabGesture(Qt.PanGesture)
        self.grabGesture(Qt.PinchGesture)
        # self.grabGesture(Qt.SwipeGesture)

        # The number of aircraft in the simulation
        self.map_texture = 0
        self.naircraft = 0
        self.nwaypoints = 0
        self.nairports = 0
        self.route_acid = ""
        self.ssd_ownship = np.array([], dtype=np.uint16)
        self.apt_inrange = np.array([])
        self.ssd_all = False
        self.navdb = navdb
        self.iactconn = 0
        self.nodedata = list()

        # Display flags
        self.show_map = True
        self.show_coast = True
        self.show_traf = True
        self.show_pz = False
        self.show_lbl = True
        self.show_wpt = True
        self.show_apt = True

        self.initialized = False

        # Connect to manager's nodelist changed and activenode changed signal
        manager.instance.nodes_changed.connect(self.nodesChanged)
        manager.instance.activenode_changed.connect(self.actnodeChanged)

        # Load vertex data
        self.vbuf_asphalt, self.vbuf_concrete, self.vbuf_runways, self.vbuf_rwythr, \
            self.apt_ctrlat, self.apt_ctrlon, self.apt_indices = load_aptsurface()

    @pyqtSlot(str, tuple, int)
    def nodesChanged(self, address, nodeid, connidx):
        # For each node we have to keep data such as the visible polygons, etc.
        self.nodedata.append(nodeData())

    @pyqtSlot(tuple, int)
    def actnodeChanged(self, nodeid, connidx):
        self.iactconn = connidx
        nact = self.nodedata[connidx]
        if len(nact.polydata) > 0:
            update_buffer(self.allpolysbuf, nact.polydata)
        self.allpolys.set_vertex_count(len(nact.polydata) / 2)

    def create_objects(self):
        if not self.isValid():
            self.invalid_count += 1
            print 'Radarwidget: Context not valid in create_objects, count=%d' % self.invalid_count
            QTimer.singleShot(100, self.create_objects)
            return

        # Make the radarwidget context current, necessary when create_objects is not called from initializeGL
        self.makeCurrent()

        # Initialize font for radar view with specified settings
        self.font = Font()
        self.font.create_font_array(char_height=text_texture_size,
                                    font_family=font_family,
                                    font_weight=font_weight)
        self.font.init_shader(self.text_shader)

        # Load and bind world texture
        max_texture_size = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE)
        print 'Maximum supported texture size: %d' % max_texture_size
        for i in [16384, 8192, 4096]:
            if max_texture_size >= i:
                fname = 'data/graphics/world.%dx%d.dds' % (i, i / 2)
                print 'Loading texture ' + fname
                self.map_texture = self.bindTexture(fname)
                break

        # Create initial empty buffers for aircraft position, orientation, label, and color
        self.achdgbuf = create_empty_buffer(MAX_NAIRCRAFT * 4,
                                            usage=gl.GL_STREAM_DRAW)
        self.aclatbuf = create_empty_buffer(MAX_NAIRCRAFT * 4,
                                            usage=gl.GL_STREAM_DRAW)
        self.aclonbuf = create_empty_buffer(MAX_NAIRCRAFT * 4,
                                            usage=gl.GL_STREAM_DRAW)
        self.acaltbuf = create_empty_buffer(MAX_NAIRCRAFT * 4,
                                            usage=gl.GL_STREAM_DRAW)
        self.actasbuf = create_empty_buffer(MAX_NAIRCRAFT * 4,
                                            usage=gl.GL_STREAM_DRAW)
        self.accolorbuf = create_empty_buffer(MAX_NAIRCRAFT * 3,
                                              usage=gl.GL_STREAM_DRAW)
        self.aclblbuf = create_empty_buffer(MAX_NAIRCRAFT * 24,
                                            usage=gl.GL_STREAM_DRAW)
        self.confcpabuf = create_empty_buffer(MAX_NCONFLICTS * 16,
                                              usage=gl.GL_STREAM_DRAW)
        self.polyprevbuf = create_empty_buffer(MAX_POLYPREV_SEGMENTS * 8,
                                               usage=gl.GL_DYNAMIC_DRAW)
        self.allpolysbuf = create_empty_buffer(MAX_ALLPOLYS_SEGMENTS * 16,
                                               usage=gl.GL_DYNAMIC_DRAW)
        self.routebuf = create_empty_buffer(MAX_ROUTE_LENGTH * 8,
                                            usage=gl.GL_DYNAMIC_DRAW)
        self.routewplatbuf = create_empty_buffer(MAX_ROUTE_LENGTH * 4,
                                                 usage=gl.GL_DYNAMIC_DRAW)
        self.routewplonbuf = create_empty_buffer(MAX_ROUTE_LENGTH * 4,
                                                 usage=gl.GL_DYNAMIC_DRAW)
        self.routelblbuf = create_empty_buffer(MAX_ROUTE_LENGTH * 12,
                                               usage=gl.GL_DYNAMIC_DRAW)

        # ------- Map ------------------------------------
        self.map = RenderObject(gl.GL_TRIANGLE_FAN, vertex_count=4)
        mapvertices = np.array([(-90.0, 540.0), (-90.0, -540.0),
                                (90.0, -540.0), (90.0, 540.0)],
                               dtype=np.float32)
        texcoords = np.array([(1, 3), (1, 0), (0, 0), (0, 3)],
                             dtype=np.float32)
        self.map.bind_attrib(ATTRIB_VERTEX, 2, mapvertices)
        self.map.bind_attrib(ATTRIB_TEXCOORDS, 2, texcoords)

        # ------- Coastlines -----------------------------
        self.coastlines = RenderObject(gl.GL_LINES)
        coastvertices, coastindices = load_coastlines()
        self.coastlines.bind_attrib(ATTRIB_VERTEX, 2, coastvertices)
        self.coastlines.bind_attrib(ATTRIB_COLOR,
                                    3,
                                    np.array(lightblue2, dtype=np.uint8),
                                    datatype=gl.GL_UNSIGNED_BYTE,
                                    normalize=True,
                                    instance_divisor=1)
        self.vcount_coast = len(coastvertices)
        self.coastindices = coastindices
        del coastvertices

        # ------- Runways --------------------------------
        self.runways = RenderObject(gl.GL_TRIANGLES)
        self.runways.bind_attrib(ATTRIB_VERTEX, 2, self.vbuf_runways)
        self.runways.bind_attrib(ATTRIB_COLOR,
                                 3,
                                 np.array(grey, dtype=np.uint8),
                                 datatype=gl.GL_UNSIGNED_BYTE,
                                 normalize=True,
                                 instance_divisor=1)
        self.runways.set_vertex_count(len(self.vbuf_runways) / 2)

        #---------Runway Thresholds-----------------------
        self.thresholds = RenderObject(gl.GL_TRIANGLES)
        self.thresholds.bind_attrib(ATTRIB_VERTEX, 2, self.vbuf_rwythr)
        self.thresholds.bind_attrib(ATTRIB_COLOR,
                                    3,
                                    np.array(white, dtype=np.uint8),
                                    datatype=gl.GL_UNSIGNED_BYTE,
                                    normalize=True,
                                    instance_divisor=1)
        self.thresholds.set_vertex_count(len(self.vbuf_rwythr) / 2)

        # ------- Taxiways -------------------------------
        self.taxiways = RenderObject(gl.GL_TRIANGLES)
        self.taxiways.bind_attrib(ATTRIB_VERTEX, 2, self.vbuf_asphalt)
        self.taxiways.bind_attrib(ATTRIB_COLOR,
                                  3,
                                  np.array(grey, dtype=np.uint8),
                                  datatype=gl.GL_UNSIGNED_BYTE,
                                  normalize=True,
                                  instance_divisor=1)
        self.taxiways.set_vertex_count(len(self.vbuf_asphalt) / 2)

        # ------- Pavement -------------------------------
        self.pavement = RenderObject(gl.GL_TRIANGLES)
        self.pavement.bind_attrib(ATTRIB_VERTEX, 2, self.vbuf_concrete)
        self.pavement.bind_attrib(ATTRIB_COLOR,
                                  3,
                                  np.array(lightgrey, dtype=np.uint8),
                                  datatype=gl.GL_UNSIGNED_BYTE,
                                  normalize=True,
                                  instance_divisor=1)
        self.pavement.set_vertex_count(len(self.vbuf_concrete) / 2)

        # Polygon preview object
        self.polyprev = RenderObject(gl.GL_LINE_LOOP)
        self.polyprev.bind_attrib(ATTRIB_VERTEX, 2, self.polyprevbuf)
        self.polyprev.bind_attrib(ATTRIB_COLOR,
                                  3,
                                  np.array(lightblue, dtype=np.uint8),
                                  datatype=gl.GL_UNSIGNED_BYTE,
                                  normalize=True,
                                  instance_divisor=1)

        # Fixed polygons
        self.allpolys = RenderObject(gl.GL_LINES)
        self.allpolys.bind_attrib(ATTRIB_VERTEX, 2, self.allpolysbuf)
        self.allpolys.bind_attrib(ATTRIB_COLOR,
                                  3,
                                  np.array(blue, dtype=np.uint8),
                                  datatype=gl.GL_UNSIGNED_BYTE,
                                  normalize=True,
                                  instance_divisor=1)

        # ------- SSD object -----------------------------
        self.ssd = RenderObject(gl.GL_POINTS)
        self.ssd.bind_attrib(ATTRIB_LAT0, 1, self.aclatbuf, instance_divisor=1)
        self.ssd.bind_attrib(ATTRIB_LON0, 1, self.aclonbuf, instance_divisor=1)
        self.ssd.bind_attrib(ATTRIB_ALT0, 1, self.acaltbuf, instance_divisor=1)
        self.ssd.bind_attrib(ATTRIB_TAS0, 1, self.actasbuf, instance_divisor=1)
        self.ssd.bind_attrib(ATTRIB_TRK0, 1, self.achdgbuf, instance_divisor=1)
        self.ssd.bind_attrib(ATTRIB_LAT1, 1, self.aclatbuf)
        self.ssd.bind_attrib(ATTRIB_LON1, 1, self.aclonbuf)
        self.ssd.bind_attrib(ATTRIB_ALT1, 1, self.acaltbuf)
        self.ssd.bind_attrib(ATTRIB_TAS1, 1, self.actasbuf)
        self.ssd.bind_attrib(ATTRIB_TRK1, 1, self.achdgbuf)

        # ------- Circle ---------------------------------
        # Create a new VAO (Vertex Array Object) and bind it
        self.protectedzone = RenderObject(gl.GL_LINE_LOOP,
                                          vertex_count=self.vcount_circle)
        circlevertices = np.transpose(
            np.array(
                (2.5 * nm *
                 np.cos(np.linspace(0.0, 2.0 * np.pi, self.vcount_circle)),
                 2.5 * nm *
                 np.sin(np.linspace(0.0, 2.0 * np.pi, self.vcount_circle))),
                dtype=np.float32))
        self.protectedzone.bind_attrib(ATTRIB_VERTEX, 2, circlevertices)
        self.protectedzone.bind_attrib(ATTRIB_LAT,
                                       1,
                                       self.aclatbuf,
                                       instance_divisor=1)
        self.protectedzone.bind_attrib(ATTRIB_LON,
                                       1,
                                       self.aclonbuf,
                                       instance_divisor=1)
        self.protectedzone.bind_attrib(ATTRIB_COLOR,
                                       3,
                                       self.accolorbuf,
                                       datatype=gl.GL_UNSIGNED_BYTE,
                                       normalize=True,
                                       instance_divisor=1)

        # ------- A/C symbol -----------------------------
        self.ac_symbol = RenderObject(gl.GL_TRIANGLE_FAN, vertex_count=4)
        acvertices = np.array([(0.0, 0.5 * ac_size),
                               (-0.5 * ac_size, -0.5 * ac_size),
                               (0.0, -0.25 * ac_size),
                               (0.5 * ac_size, -0.5 * ac_size)],
                              dtype=np.float32)
        self.ac_symbol.bind_attrib(ATTRIB_VERTEX, 2, acvertices)
        self.ac_symbol.bind_attrib(ATTRIB_LAT,
                                   1,
                                   self.aclatbuf,
                                   instance_divisor=1)
        self.ac_symbol.bind_attrib(ATTRIB_LON,
                                   1,
                                   self.aclonbuf,
                                   instance_divisor=1)
        self.ac_symbol.bind_attrib(ATTRIB_ORIENTATION,
                                   1,
                                   self.achdgbuf,
                                   instance_divisor=1)
        self.ac_symbol.bind_attrib(ATTRIB_COLOR,
                                   3,
                                   self.accolorbuf,
                                   datatype=gl.GL_UNSIGNED_BYTE,
                                   normalize=True,
                                   instance_divisor=1)
        self.aclabels = self.font.prepare_text_instanced(
            self.aclblbuf, (8, 3),
            self.aclatbuf,
            self.aclonbuf,
            self.accolorbuf,
            char_size=text_size,
            vertex_offset=(ac_size, -0.5 * ac_size))

        # ------- Conflict CPA lines ---------------------
        self.cpalines = RenderObject(gl.GL_LINES)
        self.cpalines.bind_attrib(ATTRIB_VERTEX, 2, self.confcpabuf)
        self.cpalines.bind_attrib(ATTRIB_COLOR,
                                  3,
                                  np.array(amber, dtype=np.uint8),
                                  datatype=gl.GL_UNSIGNED_BYTE,
                                  normalize=True,
                                  instance_divisor=1)

        # ------- Aircraft Route -------------------------
        self.route = RenderObject(gl.GL_LINES)
        self.route.bind_attrib(ATTRIB_VERTEX, 2, self.routebuf)
        self.route.bind_attrib(ATTRIB_COLOR,
                               3,
                               np.array(magenta, dtype=np.uint8),
                               datatype=gl.GL_UNSIGNED_BYTE,
                               normalize=True,
                               instance_divisor=1)
        self.routelbl = self.font.prepare_text_instanced(
            self.routelblbuf, (12, 1),
            self.routewplatbuf,
            self.routewplonbuf,
            char_size=text_size,
            vertex_offset=(wpt_size, 0.5 * wpt_size))

        # ------- Waypoints ------------------------------
        self.nwaypoints = len(self.navdb.wplat)
        self.waypoints = RenderObject(gl.GL_LINE_LOOP,
                                      vertex_count=3,
                                      n_instances=self.nwaypoints)
        wptvertices = np.array([(0.0, 0.5 * wpt_size),
                                (-0.5 * wpt_size, -0.5 * wpt_size),
                                (0.5 * wpt_size, -0.5 * wpt_size)],
                               dtype=np.float32)  # a triangle
        self.waypoints.bind_attrib(ATTRIB_VERTEX, 2, wptvertices)

        # Sort based on id string length
        llid = sorted(zip(self.navdb.wpid, self.navdb.wplat, self.navdb.wplon),
                      key=lambda i: len(i[0]) > 3)
        wplat = [lat for (wpid, lat, lon) in llid]
        wplon = [lon for (wpid, lon, lon) in llid]
        self.wptlatbuf = self.waypoints.bind_attrib(ATTRIB_LAT,
                                                    1,
                                                    np.array(wplat,
                                                             dtype=np.float32),
                                                    instance_divisor=1)
        self.wptlonbuf = self.waypoints.bind_attrib(ATTRIB_LON,
                                                    1,
                                                    np.array(wplon,
                                                             dtype=np.float32),
                                                    instance_divisor=1)
        wptids = ''
        self.nnavaids = 0
        for wptid in llid:
            if len(wptid[0]) <= 3:
                self.nnavaids += 1
            wptids += wptid[0].ljust(5)
        self.wptlabels = self.font.prepare_text_instanced(
            np.array(wptids, dtype=np.string_), (5, 1),
            self.wptlatbuf,
            self.wptlonbuf,
            char_size=text_size,
            vertex_offset=(wpt_size, 0.5 * wpt_size))
        del wptids

        # ------- Airports -------------------------------
        self.nairports = len(self.navdb.aptlat)
        self.airports = RenderObject(gl.GL_LINE_LOOP,
                                     vertex_count=4,
                                     n_instances=self.nairports)
        aptvertices = np.array([(-0.5 * apt_size, -0.5 * apt_size),
                                (0.5 * apt_size, -0.5 * apt_size),
                                (0.5 * apt_size, 0.5 * apt_size),
                                (-0.5 * apt_size, 0.5 * apt_size)],
                               dtype=np.float32)  # a square
        self.airports.bind_attrib(ATTRIB_VERTEX, 2, aptvertices)
        indices = self.navdb.aptype.argsort()
        aplat = np.array(self.navdb.aptlat[indices], dtype=np.float32)
        aplon = np.array(self.navdb.aptlon[indices], dtype=np.float32)
        aptypes = self.navdb.aptype[indices]
        apnames = np.array(self.navdb.aptid)
        apnames = apnames[indices]
        # The number of large, large+med, and large+med+small airports
        self.nairports = [
            aptypes.searchsorted(2),
            aptypes.searchsorted(3), self.nairports
        ]

        self.aptlatbuf = self.airports.bind_attrib(ATTRIB_LAT,
                                                   1,
                                                   aplat,
                                                   instance_divisor=1)
        self.aptlonbuf = self.airports.bind_attrib(ATTRIB_LON,
                                                   1,
                                                   aplon,
                                                   instance_divisor=1)
        aptids = ''
        for aptid in apnames:
            aptids += aptid.ljust(4)
        self.aptlabels = self.font.prepare_text_instanced(
            np.array(aptids, dtype=np.string_), (4, 1),
            self.aptlatbuf,
            self.aptlonbuf,
            char_size=text_size,
            vertex_offset=(apt_size, 0.5 * apt_size))
        del aptids

        # Unbind VAO, VBO
        RenderObject.unbind_all()

        # Set initial values for the global uniforms
        self.globaldata.set_wrap(self.wraplon, self.wrapdir)
        self.globaldata.set_pan_and_zoom(self.panlat, self.panlon, self.zoom)

        # Clean up memory
        del self.vbuf_asphalt, self.vbuf_concrete, self.vbuf_runways, self.vbuf_rwythr

        self.initialized = True

    def initializeGL(self):
        """Initialize OpenGL, VBOs, upload data on the GPU, etc."""

        # First check for supported GL version
        gl_version = float(gl.glGetString(gl.GL_VERSION)[:3])
        if gl_version < 3.3:
            print('OpenGL context created with GL version %.1f' % gl_version)
            qCritical(
                """Your system reports that it supports OpenGL up to version %.1f. The minimum requirement for BlueSky is OpenGL 3.3.
                Generally, AMD/ATI/nVidia cards from 2008 and newer support OpenGL 3.3, and Intel integrated graphics from the Haswell
                generation and newer. If you think your graphics system should be able to support GL>=3.3 please open an issue report
                on the BlueSky Github page (https://github.com/ProfHoekstra/bluesky/issues)"""
                % gl_version)
            return

        # background color
        gl.glClearColor(0, 0, 0, 0)
        gl.glEnable(gl.GL_BLEND)
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)

        self.globaldata = radarUBO()

        try:
            # Compile shaders and link color shader program
            self.color_shader = BlueSkyProgram(
                'data/graphics/shaders/radarwidget-normal.vert',
                'data/graphics/shaders/radarwidget-color.frag')
            self.color_shader.bind_uniform_buffer('global_data',
                                                  self.globaldata)

            # Compile shaders and link texture shader program
            self.texture_shader = BlueSkyProgram(
                'data/graphics/shaders/radarwidget-normal.vert',
                'data/graphics/shaders/radarwidget-texture.frag')
            self.texture_shader.bind_uniform_buffer('global_data',
                                                    self.globaldata)

            # Compile shaders and link text shader program
            self.text_shader = BlueSkyProgram(
                'data/graphics/shaders/radarwidget-text.vert',
                'data/graphics/shaders/radarwidget-text.frag')
            self.text_shader.bind_uniform_buffer('global_data',
                                                 self.globaldata)

            self.ssd_shader = BlueSkyProgram('data/graphics/shaders/ssd.vert',
                                             'data/graphics/shaders/ssd.frag',
                                             'data/graphics/shaders/ssd.geom')
            self.ssd_shader.bind_uniform_buffer('global_data', self.globaldata)
            self.ssd_shader.loc_vlimits = gl.glGetUniformLocation(
                self.ssd_shader.program, 'Vlimits')
            self.ssd_shader.loc_nac = gl.glGetUniformLocation(
                self.ssd_shader.program, 'n_ac')

        except RuntimeError as e:
            qCritical('Error compiling shaders in radarwidget: ' + e.args[0])
            return

        # create all vertex array objects
        self.create_objects()

    def paintGL(self):
        """Paint the scene."""
        # pass if the framebuffer isn't complete yet or if not initialized
        if not (gl.glCheckFramebufferStatus(
                gl.GL_FRAMEBUFFER) == gl.GL_FRAMEBUFFER_COMPLETE
                and self.initialized and self.isVisible()):
            return

        # Set the viewport and clear the framebuffer
        gl.glViewport(*self.viewport)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT)

        # Send the (possibly) updated global uniforms to the buffer
        self.globaldata.set_vertex_scale_type(VERTEX_IS_LATLON)

        # --- DRAW THE MAP AND COASTLINES ---------------------------------------------
        # Map and coastlines: don't wrap around in the shader
        self.globaldata.enable_wrap(False)

        if self.show_map:
            # Select the texture shader
            self.texture_shader.use()

            # Draw map texture
            gl.glActiveTexture(gl.GL_TEXTURE0 + 0)
            gl.glBindTexture(gl.GL_TEXTURE_2D, self.map_texture)
            self.map.draw()

        # Select the non-textured shader
        self.color_shader.use()

        # Draw coastlines
        if self.show_coast:
            if self.wrapdir == 0:
                # Normal case, no wrap around
                self.coastlines.draw(first_vertex=0,
                                     vertex_count=self.vcount_coast)
            else:
                self.coastlines.bind()
                wrapindex = np.uint32(self.coastindices[int(self.wraplon) +
                                                        180])
                if self.wrapdir == 1:
                    gl.glVertexAttrib1f(ATTRIB_LON, 360.0)
                    self.coastlines.draw(first_vertex=0,
                                         vertex_count=wrapindex)
                    gl.glVertexAttrib1f(ATTRIB_LON, 0.0)
                    self.coastlines.draw(first_vertex=wrapindex,
                                         vertex_count=self.vcount_coast -
                                         wrapindex)
                else:
                    gl.glVertexAttrib1f(ATTRIB_LON, -360.0)
                    self.coastlines.draw(first_vertex=wrapindex,
                                         vertex_count=self.vcount_coast -
                                         wrapindex)
                    gl.glVertexAttrib1f(ATTRIB_LON, 0.0)
                    self.coastlines.draw(first_vertex=0,
                                         vertex_count=wrapindex)

        # --- DRAW PREVIEW SHAPE (WHEN AVAILABLE) -----------------------------
        self.polyprev.draw()

        # --- DRAW CUSTOM SHAPES (WHEN AVAILABLE) -----------------------------
        self.allpolys.draw()

        # --- DRAW THE SELECTED AIRCRAFT ROUTE (WHEN AVAILABLE) ---------------
        if self.show_traf:
            self.route.draw()
            self.cpalines.draw()

        # --- DRAW AIRPORT DETAILS (RUNWAYS, TAXIWAYS, PAVEMENTS) -------------
        self.runways.draw()
        self.thresholds.draw()
        if self.zoom >= 1.0:
            for idx in self.apt_inrange:
                self.taxiways.draw(first_vertex=idx[0], vertex_count=idx[1])
                self.pavement.draw(first_vertex=idx[2], vertex_count=idx[3])

        # --- DRAW THE INSTANCED AIRCRAFT SHAPES ------------------------------
        # update wrap longitude and direction for the instanced objects
        self.globaldata.enable_wrap(True)

        # PZ circles only when they are bigger than the A/C symbols
        if self.naircraft > 0 and self.show_traf and self.show_pz and self.zoom >= 0.15:
            self.globaldata.set_vertex_scale_type(VERTEX_IS_METERS)
            self.protectedzone.draw(n_instances=self.naircraft)

        self.globaldata.set_vertex_scale_type(VERTEX_IS_SCREEN)

        # Draw traffic symbols
        if self.naircraft > 0 and self.show_traf:
            self.ac_symbol.draw(n_instances=self.naircraft)

        if self.zoom >= 0.5:
            nairports = self.nairports[2]
        elif self.zoom >= 0.25:
            nairports = self.nairports[1]
        else:
            nairports = self.nairports[0]

        if self.zoom >= 3:
            nwaypoints = self.nwaypoints
        else:
            nwaypoints = self.nnavaids

        # Draw waypoint symbols
        if self.show_wpt:
            self.waypoints.bind()
            gl.glVertexAttrib4Nub(ATTRIB_COLOR, *(lightblue3 + (255, )))
            self.waypoints.draw(n_instances=nwaypoints)

        # Draw airport symbols
        if self.show_apt:
            self.airports.bind()
            gl.glVertexAttrib4Nub(ATTRIB_COLOR, *(lightblue3 + (255, )))
            self.airports.draw(n_instances=nairports)

        if self.do_text:
            self.text_shader.use()
            self.font.use()

            if self.show_apt:
                self.font.set_char_size(self.aptlabels.char_size)
                self.font.set_block_size(self.aptlabels.block_size)
                self.aptlabels.bind()
                gl.glVertexAttrib4Nub(ATTRIB_COLOR, *(lightblue4 + (255, )))
                self.aptlabels.draw(n_instances=nairports)
            if self.show_wpt:
                self.font.set_char_size(self.wptlabels.char_size)
                self.font.set_block_size(self.wptlabels.block_size)
                self.wptlabels.bind()
                gl.glVertexAttrib4Nub(ATTRIB_COLOR, *(lightblue4 + (255, )))
                self.wptlabels.draw(n_instances=nwaypoints)

            if self.show_traf and self.route.vertex_count > 1:
                gl.glVertexAttrib4Nub(ATTRIB_COLOR, *(magenta + (255, )))
                self.font.set_char_size(self.routelbl.char_size)
                self.font.set_block_size(self.routelbl.block_size)
                self.routelbl.draw()

            if self.naircraft > 0 and self.show_traf and self.show_lbl:
                self.font.set_char_size(self.aclabels.char_size)
                self.font.set_block_size(self.aclabels.block_size)
                self.aclabels.draw(n_instances=self.naircraft)

        # SSD
        if self.ssd_all or len(self.ssd_ownship) > 0:
            self.ssd_shader.use()
            gl.glUniform3f(self.ssd_shader.loc_vlimits, 4e4, 25e4, 500.0)
            gl.glUniform1i(self.ssd_shader.loc_nac, self.naircraft)
            if self.ssd_all:
                self.ssd.draw(first_vertex=0,
                              vertex_count=self.naircraft,
                              n_instances=self.naircraft)
            else:
                self.ssd.draw(first_vertex=self.ssd_ownship[-1],
                              vertex_count=1,
                              n_instances=self.naircraft)

        # Unbind everything
        RenderObject.unbind_all()
        gl.glUseProgram(0)

    def resizeGL(self, width, height):
        """Called upon window resizing: reinitialize the viewport."""
        if not self.initialized:
            return

        # update the window size
        # Qt5 supports getting the device pixel ratio, which can be > 1 for HiDPI displays such as Mac Retina screens
        pixel_ratio = 1
        if QT_VERSION >= 5:
            pixel_ratio = self.devicePixelRatio()

        # Calculate zoom so that the window resize doesn't affect the scale, but only enlarges or shrinks the view
        zoom = float(self.width) / float(width) * pixel_ratio
        origin = (width / 2, height / 2)

        # Update width, height, and aspect ratio
        self.width, self.height = width / pixel_ratio, height / pixel_ratio
        self.ar = float(width) / max(1, float(height))
        self.globaldata.set_win_width_height(self.width, self.height)

        self.viewport = (0, 0, width, height)

        # Update zoom
        self.event(PanZoomEvent(zoom=zoom, origin=origin))

    def update_route_data(self, data):
        self.route_acid = data.acid
        if data.acid != "" and len(data.wplat) > 0:
            nsegments = len(data.wplat)
            data.iactwp = min(max(0, data.iactwp), nsegments - 1)
            self.routelbl.n_instances = nsegments
            self.route.set_vertex_count(2 * nsegments)
            routedata = np.empty(4 * nsegments, dtype=np.float32)
            routedata[0:4] = [
                data.aclat, data.aclon, data.wplat[data.iactwp],
                data.wplon[data.iactwp]
            ]

            routedata[4::4] = data.wplat[:-1]
            routedata[5::4] = data.wplon[:-1]
            routedata[6::4] = data.wplat[1:]
            routedata[7::4] = data.wplon[1:]

            update_buffer(self.routebuf, routedata)
            update_buffer(self.routewplatbuf,
                          np.array(data.wplat, dtype=np.float32))
            update_buffer(self.routewplonbuf,
                          np.array(data.wplon, dtype=np.float32))
            wpname = []
            for wp in data.wpname:
                wpname += wp[:12].ljust(12)
            update_buffer(self.routelblbuf, np.array(wpname))
        else:
            self.route.set_vertex_count(0)

    def update_aircraft_data(self, data):
        self.naircraft = len(data.lat)
        if self.naircraft == 0:
            self.cpalines.set_vertex_count(0)
        else:
            # Update data in GPU buffers
            update_buffer(self.aclatbuf, np.array(data.lat, dtype=np.float32))
            update_buffer(self.aclonbuf, np.array(data.lon, dtype=np.float32))
            update_buffer(self.achdgbuf, np.array(data.trk, dtype=np.float32))
            update_buffer(self.acaltbuf, np.array(data.alt, dtype=np.float32))
            update_buffer(self.actasbuf, np.array(data.tas, dtype=np.float32))

            # CPA lines to indicate conflicts
            ncpalines = len(data.confcpalat)

            cpalines = np.zeros(4 * ncpalines, dtype=np.float32)
            self.cpalines.set_vertex_count(2 * ncpalines)

            # Labels and colors
            rawlabel = ''
            color = np.empty((self.naircraft, 3), dtype=np.uint8)
            for i in range(self.naircraft):
                if np.isnan(data.tas[i]):
                    print 'CAS NaN in %d: %s' % (i, data.id[i])
                    data.cas[i] = 0.0

                if np.isnan(data.alt[i]):
                    print 'ALT NaN in %d: %s' % (i, data.id[i])
                    data.alt[i] = 0.0

                # Make label: 3 lines of 8 characters per aircraft
                rawlabel += '%-8sFL%03d   %-8d' % (data.id[i][:8],
                                                   int(data.alt[i] / ft / 100),
                                                   int(data.cas[i] / kts))
                confindices = data.iconf[i]
                if len(confindices) > 0:
                    color[i, :] = amber
                    for confidx in confindices:
                        cpalines[4 * confidx:4 * confidx + 4] = [
                            data.lat[i], data.lon[i], data.confcpalat[confidx],
                            data.confcpalon[confidx]
                        ]
                else:
                    color[i, :] = green

            update_buffer(self.confcpabuf, cpalines)
            update_buffer(self.accolorbuf, color)
            update_buffer(self.aclblbuf, np.array(rawlabel, dtype=np.string_))

            # If there is a visible route, update the start position
            if self.route_acid != "":
                if self.route_acid in data.id:
                    idx = data.id.index(self.route_acid)
                    update_buffer(
                        self.routebuf,
                        np.array([data.lat[idx], data.lon[idx]],
                                 dtype=np.float32))

    def show_ssd(self, arg):
        if arg == 'ALL':
            self.ssd_all = True
        elif arg == 'OFF':
            self.ssd_all = False
            self.ssd_ownship = np.array([], dtype=np.uint16)
        else:
            if arg in self.ssd_ownship:
                self.ssd_ownship = np.delete(
                    self.ssd_ownship, np.argmax(self.ssd_ownship == arg))
            else:
                self.ssd_ownship = np.append(self.ssd_ownship, arg)

    def updatePolygon(self, name, data_in):
        nact = self.nodedata[manager.sender()[0]]
        if name in nact.polynames:
            # We're either updating a polygon, or deleting it. In both cases
            # we remove the current one.
            nact.polydata = np.delete(nact.polydata,
                                      range(*nact.polynames[name]))
            del nact.polynames[name]

        if data_in is not None:
            nact.polynames[name] = (len(nact.polydata), 2 * len(data_in))
            newbuf = np.empty(2 * len(data_in), dtype=np.float32)
            newbuf[0::4] = data_in[0::2]  # lat
            newbuf[1::4] = data_in[1::2]  # lon
            newbuf[2:-2:4] = data_in[2::2]  # lat
            newbuf[3:-3:4] = data_in[3::2]  # lon
            newbuf[-2:] = data_in[0:2]
            nact.polydata = np.append(nact.polydata, newbuf)
            update_buffer(self.allpolysbuf, nact.polydata)
            self.allpolys.set_vertex_count(len(nact.polydata) / 2)

    def previewpoly(self, shape_type, data_in=None):
        if shape_type is None:
            self.polyprev.set_vertex_count(0)
            return
        if shape_type in ['BOX', 'AREA']:
            # For a box (an area is a box) we need to add two additional corners
            data = np.zeros(8, dtype=np.float32)
            data[0:2] = data_in[0:2]
            data[2:4] = data_in[2], data_in[1]
            data[4:6] = data_in[2:4]
            data[6:8] = data_in[0], data_in[3]
        else:
            data = data_in
        update_buffer(self.polyprevbuf, data)
        self.polyprev.set_vertex_count(len(data) / 2)

    def airportsInRange(self):
        ll_range = max(1.5 / self.zoom, 1.0)
        indices = np.logical_and.reduce(
            (self.apt_ctrlat >= self.panlat - ll_range,
             self.apt_ctrlat <= self.panlat + ll_range,
             self.apt_ctrlon >= self.panlon - ll_range,
             self.apt_ctrlon <= self.panlon + ll_range))

        self.apt_inrange = self.apt_indices[indices]

    def pixelCoordsToGLxy(self, x, y):
        """Convert screen pixel coordinates to GL projection coordinates (x, y range -1 -- 1)
        """
        # GL coordinates (x, y range -1 -- 1)
        glx = (float(2.0 * x) / self.width - 1.0)
        gly = -(float(2.0 * y) / self.height - 1.0)
        return glx, gly

    def pixelCoordsToLatLon(self, x, y):
        """Convert screen pixel coordinates to lat/lon coordinates
        """
        glx, gly = self.pixelCoordsToGLxy(x, y)

        # glxy   = zoom * (latlon - pan)
        # latlon = pan + glxy / zoom
        lat = self.panlat + gly / (self.zoom * self.ar)
        lon = self.panlon + glx / (self.zoom * self.flat_earth)
        return lat, lon

    def event(self, event):
        if event.type() == PanZoomEventType:
            if event.pan is not None:
                # Absolute pan operation
                if event.absolute:
                    self.panlat = event.pan[0]
                    self.panlon = event.pan[1]
                # Relative pan operation
                else:
                    self.panlat += event.pan[0]
                    self.panlon += event.pan[1]

                # Don't pan further than the poles in y-direction
                self.panlat = min(
                    max(self.panlat, -90.0 + 1.0 / (self.zoom * self.ar)),
                    90.0 - 1.0 / (self.zoom * self.ar))

                # Update flat-earth factor and possibly zoom in case of very wide windows (> 2:1)
                self.flat_earth = np.cos(np.deg2rad(self.panlat))
                self.zoom = max(self.zoom, 1.0 / (180.0 * self.flat_earth))

            if event.zoom is not None:
                if event.absolute:
                    # Limit zoom extents in x-direction to [-180:180], and in y-direction to [-90:90]
                    self.zoom = max(
                        event.zoom,
                        1.0 / min(90.0 * self.ar, 180.0 * self.flat_earth))
                else:
                    prevzoom = self.zoom
                    glx, gly = self.pixelCoordsToGLxy(event.origin[0],
                                                      event.origin[1])
                    self.zoom *= event.zoom

                    # Limit zoom extents in x-direction to [-180:180], and in y-direction to [-90:90]
                    self.zoom = max(
                        self.zoom,
                        1.0 / min(90.0 * self.ar, 180.0 * self.flat_earth))

                    # Correct pan so that zoom actions are around the mouse position, not around 0, 0
                    # glxy / zoom1 - pan1 = glxy / zoom2 - pan2
                    # pan2 = pan1 + glxy (1/zoom2 - 1/zoom1)
                    self.panlon = self.panlon - glx * (
                        1.0 / self.zoom - 1.0 / prevzoom) / self.flat_earth
                    self.panlat = self.panlat - gly * (
                        1.0 / self.zoom - 1.0 / prevzoom) / self.ar

                # Don't pan further than the poles in y-direction
                self.panlat = min(
                    max(self.panlat, -90.0 + 1.0 / (self.zoom * self.ar)),
                    90.0 - 1.0 / (self.zoom * self.ar))

                # Update flat-earth factor
                self.flat_earth = np.cos(np.deg2rad(self.panlat))

            if self.zoom >= 1.0:
                self.airportsInRange()
            event.accept()

            # Check for necessity wrap-around in x-direction
            self.wraplon = -999.9
            self.wrapdir = 0
            if self.panlon + 1.0 / (self.zoom * self.flat_earth) < -180.0:
                # The left edge of the map has passed the right edge of the screen: we can just change the pan position
                self.panlon += 360.0
            elif self.panlon - 1.0 / (self.zoom * self.flat_earth) < -180.0:
                # The left edge of the map has passed the left edge of the screen: we need to wrap around to the left
                self.wraplon = float(
                    np.ceil(360.0 + self.panlon - 1.0 /
                            (self.zoom * self.flat_earth)))
                self.wrapdir = -1
            elif self.panlon - 1.0 / (self.zoom * self.flat_earth) > 180.0:
                # The right edge of the map has passed the left edge of the screen: we can just change the pan position
                self.panlon -= 360.0
            elif self.panlon + 1.0 / (self.zoom * self.flat_earth) > 180.0:
                # The right edge of the map has passed the right edge of the screen: we need to wrap around to the right
                self.wraplon = float(
                    np.floor(-360.0 + self.panlon + 1.0 /
                             (self.zoom * self.flat_earth)))
                self.wrapdir = 1

            self.globaldata.set_wrap(self.wraplon, self.wrapdir)

            # update pan and zoom on GPU for all shaders
            self.globaldata.set_pan_and_zoom(self.panlat, self.panlon,
                                             self.zoom)
            return True

        else:
            return super(RadarWidget, self).event(event)
Example #2
0
class BlipDriver(QGLWidget):
    def __init__(self, parent=None):
        super(BlipDriver, self).__init__(parent=parent)
        self.initialized = False
        self.width = 800
        self.height = 800
        self.ndviewport = (100, 200, 600, 600)
        self.mcpviewport = (0, 0, 800, 200)
        self.btn_state = [False] * 14
        self.resize(800, 800)
        self.setMouseTracking(True)
        self.drag_start = (0, 0)
        self.btn_pressed = None
        self.selValues = 5 * [
            0
        ]  # [0:courseLeftRight, 1:spd, 2:hdg, 3:alt, 4:vs]
        self.rate = 0.0
        self.remainder = 0.0
        self.updownpos = None

    def create_objects(self):
        self.mcp = RenderObject(gl.GL_TRIANGLES,
                                vertex=np.array(rect(-1, -1, 2, 2),
                                                dtype=np.float32))
        self.mcp_texture = load_texture('mcp737.png')
        self.btn_tex = load_texture('btn_led.png')

        self.lcd_tex = load_lcd_font()

        v_mcp_text = []
        for pos in [
                0.0, 0.03, 0.06, 0.31, 0.34, 0.37, 0.4, 0.43, 0.644, 0.674,
                0.704, 0.955, 0.985, 1.015, 1.045, 1.075, 1.22, 1.25, 1.28,
                1.31, 1.34, 1.69, 1.72, 1.75
        ]:
            v, t = Font.char(-0.886 + pos, 0.45, 0.03, 0.25)
            v_mcp_text += v

        self.mcp_text = RenderObject(gl.GL_TRIANGLES,
                                     vertex=np.array(v_mcp_text,
                                                     dtype=np.float32))
        self.lcd_charcoords = np.zeros(24 * 6, dtype=np.float32)
        self.lcdbuf = self.mcp_text.bind_attrib(1,
                                                1,
                                                self.lcd_charcoords,
                                                datatype=gl.GL_FLOAT)

        btn_leds = []
        for pos in [(-0.74, -0.75), (-0.645, -0.75), (-0.37, -0.75),
                    (-0.232, -0.75), (-0.09, -0.75), (0.105, -0.75),
                    (0.2, -0.75), (-0.37, 0.5), (-0.09, 0.5), (-0.09, -0.125),
                    (0.575, 0.34), (0.575, -0.34), (0.684, 0.34),
                    (0.684, -0.34)]:
            btn_leds += rect(pos[0], pos[1], 0.055, 0.075)
        btn_color = np.zeros(14 * 6 * 4, dtype=np.uint8)
        self.btn_leds = RenderObject(gl.GL_TRIANGLES,
                                     vertex=np.array(btn_leds,
                                                     dtype=np.float32),
                                     color=btn_color)

        # Button up/down indicator
        w, h, offset = 0.04, 0.16, 0.04
        triangles = np.array([
            -w, offset, 0.0, offset + h, w, offset, -w, -offset, 0.0,
            -offset - h, w, -offset
        ],
                             dtype=np.float32)
        col_triangles = np.array(6 * [255, 255, 255, 180], dtype=np.uint8)
        self.updown = RenderObject(gl.GL_TRIANGLES,
                                   vertex=triangles,
                                   color=col_triangles)

        # Use the same font as the radarwidget
        self.font = Font()
        self.font.create_font_array('../../data/graphics/font/')
        self.font.init_shader(self.text_shader)

        edge = np.zeros(120, dtype=np.float32)
        edge[0:120:2] = 1.4 * np.sin(np.radians(np.arange(-60, 60, 2)))
        edge[1:120:2] = 1.4 * np.cos(np.radians(np.arange(-60, 60, 2)))
        self.edge = RenderObject(gl.GL_LINE_STRIP, vertex=edge, color=white)

        arcs = []
        for i in range(1, 4):
            for angle in range(-60, 60, max(2, 6 - 2 * i)):
                arcs.append(float(i) * 0.35 * sin(radians(angle)))
                arcs.append(float(i) * 0.35 * cos(radians(angle)))
                if i == 4:
                    arcs.append(float(i) * 0.35 * sin(radians(angle + 2)))
                    arcs.append(float(i) * 0.35 * cos(radians(angle + 2)))
        arcs = np.array(arcs, dtype=np.float32)
        self.arcs = RenderObject(gl.GL_LINES, vertex=arcs, color=white)

        mask = []
        for angle in range(-60, 60, 2):
            mask.append(1.4 * sin(radians(angle)))
            mask.append(10.0)
            mask.append(1.4 * sin(radians(angle)))
            mask.append(1.4 * cos(radians(angle)))
        mask = np.array(mask, dtype=np.float32)
        self.mask = RenderObject(gl.GL_TRIANGLE_STRIP,
                                 vertex=mask,
                                 color=black)

        ticks = np.zeros(288, dtype=np.float32)
        for i in range(72):
            ticktop = 1.46 if i % 6 == 0 else (1.44 if i % 2 == 0 else 1.42)
            ticks[4 * i:4 * i + 2] = (1.4 * sin(radians(i * 5)),
                                      1.4 * cos(radians(i * 5)))
            ticks[4 * i + 2:4 * i + 4] = (ticktop * sin(radians(i * 5)),
                                          ticktop * cos(radians(i * 5)))
        self.ticks = RenderObject(gl.GL_LINES, vertex=ticks, color=white)

        ticklbls = np.zeros(24 * 36, dtype=np.float32)
        texcoords = np.zeros(36 * 36, dtype=np.float32)

        for i in range(36):
            if i % 3 == 0:
                w, h, y = 0.045, 0.09, 1.48
            else:
                w, h, y = 0.035, 0.07, 1.46
            tmp = [(-w, h + y), (-w, y), (0.0, h + y), (0.0, h + y), (-w, y),
                   (0.0, y), (0.0, h + y), (0.0, y), (w, h + y), (w, h + y),
                   (0.0, y), (w, y)]

            # numerics start at ASCII 48
            c1 = i / 10 + 48
            c2 = i % 10 + 48
            texcoords[36 * i:36 * i + 18] = [
                0, 0, c1, 0, 1, c1, 1, 0, c1, 1, 0, c1, 0, 1, c1, 1, 1, c1
            ]
            texcoords[36 * i + 18:36 * i + 36] = [
                0, 0, c2, 0, 1, c2, 1, 0, c2, 1, 0, c2, 0, 1, c2, 1, 1, c2
            ]
            angle = radians(10 * (36 - i))
            rot = np.array([[cos(angle), -sin(angle)],
                            [sin(angle), cos(angle)]])
            for j in range(12):
                ticklbls[24 * i + 2 * j:24 * i + 2 * j + 2] = rot.dot(tmp[j])

        self.ticklbls = RenderObject(gl.GL_TRIANGLES,
                                     vertex=ticklbls,
                                     color=white,
                                     texcoords=texcoords)

        vown = np.array([
            0.0, 0.0, 0.0, -0.12, 0.065, -0.03, -0.065, -0.03, 0.022, -0.1,
            -0.022, -0.1
        ],
                        dtype=np.float32)
        self.ownship = RenderObject(gl.GL_LINES, vertex=vown, color=yellow)

        self.spdlabel_text = self.font.prepare_text_string(
            'GS    TAS', 0.05, white, (-0.98, 1.6))
        self.spdlabel_val = self.font.prepare_text_string(
            '  000    000', 0.05, green, (-0.97, 1.6))

        self.initialized = True
        # Unbind everything
        RenderObject.unbind_all()

    def setAircraftID(self, ac_id):
        self.ac_id = ac_id
        self.setWindowTitle(ac_id)

    def update_aircraft_data(self, ownid, ownlat, ownlon, owntas, ownhdg,
                             n_aircraft):
        self.globaldata.set_owndata(ownid, ownlat, ownlon, ownhdg)
        self.n_aircraft = n_aircraft

    def initializeGL(self):
        """Initialize OpenGL, VBOs, upload data on the GPU, etc."""

        # First check for supported GL version
        gl_version = float(gl.glGetString(gl.GL_VERSION)[:3])
        if gl_version < 3.3:
            return

        # background color
        gl.glClearColor(0, 0, 0, 0)
        gl.glEnable(gl.GL_BLEND)
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)

        # self.mcp_data       = mcpUBO()
        self.globaldata = ndUBO()
        self.mcp_col_shader = BlueSkyProgram('mcp.vert', 'color.frag')
        self.mcp_tex_shader = BlueSkyProgram('mcp.vert', 'texture.frag')
        self.mcp_txt_shader = BlueSkyProgram('mcp_text.vert', 'mcp_text.frag')

        self.color_shader = BlueSkyProgram('normal.vert', 'color.frag')
        self.text_shader = BlueSkyProgram('text.vert', 'text.frag')
        self.text_shader.bind_uniform_buffer('global_data', self.globaldata)

        self.create_objects()
        self.update_lcd()

    def resizeGL(self, width, height):
        pixel_ratio = 1
        if QT_VERSION >= 5:
            pixel_ratio = self.devicePixelRatio()

        self.width, self.height = width / pixel_ratio, height / pixel_ratio
        hmcp = height / 10 * 2
        # paint ND within the largest possible rectangular area in the window
        wnd = hnd = min(width, height - hmcp)
        xnd = max(0, (width - wnd) / 2)
        ynd = hmcp + max(0, (height - hmcp - hnd) / 2)
        self.mcpviewport = (0, 0, width, hmcp)
        self.ndviewport = (xnd, ynd, wnd, hnd)

    def paintGL(self):
        """Paint the scene."""
        # pass if the framebuffer isn't complete yet or if not initialized
        if not (gl.glCheckFramebufferStatus(
                gl.GL_FRAMEBUFFER) == gl.GL_FRAMEBUFFER_COMPLETE
                and self.isVisible() and self.initialized):
            return

        gl.glClearColor(0, 0, 0, 0)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
        # Set the viewport and clear the framebuffer

        # --- Draw the ND in its viewport ----
        gl.glViewport(*self.ndviewport)
        self.color_shader.use()
        self.arcs.draw()

        self.ownship.draw()
        self.mask.draw()
        self.edge.draw()

        self.ticks.draw()

        # Select the text shader
        self.text_shader.use()
        self.font.use()
        self.font.set_block_size((0, 0))
        self.globaldata.set_vertex_modifiers(VERTEX_IS_GLXY, True)
        self.ticklbls.draw()

        self.globaldata.set_vertex_modifiers(VERTEX_IS_GLXY, False)
        self.spdlabel_text.draw()
        self.spdlabel_val.draw()

        # --- Draw the MCP in its viewport ---
        gl.glViewport(*self.mcpviewport)
        self.mcp_tex_shader.use()
        gl.glActiveTexture(gl.GL_TEXTURE0 + 0)
        gl.glBindTexture(gl.GL_TEXTURE_2D, self.mcp_texture)
        self.mcp.draw()

        gl.glBindTexture(gl.GL_TEXTURE_2D, self.btn_tex)
        self.btn_leds.draw()

        if self.updownpos is not None:
            self.mcp_col_shader.use()
            gl.glVertexAttrib2f(2, *self.updownpos)
            self.updown.draw()
            gl.glVertexAttrib2f(2, 0.0, 0.0)

        self.mcp_txt_shader.use()
        gl.glActiveTexture(gl.GL_TEXTURE0 + 0)
        gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, self.lcd_tex)
        self.mcp_text.draw()

        # Unbind everything
        RenderObject.unbind_all()
        gl.glUseProgram(0)

    def update_lcd(self):
        # [0:courseLeftRight, 1:spd, 2:hdg, 3:alt, 4:vs]
        val = self.selValues
        chars = '%03d' % (int(val[0]) % 360)
        chars += '% 5d' % max(0, val[1])
        chars += '%03d' % (int(val[2]) % 360)
        chars += '% 5d' % min(99999, max(0, val[3]))
        chars += '     ' if val[4] == 0 else '% 5d' % min(
            9999, max(-9999, val[4]))
        chars += '%03d' % (int(val[0]) % 360)

        indices = []
        for c in chars:
            if c == ' ':
                indices += [0] * 6
            elif c == '-':
                indices += [1] * 6
            elif c == '.':
                indices += [2] * 6
            else:
                indices += [int(c) + 3] * 6

        update_buffer(self.lcdbuf, np.array(indices, dtype=np.float32))

    def event(self, event):
        if not self.initialized:
            return super(BlipDriver, self).event(event)

        self.makeCurrent()
        if event.type() in [
                QEvent.MouseMove, QEvent.MouseButtonPress,
                QEvent.MouseButtonRelease
        ]:
            px = 2.0 * (event.x()) / self.width - 1.0
            py = 10.0 * (self.height - event.y()) / self.height - 1.0

        if event.type(
        ) == QEvent.MouseButtonPress and event.button() & Qt.LeftButton:
            self.btn_pressed, _ = check_knob(px, py)
            if self.btn_pressed is not None:
                self.drag_start = event.x(), event.y()
                QTimer.singleShot(100, self.updateAPValues)

        elif event.type(
        ) == QEvent.MouseButtonRelease and event.button() & Qt.LeftButton:
            # print px, py
            self.btn_pressed = None
            col_triangles = 6 * [255, 255, 255, 180]
            update_buffer(self.updown.colorbuf,
                          np.array(col_triangles, dtype=np.uint8))
            # MCP button clicked?
            name, idx = check_btn(px, py)
            if name is not None:
                self.btn_state[idx] = not self.btn_state[idx]

            btn_color = []
            for b in self.btn_state:
                btn_color += 24 * [255] if b else 24 * [0]
            update_buffer(self.btn_leds.colorbuf,
                          np.array(btn_color, dtype=np.uint8))
        elif event.type() == QEvent.MouseMove:
            if event.buttons(
            ) & Qt.LeftButton and self.btn_pressed is not None:
                self.rate = float(self.drag_start[1] - event.y()) / self.height
                if self.rate > 1e-2:
                    col_triangles = 3 * [255, 140, 0, 255
                                         ] + 3 * [255, 255, 255, 180]
                elif self.rate < 1e-2:
                    col_triangles = 3 * [255, 255, 255, 180
                                         ] + 3 * [255, 140, 0, 255]
                else:
                    col_triangles = 6 * [255, 255, 255, 180]
                update_buffer(self.updown.colorbuf,
                              np.array(col_triangles, dtype=np.uint8))
                return True
            # Mouse-over for knobs
            name, self.updownpos = check_knob(px, py)

            # ismcp = float(self.height - event.y()) / self.height <= 0.2

        return super(BlipDriver, self).event(event)

    @pyqtSlot()
    def updateAPValues(self):
        if self.btn_pressed == 'COURSE':
            val = self.remainder + 15 * self.rate
            self.selValues[0] += int(val)
            self.remainder = val - int(val)

        if self.btn_pressed == 'SPD':
            val = self.remainder + 20 * self.rate
            self.selValues[1] += int(val)
            self.remainder = val - int(val)

        if self.btn_pressed == 'HDG':
            val = self.remainder + 15 * self.rate
            self.selValues[2] += int(val)
            self.remainder = val - int(val)

        if self.btn_pressed == 'ALT':
            if abs(self.rate) > 0.06:
                increment = 1000
                val = self.remainder + min(2.0,
                                           abs(self.rate) / 0.04) * np.sign(
                                               self.rate)
            elif abs(self.rate) > 0.02:
                increment = 100
                val = self.remainder + self.rate / 0.04
            else:
                increment = 10
                val = self.remainder + self.rate / 0.02

            self.selValues[3] += int(val) * increment
            self.remainder = val - int(val)

        if self.btn_pressed == 'VS':
            if abs(self.rate) > 0.2:
                increment = 1000
                val = self.remainder + min(2.0,
                                           abs(self.rate) / 0.04) * np.sign(
                                               self.rate)
            elif abs(self.rate) > 0.05:
                increment = 100
                val = self.remainder + self.rate / 0.04
            else:
                increment = 10
                val = self.remainder + self.rate / 0.02

            self.selValues[4] += int(val) * increment
            self.remainder = val - int(val)

        self.update_lcd()
        if self.btn_pressed is not None:
            QTimer.singleShot(200, self.updateAPValues)
Example #3
0
class RadarWidget(QGLWidget):
    vcount_circle = 36
    width = height = 600
    viewport = (0, 0, width, height)
    panlat = 0.0
    panlon = 0.0
    zoom = 1.0
    ar = 1.0
    flat_earth = 1.0
    wraplon = int(-999)
    wrapdir = int(0)
    max_texture_size = 0

    do_text = True
    invalid_count = 0

    def __init__(self, navdb, shareWidget=None):
        super(RadarWidget, self).__init__(shareWidget=shareWidget)
        self.setAttribute(Qt.WA_AcceptTouchEvents, True)
        self.grabGesture(Qt.PanGesture)
        self.grabGesture(Qt.PinchGesture)
        # self.grabGesture(Qt.SwipeGesture)

        # The number of aircraft in the simulation
        self.map_texture    = 0
        self.naircraft      = 0
        self.nwaypoints     = 0
        self.nairports      = 0
        self.route_acid     = ""
        self.ssd_ownship    = np.array([], dtype=np.uint16)
        self.apt_inrange    = np.array([])
        self.ssd_all        = False
        self.navdb          = navdb
        self.iactconn       = 0
        self.nodedata       = list()

        # Display flags
        self.show_map       = True
        self.show_coast     = True
        self.show_traf      = True
        self.show_pz        = False
        self.show_lbl       = True
        self.show_wpt       = True
        self.show_apt       = True

        self.initialized    = False

        # Connect to manager's nodelist changed and activenode changed signal
        manager.instance.nodes_changed.connect(self.nodesChanged)
        manager.instance.activenode_changed.connect(self.actnodeChanged)

        # Load vertex data
        self.vbuf_asphalt, self.vbuf_concrete, self.vbuf_runways, self.vbuf_rwythr, \
            self.apt_ctrlat, self.apt_ctrlon, self.apt_indices = load_aptsurface()

    @pyqtSlot(str, int)
    def nodesChanged(self, address, nodeid, connidx):
        # For each node we have to keep data such as the visible polygons, etc.
        self.nodedata.append(nodeData())

    @pyqtSlot(int)
    def actnodeChanged(self, nodeid, connidx):
        self.iactconn = connidx
        nact = self.nodedata[connidx]
        if len(nact.polydata) > 0:
            update_buffer(self.allpolysbuf, nact.polydata)
        self.allpolys.set_vertex_count(len(nact.polydata) / 2)

    def create_objects(self):
        if not self.isValid():
            self.invalid_count += 1
            print 'Radarwidget: Context not valid in create_objects, count=%d' % self.invalid_count
            QTimer.singleShot(100, self.create_objects)
            return

        # Make the radarwidget context current, necessary when create_objects is not called from initializeGL
        self.makeCurrent()

        # Initialize font for radar view with specified settings
        self.font = Font()
        self.font.create_font_array(char_height=text_texture_size, font_family=font_family, font_weight=font_weight)
        self.font.init_shader(self.text_shader)

        # Load and bind world texture
        max_texture_size = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE)
        print 'Maximum supported texture size: %d' % max_texture_size
        for i in [16384, 8192, 4096]:
            if max_texture_size >= i:
                fname = 'data/graphics/world.%dx%d.dds' % (i, i / 2)
                print 'Loading texture ' + fname
                self.map_texture = self.bindTexture(fname)
                break

        # Create initial empty buffers for aircraft position, orientation, label, and color
        self.achdgbuf      = create_empty_buffer(MAX_NAIRCRAFT * 4, usage=gl.GL_STREAM_DRAW)
        self.aclatbuf      = create_empty_buffer(MAX_NAIRCRAFT * 4, usage=gl.GL_STREAM_DRAW)
        self.aclonbuf      = create_empty_buffer(MAX_NAIRCRAFT * 4, usage=gl.GL_STREAM_DRAW)
        self.acaltbuf      = create_empty_buffer(MAX_NAIRCRAFT * 4, usage=gl.GL_STREAM_DRAW)
        self.actasbuf      = create_empty_buffer(MAX_NAIRCRAFT * 4, usage=gl.GL_STREAM_DRAW)
        self.accolorbuf    = create_empty_buffer(MAX_NAIRCRAFT * 3, usage=gl.GL_STREAM_DRAW)
        self.aclblbuf      = create_empty_buffer(MAX_NAIRCRAFT * 24, usage=gl.GL_STREAM_DRAW)
        self.confcpabuf    = create_empty_buffer(MAX_NCONFLICTS * 16, usage=gl.GL_STREAM_DRAW)
        self.polyprevbuf   = create_empty_buffer(MAX_POLYPREV_SEGMENTS * 8, usage=gl.GL_DYNAMIC_DRAW)
        self.allpolysbuf   = create_empty_buffer(MAX_ALLPOLYS_SEGMENTS * 16, usage=gl.GL_DYNAMIC_DRAW)
        self.routebuf      = create_empty_buffer(MAX_ROUTE_LENGTH * 8, usage=gl.GL_DYNAMIC_DRAW)
        self.routewplatbuf = create_empty_buffer(MAX_ROUTE_LENGTH * 4, usage=gl.GL_DYNAMIC_DRAW)
        self.routewplonbuf = create_empty_buffer(MAX_ROUTE_LENGTH * 4, usage=gl.GL_DYNAMIC_DRAW)
        self.routelblbuf   = create_empty_buffer(MAX_ROUTE_LENGTH * 12, usage=gl.GL_DYNAMIC_DRAW)

        # ------- Map ------------------------------------
        self.map = RenderObject(gl.GL_TRIANGLE_FAN, vertex_count=4)
        mapvertices = np.array([(-90.0, 540.0), (-90.0, -540.0), (90.0, -540.0), (90.0, 540.0)], dtype=np.float32)
        texcoords = np.array([(1, 3), (1, 0), (0, 0), (0, 3)], dtype=np.float32)
        self.map.bind_attrib(ATTRIB_VERTEX, 2, mapvertices)
        self.map.bind_attrib(ATTRIB_TEXCOORDS, 2, texcoords)

        # ------- Coastlines -----------------------------
        self.coastlines = RenderObject(gl.GL_LINES)
        coastvertices, coastindices = load_coastlines()
        self.coastlines.bind_attrib(ATTRIB_VERTEX, 2, coastvertices)
        self.coastlines.bind_attrib(ATTRIB_COLOR, 3, np.array(lightblue2, dtype=np.uint8), datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)
        self.vcount_coast = len(coastvertices)
        self.coastindices = coastindices
        del coastvertices

        # ------- Runways --------------------------------
        self.runways = RenderObject(gl.GL_TRIANGLES)
        self.runways.bind_attrib(ATTRIB_VERTEX, 2, self.vbuf_runways)
        self.runways.bind_attrib(ATTRIB_COLOR, 3, np.array(grey, dtype=np.uint8), datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)
        self.runways.set_vertex_count(len(self.vbuf_runways) / 2)

        #---------Runway Thresholds-----------------------
        self.thresholds = RenderObject(gl.GL_TRIANGLES)
        self.thresholds.bind_attrib(ATTRIB_VERTEX, 2, self.vbuf_rwythr)
        self.thresholds.bind_attrib(ATTRIB_COLOR, 3, np.array(white, dtype=np.uint8), datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)
        self.thresholds.set_vertex_count(len(self.vbuf_rwythr) / 2)

        # ------- Taxiways -------------------------------
        self.taxiways = RenderObject(gl.GL_TRIANGLES)
        self.taxiways.bind_attrib(ATTRIB_VERTEX, 2, self.vbuf_asphalt)
        self.taxiways.bind_attrib(ATTRIB_COLOR, 3, np.array(grey, dtype=np.uint8), datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)
        self.taxiways.set_vertex_count(len(self.vbuf_asphalt) / 2)

        # ------- Pavement -------------------------------
        self.pavement = RenderObject(gl.GL_TRIANGLES)
        self.pavement.bind_attrib(ATTRIB_VERTEX, 2, self.vbuf_concrete)
        self.pavement.bind_attrib(ATTRIB_COLOR, 3, np.array(lightgrey, dtype=np.uint8), datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)
        self.pavement.set_vertex_count(len(self.vbuf_concrete) / 2)

        # Polygon preview object
        self.polyprev = RenderObject(gl.GL_LINE_LOOP)
        self.polyprev.bind_attrib(ATTRIB_VERTEX, 2, self.polyprevbuf)
        self.polyprev.bind_attrib(ATTRIB_COLOR, 3, np.array(lightblue, dtype=np.uint8), datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)

        # Fixed polygons
        self.allpolys = RenderObject(gl.GL_LINES)
        self.allpolys.bind_attrib(ATTRIB_VERTEX, 2, self.allpolysbuf)
        self.allpolys.bind_attrib(ATTRIB_COLOR, 3, np.array(blue, dtype=np.uint8), datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)

        # ------- SSD object -----------------------------
        self.ssd = RenderObject(gl.GL_POINTS)
        self.ssd.bind_attrib(ATTRIB_LAT0, 1, self.aclatbuf)
        self.ssd.bind_attrib(ATTRIB_LON0, 1, self.aclonbuf)
        self.ssd.bind_attrib(ATTRIB_ALT0, 1, self.acaltbuf)
        self.ssd.bind_attrib(ATTRIB_TAS0, 1, self.actasbuf)
        self.ssd.bind_attrib(ATTRIB_TRK0, 1, self.achdgbuf)
        self.ssd.bind_attrib(ATTRIB_LAT1, 1, self.aclatbuf, instance_divisor=1)
        self.ssd.bind_attrib(ATTRIB_LON1, 1, self.aclonbuf, instance_divisor=1)
        self.ssd.bind_attrib(ATTRIB_ALT1, 1, self.acaltbuf, instance_divisor=1)
        self.ssd.bind_attrib(ATTRIB_TAS1, 1, self.actasbuf, instance_divisor=1)
        self.ssd.bind_attrib(ATTRIB_TRK1, 1, self.achdgbuf, instance_divisor=1)

        # ------- Circle ---------------------------------
        # Create a new VAO (Vertex Array Object) and bind it
        self.protectedzone = RenderObject(gl.GL_LINE_LOOP, vertex_count=self.vcount_circle)
        circlevertices = np.transpose(np.array((5.0 * nm * np.cos(np.linspace(0.0, 2.0 * np.pi, self.vcount_circle)), 5.0 * nm * np.sin(np.linspace(0.0, 2.0 * np.pi, self.vcount_circle))), dtype=np.float32))
        self.protectedzone.bind_attrib(ATTRIB_VERTEX, 2, circlevertices)
        self.protectedzone.bind_attrib(ATTRIB_LAT, 1, self.aclatbuf, instance_divisor=1)
        self.protectedzone.bind_attrib(ATTRIB_LON, 1, self.aclonbuf, instance_divisor=1)
        self.protectedzone.bind_attrib(ATTRIB_COLOR, 3, self.accolorbuf, datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)

        # ------- A/C symbol -----------------------------
        self.ac_symbol = RenderObject(gl.GL_TRIANGLE_FAN, vertex_count=4)
        acvertices = np.array([(0.0, 0.5 * ac_size), (-0.5 * ac_size, -0.5 * ac_size), (0.0, -0.25 * ac_size), (0.5 * ac_size, -0.5 * ac_size)], dtype=np.float32)
        self.ac_symbol.bind_attrib(ATTRIB_VERTEX, 2, acvertices)
        self.ac_symbol.bind_attrib(ATTRIB_LAT, 1, self.aclatbuf, instance_divisor=1)
        self.ac_symbol.bind_attrib(ATTRIB_LON, 1, self.aclonbuf, instance_divisor=1)
        self.ac_symbol.bind_attrib(ATTRIB_ORIENTATION, 1, self.achdgbuf, instance_divisor=1)
        self.ac_symbol.bind_attrib(ATTRIB_COLOR, 3, self.accolorbuf, datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)
        self.aclabels = self.font.prepare_text_instanced(self.aclblbuf, (8, 3), self.aclatbuf, self.aclonbuf, self.accolorbuf, char_size=text_size, vertex_offset=(ac_size, -0.5 * ac_size))

        # ------- Conflict CPA lines ---------------------
        self.cpalines = RenderObject(gl.GL_LINES)
        self.cpalines.bind_attrib(ATTRIB_VERTEX, 2, self.confcpabuf)
        self.cpalines.bind_attrib(ATTRIB_COLOR, 3, np.array(amber, dtype=np.uint8), datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)

        # ------- Aircraft Route -------------------------
        self.route = RenderObject(gl.GL_LINES)
        self.route.bind_attrib(ATTRIB_VERTEX, 2, self.routebuf)
        self.route.bind_attrib(ATTRIB_COLOR, 3, np.array(magenta, dtype=np.uint8), datatype=gl.GL_UNSIGNED_BYTE, normalize=True, instance_divisor=1)
        self.routelbl = self.font.prepare_text_instanced(self.routelblbuf, (12, 1), self.routewplatbuf, self.routewplonbuf, char_size=text_size, vertex_offset=(wpt_size, 0.5 * wpt_size))

        # ------- Waypoints ------------------------------
        self.nwaypoints = len(self.navdb.wplat)
        self.waypoints = RenderObject(gl.GL_LINE_LOOP, vertex_count=3, n_instances=self.nwaypoints)
        wptvertices = np.array([(0.0, 0.5 * wpt_size), (-0.5 * wpt_size, -0.5 * wpt_size), (0.5 * wpt_size, -0.5 * wpt_size)], dtype=np.float32)  # a triangle
        self.waypoints.bind_attrib(ATTRIB_VERTEX, 2, wptvertices)

        # Sort based on id string length
        llid = sorted(zip(self.navdb.wpid, self.navdb.wplat, self.navdb.wplon), key=lambda i: len(i[0]) > 3)
        wplat = [lat for (wpid, lat, lon) in llid]
        wplon = [lon for (wpid, lon, lon) in llid]
        self.wptlatbuf = self.waypoints.bind_attrib(ATTRIB_LAT, 1, np.array(wplat, dtype=np.float32), instance_divisor=1)
        self.wptlonbuf = self.waypoints.bind_attrib(ATTRIB_LON, 1, np.array(wplon, dtype=np.float32), instance_divisor=1)
        wptids = ''
        self.nnavaids = 0
        for wptid in llid:
            if len(wptid[0]) <= 3:
                self.nnavaids += 1
            wptids += wptid[0].ljust(5)
        self.wptlabels = self.font.prepare_text_instanced(np.array(wptids, dtype=np.string_), (5, 1), self.wptlatbuf, self.wptlonbuf, char_size=text_size, vertex_offset=(wpt_size, 0.5 * wpt_size))
        del wptids

        # ------- Airports -------------------------------
        self.nairports = len(self.navdb.aplat)
        self.airports = RenderObject(gl.GL_LINE_LOOP, vertex_count=4, n_instances=self.nairports)
        aptvertices = np.array([(-0.5 * apt_size, -0.5 * apt_size), (0.5 * apt_size, -0.5 * apt_size), (0.5 * apt_size, 0.5 * apt_size), (-0.5 * apt_size, 0.5 * apt_size)], dtype=np.float32)  # a square
        self.airports.bind_attrib(ATTRIB_VERTEX, 2, aptvertices)
        indices = self.navdb.aptype.argsort()
        aplat   = np.array(self.navdb.aplat[indices], dtype=np.float32)
        aplon   = np.array(self.navdb.aplon[indices], dtype=np.float32)
        aptypes = self.navdb.aptype[indices]
        apnames = np.array(self.navdb.apid)
        apnames = apnames[indices]
        # The number of large, large+med, and large+med+small airports
        self.nairports = [aptypes.searchsorted(2), aptypes.searchsorted(3), self.nairports]

        self.aptlatbuf = self.airports.bind_attrib(ATTRIB_LAT, 1, aplat, instance_divisor=1)
        self.aptlonbuf = self.airports.bind_attrib(ATTRIB_LON, 1, aplon, instance_divisor=1)
        aptids = ''
        for aptid in apnames:
            aptids += aptid.ljust(4)
        self.aptlabels = self.font.prepare_text_instanced(np.array(aptids, dtype=np.string_), (4, 1), self.aptlatbuf, self.aptlonbuf, char_size=text_size, vertex_offset=(apt_size, 0.5 * apt_size))
        del aptids

        # Unbind VAO, VBO
        RenderObject.unbind_all()

        # Set initial values for the global uniforms
        self.globaldata.set_wrap(self.wraplon, self.wrapdir)
        self.globaldata.set_pan_and_zoom(self.panlat, self.panlon, self.zoom)

        # Clean up memory
        del self.vbuf_asphalt, self.vbuf_concrete, self.vbuf_runways, self.vbuf_rwythr

        self.initialized = True

    def initializeGL(self):
        """Initialize OpenGL, VBOs, upload data on the GPU, etc."""

        # First check for supported GL version
        gl_version = float(gl.glGetString(gl.GL_VERSION)[:3])
        if gl_version < 3.3:
            print('OpenGL context created with GL version %.1f' % gl_version)
            qCritical("""Your system reports that it supports OpenGL up to version %.1f. The minimum requirement for BlueSky is OpenGL 3.3.
                Generally, AMD/ATI/nVidia cards from 2008 and newer support OpenGL 3.3, and Intel integrated graphics from the Haswell
                generation and newer. If you think your graphics system should be able to support GL>=3.3 please open an issue report
                on the BlueSky Github page (https://github.com/ProfHoekstra/bluesky/issues)""" % gl_version)
            return

        # background color
        gl.glClearColor(0, 0, 0, 0)
        gl.glEnable(gl.GL_BLEND)
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)

        self.globaldata = radarUBO()

        try:
            # Compile shaders and link color shader program
            self.color_shader = BlueSkyProgram('data/graphics/shaders/radarwidget-normal.vert', 'data/graphics/shaders/radarwidget-color.frag')
            self.color_shader.bind_uniform_buffer('global_data', self.globaldata)

            # Compile shaders and link texture shader program
            self.texture_shader = BlueSkyProgram('data/graphics/shaders/radarwidget-normal.vert', 'data/graphics/shaders/radarwidget-texture.frag')
            self.texture_shader.bind_uniform_buffer('global_data', self.globaldata)

            # Compile shaders and link text shader program
            self.text_shader = BlueSkyProgram('data/graphics/shaders/radarwidget-text.vert', 'data/graphics/shaders/radarwidget-text.frag')
            self.text_shader.bind_uniform_buffer('global_data', self.globaldata)

            self.ssd_shader = BlueSkyProgram('data/graphics/shaders/ssd.vert', 'data/graphics/shaders/ssd.frag', 'data/graphics/shaders/ssd.geom')
            self.ssd_shader.bind_uniform_buffer('global_data', self.globaldata)
            self.ssd_shader.loc_vlimits = gl.glGetUniformLocation(self.ssd_shader.program, 'Vlimits')

        except RuntimeError as e:
            qCritical('Error compiling shaders in radarwidget: ' + e.args[0])
            return

        # create all vertex array objects
        self.create_objects()

    def paintGL(self):
        """Paint the scene."""
        # pass if the framebuffer isn't complete yet or if not initialized
        if not (gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) == gl.GL_FRAMEBUFFER_COMPLETE and self.initialized and self.isVisible()):
            return

        # Set the viewport and clear the framebuffer
        gl.glViewport(*self.viewport)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT)

        # Send the (possibly) updated global uniforms to the buffer
        self.globaldata.set_vertex_scale_type(VERTEX_IS_LATLON)

        # --- DRAW THE MAP AND COASTLINES ---------------------------------------------
        # Map and coastlines: don't wrap around in the shader
        self.globaldata.enable_wrap(False)

        if self.show_map:
            # Select the texture shader
            self.texture_shader.use()

            # Draw map texture
            gl.glActiveTexture(gl.GL_TEXTURE0 + 0)
            gl.glBindTexture(gl.GL_TEXTURE_2D, self.map_texture)
            self.map.draw()

        # Select the non-textured shader
        self.color_shader.use()

        # Draw coastlines
        if self.show_coast:
            if self.wrapdir == 0:
                # Normal case, no wrap around
                self.coastlines.draw(first_vertex=0, vertex_count=self.vcount_coast)
            else:
                self.coastlines.bind()
                wrapindex = np.uint32(self.coastindices[int(self.wraplon) + 180])
                if self.wrapdir == 1:
                    gl.glVertexAttrib1f(ATTRIB_LON, 360.0)
                    self.coastlines.draw(first_vertex=0, vertex_count=wrapindex)
                    gl.glVertexAttrib1f(ATTRIB_LON, 0.0)
                    self.coastlines.draw(first_vertex=wrapindex, vertex_count=self.vcount_coast - wrapindex)
                else:
                    gl.glVertexAttrib1f(ATTRIB_LON, -360.0)
                    self.coastlines.draw(first_vertex=wrapindex, vertex_count=self.vcount_coast - wrapindex)
                    gl.glVertexAttrib1f(ATTRIB_LON, 0.0)
                    self.coastlines.draw(first_vertex=0, vertex_count=wrapindex)

        # --- DRAW PREVIEW SHAPE (WHEN AVAILABLE) -----------------------------
        self.polyprev.draw()

        # --- DRAW CUSTOM SHAPES (WHEN AVAILABLE) -----------------------------
        self.allpolys.draw()

        # --- DRAW THE SELECTED AIRCRAFT ROUTE (WHEN AVAILABLE) ---------------
        if self.show_traf:
            self.route.draw()
            self.cpalines.draw()

        # --- DRAW AIRPORT DETAILS (RUNWAYS, TAXIWAYS, PAVEMENTS) -------------
        self.runways.draw()
        self.thresholds.draw()
        if self.zoom >= 1.0:
            for idx in self.apt_inrange:
                self.taxiways.draw(first_vertex=idx[0], vertex_count=idx[1])
                self.pavement.draw(first_vertex=idx[2], vertex_count=idx[3])

        # --- DRAW THE INSTANCED AIRCRAFT SHAPES ------------------------------
        # update wrap longitude and direction for the instanced objects
        self.globaldata.enable_wrap(True)

        # PZ circles only when they are bigger than the A/C symbols
        if self.naircraft > 0 and self.show_traf and self.show_pz and self.zoom >= 0.15:
            self.globaldata.set_vertex_scale_type(VERTEX_IS_METERS)
            self.protectedzone.draw(n_instances=self.naircraft)

        self.globaldata.set_vertex_scale_type(VERTEX_IS_SCREEN)

        # Draw traffic symbols
        if self.naircraft > 0 and self.show_traf:
            self.ac_symbol.draw(n_instances=self.naircraft)

        if self.zoom >= 0.5:
            nairports = self.nairports[2]
        elif self.zoom  >= 0.25:
            nairports = self.nairports[1]
        else:
            nairports = self.nairports[0]

        if self.zoom >= 3:
            nwaypoints = self.nwaypoints
        else:
            nwaypoints = self.nnavaids

        # Draw waypoint symbols
        if self.show_wpt:
            self.waypoints.bind()
            gl.glVertexAttrib4Nub(ATTRIB_COLOR, *(lightblue3 + (255,)))
            self.waypoints.draw(n_instances=nwaypoints)

        # Draw airport symbols
        if self.show_apt:
            self.airports.bind()
            gl.glVertexAttrib4Nub(ATTRIB_COLOR, *(lightblue3 + (255,)))
            self.airports.draw(n_instances=nairports)

        if self.do_text:
            self.text_shader.use()
            self.font.use()

            if self.show_apt:
                self.font.set_char_size(self.aptlabels.char_size)
                self.font.set_block_size(self.aptlabels.block_size)
                self.aptlabels.bind()
                gl.glVertexAttrib4Nub(ATTRIB_COLOR, *(lightblue4 + (255,)))
                self.aptlabels.draw(n_instances=nairports)
            if self.show_wpt:
                self.font.set_char_size(self.wptlabels.char_size)
                self.font.set_block_size(self.wptlabels.block_size)
                self.wptlabels.bind()
                gl.glVertexAttrib4Nub(ATTRIB_COLOR, *(lightblue4 + (255,)))
                self.wptlabels.draw(n_instances=nwaypoints)

            if self.show_traf and self.route.vertex_count > 1:
                gl.glVertexAttrib4Nub(ATTRIB_COLOR, *(magenta + (255,)))
                self.font.set_char_size(self.routelbl.char_size)
                self.font.set_block_size(self.routelbl.block_size)
                self.routelbl.draw()

            if self.naircraft > 0 and self.show_traf and self.show_lbl:
                self.font.set_char_size(self.aclabels.char_size)
                self.font.set_block_size(self.aclabels.block_size)
                self.aclabels.draw(n_instances=self.naircraft)

        # SSD
        if self.ssd_all or len(self.ssd_ownship) > 0:
            self.ssd_shader.use()
            gl.glUniform3f(self.ssd_shader.loc_vlimits, 1e4, 4e4, 200.0)
            if self.ssd_all:
                self.ssd.draw(first_vertex=0, vertex_count=self.naircraft, n_instances=self.naircraft)
            else:
                self.ssd.draw(first_vertex=self.ssd_ownship[-1], vertex_count=1, n_instances=self.naircraft)

        # Unbind everything
        RenderObject.unbind_all()
        gl.glUseProgram(0)

    def resizeGL(self, width, height):
        """Called upon window resizing: reinitialize the viewport."""
        if not self.initialized:
            return

        # update the window size
        # Qt5 supports getting the device pixel ratio, which can be > 1 for HiDPI displays such as Mac Retina screens
        pixel_ratio = 1
        if QT_VERSION >= 5:
            pixel_ratio = self.devicePixelRatio()

        # Calculate zoom so that the window resize doesn't affect the scale, but only enlarges or shrinks the view
        zoom   = float(self.width) / float(width) * pixel_ratio
        origin = (width / 2, height / 2)

        # Update width, height, and aspect ratio
        self.width, self.height = width / pixel_ratio, height / pixel_ratio
        self.ar = float(width) / max(1, float(height))
        self.globaldata.set_win_width_height(self.width, self.height)

        self.viewport = (0, 0, width, height)

        # Update zoom
        self.event(PanZoomEvent(zoom=zoom, origin=origin))

    def update_route_data(self, data):
        self.route_acid = data.acid
        if data.acid != "" and len(data.lat) > 0:
            nsegments = len(data.lat)
            data.iactwp = max(0, data.iactwp)
            self.routelbl.n_instances = nsegments
            self.route.set_vertex_count(2 * nsegments)
            routedata = np.empty(4 * nsegments, dtype=np.float32)
            routedata[0:4] = [data.aclat, data.aclon,
                data.lat[data.iactwp], data.lon[data.iactwp]]

            routedata[4::4] = data.lat[:-1]
            routedata[5::4] = data.lon[:-1]
            routedata[6::4] = data.lat[1:]
            routedata[7::4] = data.lon[1:]

            update_buffer(self.routebuf, routedata)
            update_buffer(self.routewplatbuf, np.array(data.lat, dtype=np.float32))
            update_buffer(self.routewplonbuf, np.array(data.lon, dtype=np.float32))
            wptlabels = []
            for wp in data.wptlabels:
                wptlabels += wp[:12].ljust(12)
            update_buffer(self.routelblbuf, np.array(wptlabels))
        else:
            self.route.set_vertex_count(0)

    def update_aircraft_data(self, data):
        self.naircraft = len(data.lat)
        if self.naircraft == 0:
            self.cpalines.set_vertex_count(0)
        else:
            # Update data in GPU buffers
            update_buffer(self.aclatbuf, np.array(data.lat, dtype=np.float32))
            update_buffer(self.aclonbuf, np.array(data.lon, dtype=np.float32))
            update_buffer(self.achdgbuf, np.array(data.trk, dtype=np.float32))
            update_buffer(self.acaltbuf, np.array(data.alt, dtype=np.float32))
            update_buffer(self.actasbuf, np.array(data.tas, dtype=np.float32))

            # CPA lines to indicate conflicts
            ncpalines = len(data.confcpalat)

            cpalines  = np.zeros(4 * ncpalines, dtype=np.float32)
            self.cpalines.set_vertex_count(2 * ncpalines)

            # Labels and colors
            rawlabel = ''
            color    = np.empty((self.naircraft, 3), dtype=np.uint8)
            for i in range(self.naircraft):
                if np.isnan(data.tas[i]):
                    print 'CAS NaN in %d: %s' % (i, data.id[i])
                    data.cas[i] = 0.0

                if np.isnan(data.alt[i]):
                    print 'ALT NaN in %d: %s' % (i, data.id[i])
                    data.alt[i] = 0.0

                # Make label: 3 lines of 8 characters per aircraft
                rawlabel += '%-8sFL%03d   %-8d' % (data.id[i][:8], int(data.alt[i] / ft / 100), int(data.cas[i] / kts))
                confindices = data.iconf[i]
                if len(confindices) > 0:
                    color[i, :] = amber
                    for confidx in confindices:
                        cpalines[4 * confidx : 4 * confidx + 4] = [ data.lat[i], data.lon[i],
                                                                    data.confcpalat[confidx], data.confcpalon[confidx]]
                else:
                    color[i, :] = green

            update_buffer(self.confcpabuf, cpalines)
            update_buffer(self.accolorbuf, color)
            update_buffer(self.aclblbuf, np.array(rawlabel, dtype=np.string_))

            # If there is a visible route, update the start position
            if self.route_acid != "":
                if self.route_acid in data.id:
                    idx = data.id.index(self.route_acid)
                    update_buffer(self.routebuf,
                                  np.array([data.lat[idx], data.lon[idx]], dtype=np.float32))

    def show_ssd(self, arg):
        if arg == 'ALL':
            self.ssd_all = True
        elif arg == 'OFF':
            self.ssd_all = False
            self.ssd_ownship = np.array([], dtype=np.uint16)
        else:
            if arg in self.ssd_ownship:
                self.ssd_ownship = np.delete(self.ssd_ownship, np.argmax(self.ssd_ownship == arg))
            else:
                self.ssd_ownship = np.append(self.ssd_ownship, arg)

    def updatePolygon(self, name, data_in):
        nact = self.nodedata[manager.sender()[0]]
        if name in nact.polynames:
            # We're either updating a polygon, or deleting it. In both cases
            # we remove the current one.
            nact.polydata = np.delete(nact.polydata, range(*nact.polynames[name]))
            del nact.polynames[name]

        if data_in is not None:
            nact.polynames[name] = (len(nact.polydata), 2*len(data_in))
            newbuf = np.empty(2 * len(data_in), dtype=np.float32)
            newbuf[0::4]   = data_in[0::2]  # lat
            newbuf[1::4]   = data_in[1::2]  # lon
            newbuf[2:-2:4] = data_in[2::2]  # lat
            newbuf[3:-3:4] = data_in[3::2]  # lon
            newbuf[-2:]    = data_in[0:2]
            nact.polydata = np.append(nact.polydata, newbuf)
            update_buffer(self.allpolysbuf, nact.polydata)
            self.allpolys.set_vertex_count(len(nact.polydata)/2)

    def previewpoly(self, shape_type, data_in=None):
        if shape_type is None:
            self.polyprev.set_vertex_count(0)
            return
        if shape_type in ['BOX', 'AREA']:
            # For a box (an area is a box) we need to add two additional corners
            data = np.zeros(8, dtype=np.float32)
            data[0:2] = data_in[0:2]
            data[2:4] = data_in[2], data_in[1]
            data[4:6] = data_in[2:4]
            data[6:8] = data_in[0], data_in[3]
        else:
            data = data_in
        update_buffer(self.polyprevbuf, data)
        self.polyprev.set_vertex_count(len(data)/2)

    def airportsInRange(self):
        ll_range = max(1.5 / self.zoom, 1.0)
        indices = np.logical_and.reduce((self.apt_ctrlat >= self.panlat - ll_range, self.apt_ctrlat <= self.panlat + ll_range,
                                         self.apt_ctrlon >= self.panlon - ll_range, self.apt_ctrlon <= self.panlon + ll_range))

        self.apt_inrange = self.apt_indices[indices]

    def pixelCoordsToGLxy(self, x, y):
        """Convert screen pixel coordinates to GL projection coordinates (x, y range -1 -- 1)
        """
        # GL coordinates (x, y range -1 -- 1)
        glx = (float(2.0 * x) / self.width  - 1.0)
        gly = -(float(2.0 * y) / self.height - 1.0)
        return glx, gly

    def pixelCoordsToLatLon(self, x, y):
        """Convert screen pixel coordinates to lat/lon coordinates
        """
        glx, gly = self.pixelCoordsToGLxy(x, y)

        # glxy   = zoom * (latlon - pan)
        # latlon = pan + glxy / zoom
        lat = self.panlat + gly / (self.zoom * self.ar)
        lon = self.panlon + glx / (self.zoom * self.flat_earth)
        return lat, lon

    def event(self, event):
        if event.type() == PanZoomEventType:
            if event.pan is not None:
                # Absolute pan operation
                if event.absolute:
                    self.panlat = event.pan[0]
                    self.panlon = event.pan[1]
                # Relative pan operation
                else:
                    self.panlat += event.pan[0]
                    self.panlon += event.pan[1]

                # Don't pan further than the poles in y-direction
                self.panlat = min(max(self.panlat, -90.0 + 1.0 /
                      (self.zoom * self.ar)), 90.0 - 1.0 / (self.zoom * self.ar))

                # Update flat-earth factor and possibly zoom in case of very wide windows (> 2:1)
                self.flat_earth = np.cos(np.deg2rad(self.panlat))
                self.zoom = max(self.zoom, 1.0 / (180.0 * self.flat_earth))

            if event.zoom is not None:
                if event.absolute:
                    # Limit zoom extents in x-direction to [-180:180], and in y-direction to [-90:90]
                    self.zoom = max(event.zoom, 1.0 / min(90.0 * self.ar, 180.0 * self.flat_earth))
                else:
                    prevzoom = self.zoom
                    glx, gly = self.pixelCoordsToGLxy(event.origin[0], event.origin[1])
                    self.zoom *= event.zoom

                    # Limit zoom extents in x-direction to [-180:180], and in y-direction to [-90:90]
                    self.zoom = max(self.zoom, 1.0 / min(90.0 * self.ar, 180.0 * self.flat_earth))

                    # Correct pan so that zoom actions are around the mouse position, not around 0, 0
                    # glxy / zoom1 - pan1 = glxy / zoom2 - pan2
                    # pan2 = pan1 + glxy (1/zoom2 - 1/zoom1)
                    self.panlon = self.panlon - glx * (1.0 / self.zoom - 1.0 / prevzoom) / self.flat_earth
                    self.panlat = self.panlat - gly * (1.0 / self.zoom - 1.0 / prevzoom) / self.ar

                # Don't pan further than the poles in y-direction
                self.panlat = min(max(self.panlat, -90.0 + 1.0 / (self.zoom * self.ar)), 90.0 - 1.0 / (self.zoom * self.ar))

                # Update flat-earth factor
                self.flat_earth = np.cos(np.deg2rad(self.panlat))

            if self.zoom >= 1.0:
                self.airportsInRange()
            event.accept()

            # Check for necessity wrap-around in x-direction
            self.wraplon  = -999.9
            self.wrapdir  = 0
            if self.panlon + 1.0 / (self.zoom * self.flat_earth) < -180.0:
                # The left edge of the map has passed the right edge of the screen: we can just change the pan position
                self.panlon += 360.0
            elif self.panlon - 1.0 / (self.zoom * self.flat_earth) < -180.0:
                # The left edge of the map has passed the left edge of the screen: we need to wrap around to the left
                self.wraplon = float(np.ceil(360.0 + self.panlon - 1.0 / (self.zoom * self.flat_earth)))
                self.wrapdir = -1
            elif self.panlon - 1.0 / (self.zoom * self.flat_earth) > 180.0:
                # The right edge of the map has passed the left edge of the screen: we can just change the pan position
                self.panlon -= 360.0
            elif self.panlon + 1.0 / (self.zoom * self.flat_earth) > 180.0:
                # The right edge of the map has passed the right edge of the screen: we need to wrap around to the right
                self.wraplon = float(np.floor(-360.0 + self.panlon + 1.0 / (self.zoom * self.flat_earth)))
                self.wrapdir = 1

            self.globaldata.set_wrap(self.wraplon, self.wrapdir)

            # update pan and zoom on GPU for all shaders
            self.globaldata.set_pan_and_zoom(self.panlat, self.panlon, self.zoom)
            return True

        else:
            return super(RadarWidget, self).event(event)
Example #4
0
class BlipDriver(QGLWidget):
    def __init__(self, parent=None):
        super(BlipDriver, self).__init__(parent=parent)
        self.initialized = False
        self.width       = 800
        self.height      = 800
        self.ndviewport  = (100, 200, 600, 600)
        self.mcpviewport = (0, 0, 800, 200)
        self.btn_state   = [False] * 14
        self.resize(800, 800)
        self.setMouseTracking(True)
        self.drag_start  = (0, 0)
        self.btn_pressed = None
        self.selValues   = 5 * [0]  # [0:courseLeftRight, 1:spd, 2:hdg, 3:alt, 4:vs]
        self.rate        = 0.0
        self.remainder   = 0.0
        self.updownpos   = None

    def create_objects(self):
        self.mcp         = RenderObject(gl.GL_TRIANGLES, vertex=np.array(rect(-1, -1, 2, 2), dtype=np.float32))
        self.mcp_texture = load_texture('mcp737.png')
        self.btn_tex     = load_texture('btn_led.png')

        self.lcd_tex     = load_lcd_font()

        v_mcp_text = []
        for pos in [0.0, 0.03, 0.06,
                    0.31, 0.34, 0.37, 0.4, 0.43,
                    0.644, 0.674, 0.704,
                    0.955, 0.985, 1.015, 1.045, 1.075,
                    1.22, 1.25, 1.28, 1.31, 1.34,
                    1.69, 1.72, 1.75]:
            v, t = Font.char(-0.886 + pos, 0.45, 0.03, 0.25)
            v_mcp_text += v

        self.mcp_text       = RenderObject(gl.GL_TRIANGLES, vertex=np.array(v_mcp_text, dtype=np.float32))
        self.lcd_charcoords = np.zeros(24 * 6, dtype=np.float32)
        self.lcdbuf         = self.mcp_text.bind_attrib(1, 1, self.lcd_charcoords, datatype=gl.GL_FLOAT)

        btn_leds = []
        for pos in [(-0.74, -0.75), (-0.645, -0.75), (-0.37, -0.75), (-0.232, -0.75),
                    (-0.09, -0.75), (0.105, -0.75), (0.2, -0.75),
                    (-0.37, 0.5), (-0.09, 0.5), (-0.09, -0.125),
                     (0.575, 0.34), (0.575, -0.34),
                     (0.684, 0.34), (0.684, -0.34)]:
            btn_leds += rect(pos[0], pos[1], 0.055, 0.075)
        btn_color = np.zeros(14 * 6 * 4, dtype=np.uint8)
        self.btn_leds = RenderObject(gl.GL_TRIANGLES, vertex=np.array(btn_leds, dtype=np.float32), color=btn_color)

        # Button up/down indicator
        w, h, offset = 0.04, 0.16, 0.04
        triangles = np.array([-w, offset,  0.0, offset + h, w, offset,
                     -w, -offset, 0.0, -offset - h, w, -offset], dtype=np.float32)
        col_triangles = np.array(6 * [255, 255, 255, 180], dtype=np.uint8)
        self.updown = RenderObject(gl.GL_TRIANGLES, vertex=triangles, color=col_triangles)

        # Use the same font as the radarwidget
        self.font = Font()
        self.font.create_font_array('../../data/graphics/font/')
        self.font.init_shader(self.text_shader)

        edge = np.zeros(120, dtype=np.float32)
        edge[0:120:2] = 1.4 * np.sin(np.radians(np.arange(-60, 60, 2)))
        edge[1:120:2] = 1.4 * np.cos(np.radians(np.arange(-60, 60, 2)))
        self.edge = RenderObject(gl.GL_LINE_STRIP, vertex=edge, color=white)

        arcs = []
        for i in range(1, 4):
            for angle in range(-60, 60, max(2, 6 - 2 * i)):
                arcs.append(float(i) * 0.35 * sin(radians(angle)))
                arcs.append(float(i) * 0.35 * cos(radians(angle)))
                if i == 4:
                    arcs.append(float(i) * 0.35 * sin(radians(angle + 2)))
                    arcs.append(float(i) * 0.35 * cos(radians(angle + 2)))
        arcs = np.array(arcs, dtype=np.float32)
        self.arcs = RenderObject(gl.GL_LINES, vertex=arcs, color=white)

        mask = []
        for angle in range(-60, 60, 2):
            mask.append(1.4 * sin(radians(angle)))
            mask.append(10.0)
            mask.append(1.4 * sin(radians(angle)))
            mask.append(1.4 * cos(radians(angle)))
        mask = np.array(mask, dtype=np.float32)
        self.mask = RenderObject(gl.GL_TRIANGLE_STRIP, vertex=mask, color=black)

        ticks = np.zeros(288, dtype=np.float32)
        for i in range(72):
            ticktop = 1.46 if i % 6 == 0 else (1.44 if i % 2 == 0 else 1.42)
            ticks[4*i  :4*i+2] = (1.4 * sin(radians(i * 5)), 1.4 * cos(radians(i * 5)))
            ticks[4*i+2:4*i+4] = (ticktop * sin(radians(i * 5)), ticktop * cos(radians(i * 5)))
        self.ticks = RenderObject(gl.GL_LINES, vertex=ticks, color=white)

        ticklbls = np.zeros(24 * 36, dtype=np.float32)
        texcoords = np.zeros(36 * 36, dtype=np.float32)

        for i in range(36):
            if i % 3 == 0:
                w, h, y = 0.045, 0.09, 1.48
            else:
                w, h, y = 0.035, 0.07, 1.46
            tmp = [(-w, h+y), (-w, y), (0.0, h+y), (0.0, h+y), (-w, y), (0.0, y),
                   (0.0, h+y), (0.0, y), (w, h+y), (w, h+y), (0.0, y), (w, y)]

            # numerics start at ASCII 48
            c1 = i / 10 + 48
            c2 = i % 10 + 48
            texcoords[36*i:36*i+18]    = [0, 0, c1, 0, 1, c1, 1, 0, c1, 1, 0, c1, 0, 1, c1, 1, 1, c1]
            texcoords[36*i+18:36*i+36] = [0, 0, c2, 0, 1, c2, 1, 0, c2, 1, 0, c2, 0, 1, c2, 1, 1, c2]
            angle = radians(10 * (36 - i))
            rot = np.array([[cos(angle), -sin(angle)], [sin(angle), cos(angle)]])
            for j in range(12):
                ticklbls[24*i+2*j:24*i+2*j+2] = rot.dot(tmp[j])

        self.ticklbls = RenderObject(gl.GL_TRIANGLES, vertex=ticklbls, color=white, texcoords=texcoords)

        vown = np.array([0.0, 0.0, 0.0, -0.12, 0.065, -0.03, -0.065, -0.03, 0.022, -0.1, -0.022, -0.1], dtype=np.float32)
        self.ownship = RenderObject(gl.GL_LINES, vertex=vown, color=yellow)

        self.spdlabel_text = self.font.prepare_text_string('GS    TAS', 0.05, white, (-0.98, 1.6))
        self.spdlabel_val  = self.font.prepare_text_string('  000    000', 0.05, green, (-0.97, 1.6))

        self.initialized = True
        # Unbind everything
        RenderObject.unbind_all()

    def setAircraftID(self, ac_id):
        self.ac_id = ac_id
        self.setWindowTitle(ac_id)

    def update_aircraft_data(self, ownid, ownlat, ownlon, owntas, ownhdg, n_aircraft):
        self.globaldata.set_owndata(ownid, ownlat, ownlon, ownhdg)
        self.n_aircraft = n_aircraft

    def initializeGL(self):
        """Initialize OpenGL, VBOs, upload data on the GPU, etc."""

        # First check for supported GL version
        gl_version = float(gl.glGetString(gl.GL_VERSION)[:3])
        if gl_version < 3.3:
            return

        # background color
        gl.glClearColor(0, 0, 0, 0)
        gl.glEnable(gl.GL_BLEND)
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)

        # self.mcp_data       = mcpUBO()
        self.globaldata     = ndUBO()
        self.mcp_col_shader = BlueSkyProgram('mcp.vert', 'color.frag')
        self.mcp_tex_shader = BlueSkyProgram('mcp.vert', 'texture.frag')
        self.mcp_txt_shader = BlueSkyProgram('mcp_text.vert', 'mcp_text.frag')

        self.color_shader   = BlueSkyProgram('normal.vert', 'color.frag')
        self.text_shader    = BlueSkyProgram('text.vert', 'text.frag')
        self.text_shader.bind_uniform_buffer('global_data', self.globaldata)

        self.create_objects()
        self.update_lcd()

    def resizeGL(self, width, height):
        pixel_ratio = self.devicePixelRatio()

        self.width, self.height = width / pixel_ratio, height / pixel_ratio
        hmcp = height / 10 * 2
        # paint ND within the largest possible rectangular area in the window
        wnd = hnd = min(width, height - hmcp)
        xnd = max(0, (width - wnd) / 2)
        ynd = hmcp + max(0, (height - hmcp - hnd) / 2)
        self.mcpviewport = (0, 0, width, hmcp)
        self.ndviewport = (xnd, ynd, wnd, hnd)

    def paintGL(self):
        """Paint the scene."""
        # pass if the framebuffer isn't complete yet or if not initialized
        if not (gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) == gl.GL_FRAMEBUFFER_COMPLETE and self.isVisible() and self.initialized):
            return

        gl.glClearColor(0, 0, 0, 0)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
        # Set the viewport and clear the framebuffer

        # --- Draw the ND in its viewport ----
        gl.glViewport(*self.ndviewport)
        self.color_shader.use()
        self.arcs.draw()

        self.ownship.draw()
        self.mask.draw()
        self.edge.draw()

        self.ticks.draw()

        # Select the text shader
        self.text_shader.use()
        self.font.use()
        self.font.set_block_size((0, 0))
        self.globaldata.set_vertex_modifiers(VERTEX_IS_GLXY, True)
        self.ticklbls.draw()

        self.globaldata.set_vertex_modifiers(VERTEX_IS_GLXY, False)
        self.spdlabel_text.draw()
        self.spdlabel_val.draw()

        # --- Draw the MCP in its viewport ---
        gl.glViewport(*self.mcpviewport)
        self.mcp_tex_shader.use()
        gl.glActiveTexture(gl.GL_TEXTURE0 + 0)
        gl.glBindTexture(gl.GL_TEXTURE_2D, self.mcp_texture)
        self.mcp.draw()

        gl.glBindTexture(gl.GL_TEXTURE_2D, self.btn_tex)
        self.btn_leds.draw()

        if self.updownpos is not None:
            self.mcp_col_shader.use()
            gl.glVertexAttrib2f(2, *self.updownpos)
            self.updown.draw()
            gl.glVertexAttrib2f(2, 0.0, 0.0)

        self.mcp_txt_shader.use()
        gl.glActiveTexture(gl.GL_TEXTURE0 + 0)
        gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, self.lcd_tex)
        self.mcp_text.draw()

        # Unbind everything
        RenderObject.unbind_all()
        gl.glUseProgram(0)

    def update_lcd(self):
        # [0:courseLeftRight, 1:spd, 2:hdg, 3:alt, 4:vs]
        val = self.selValues
        chars = '%03d' % (int(val[0]) % 360)
        chars += '% 5d' % max(0, val[1])
        chars += '%03d' % (int(val[2]) % 360)
        chars += '% 5d' % min(99999, max(0, val[3]))
        chars += '     ' if val[4] == 0 else '% 5d' % min(9999, max(-9999, val[4]))
        chars += '%03d' % (int(val[0]) % 360)

        indices = []
        for c in chars:
            if c == ' ':
                indices += [0] * 6
            elif c == '-':
                indices += [1] * 6
            elif c == '.':
                indices += [2] * 6
            else:
                indices += [int(c) + 3] * 6

        update_buffer(self.lcdbuf, np.array(indices, dtype=np.float32))

    def event(self, event):
        if not self.initialized:
            return super(BlipDriver, self).event(event)

        self.makeCurrent()
        if event.type() in [QEvent.MouseMove, QEvent.MouseButtonPress, QEvent.MouseButtonRelease]:
            px = 2.0 * (event.x()) / self.width - 1.0
            py = 10.0 * (self.height - event.y()) / self.height - 1.0

        if event.type() == QEvent.MouseButtonPress and event.button() & Qt.LeftButton:
            self.btn_pressed, _ = check_knob(px, py)
            if self.btn_pressed is not None:
                self.drag_start = event.x(), event.y()
                QTimer.singleShot(100, self.updateAPValues)

        elif event.type() == QEvent.MouseButtonRelease and event.button() & Qt.LeftButton:
            # print px, py
            self.btn_pressed = None
            col_triangles = 6 * [255, 255, 255, 180]
            update_buffer(self.updown.colorbuf, np.array(col_triangles, dtype=np.uint8))
            # MCP button clicked?
            name, idx = check_btn(px, py)
            if name is not None:
                self.btn_state[idx] = not self.btn_state[idx]

            btn_color = []
            for b in self.btn_state:
                btn_color += 24 * [255] if b else 24 * [0]
            update_buffer(self.btn_leds.colorbuf, np.array(btn_color, dtype=np.uint8))
        elif event.type() == QEvent.MouseMove:
            if event.buttons() & Qt.LeftButton and self.btn_pressed is not None:
                self.rate = float(self.drag_start[1] - event.y()) / self.height
                if self.rate > 1e-2:
                    col_triangles = 3 * [255, 140, 0, 255] + 3 * [255, 255, 255, 180]
                elif self.rate < 1e-2:
                    col_triangles = 3 * [255, 255, 255, 180] + 3 * [255, 140, 0, 255]
                else:
                    col_triangles = 6 * [255, 255, 255, 180]
                update_buffer(self.updown.colorbuf, np.array(col_triangles, dtype=np.uint8))
                return True
            # Mouse-over for knobs
            name, self.updownpos = check_knob(px, py)

            # ismcp = float(self.height - event.y()) / self.height <= 0.2

        return super(BlipDriver, self).event(event)

    @pyqtSlot()
    def updateAPValues(self):
        if self.btn_pressed == 'COURSE':
            val = self.remainder + 15 * self.rate
            self.selValues[0] += int(val)
            self.remainder = val - int(val)

        if self.btn_pressed == 'SPD':
            val = self.remainder + 20 * self.rate
            self.selValues[1] += int(val)
            self.remainder = val - int(val)

        if self.btn_pressed == 'HDG':
            val = self.remainder + 15 * self.rate
            self.selValues[2] += int(val)
            self.remainder = val - int(val)

        if self.btn_pressed == 'ALT':
            if abs(self.rate) > 0.06:
                increment = 1000
                val = self.remainder + min(2.0, abs(self.rate) / 0.04) * np.sign(self.rate)
            elif abs(self.rate) > 0.02:
                increment = 100
                val = self.remainder + self.rate / 0.04
            else:
                increment = 10
                val = self.remainder + self.rate / 0.02

            self.selValues[3] += int(val) * increment
            self.remainder = val - int(val)

        if self.btn_pressed == 'VS':
            if abs(self.rate) > 0.2:
                increment = 1000
                val = self.remainder + min(2.0, abs(self.rate) / 0.04) * np.sign(self.rate)
            elif abs(self.rate) > 0.05:
                increment = 100
                val = self.remainder + self.rate / 0.04
            else:
                increment = 10
                val = self.remainder + self.rate / 0.02

            self.selValues[4] += int(val) * increment
            self.remainder = val - int(val)

        self.update_lcd()
        if self.btn_pressed is not None:
            QTimer.singleShot(200, self.updateAPValues)