#+TITLE: Python CAD Tutorial 04 - Mouse coordinates in 3D space
#+DATE: 2013-10-04 12:00:00 UTC
#+DESCRIPTION: Setup the camera so we can see our models.
#+FILETAGS: GTK-3:python:cad:opengl
#+CATEGORY: python
#+SLUG: 04-mouse-coordinates-in-3d-space
#+THUMBNAIL: ../../../images/cad/tut04-interface.png
#+BEGIN_COMMENT
.. title: Python CAD Tutorial 04 - Mouse coordinates in 3D space
.. slug: 04-mouse-coordinates-in-3d-space.org
.. date: 2013-10-04 12:00:00 UTC
.. tags: GTK-3, python, cad, opengl
.. category: python
.. description: Setup the camera so we can see our models.
.. type: text
#+END_COMMENT

[[https://code.launchpad.net/~oly/fabricad/tut04][View/Download Code]]


#+CAPTION: Mouse coordinates 
[[../../../images/cad/tut04-interface.png]]

We have a camera and something displayed on the screen, now let's start taking input from the mouse. We will create a new mouse handling class which will allow us to store a series of clicks. We can also use the stored positions for previews at a later date.The new class contains a few simple methods:

 + append() to add a new click
 + count() to return the number of stored clicks
 + get_click_position() returns the last clicked position
 + get_points() returns a list of the stored locations, and resets the list
 + clear() resets the stored coordinates.

#+BEGIN_SRC python
class mouse_state:
    """ store mouse clicks so we know where to draw and what to create """
    button1 = 0
    button2 = 0
    button3 = 0
    cordinates = []
    x = y = 0

    def append(self, x, y, button=1):
        """ store a new mouse click """
        self.x = x
        self.y = y
        self.cordinates.append((x, y,))

    def get_click_position(self):
        """ return the position of the last click """
        return self.x, self.y

    def get_points(self):
        """ return all stored points we may want to store lots of points when drawing a line for example"""
        if len(self.cordinates) != 2:
            return None
        result = self.cordinates
        self.cordinates = []
        return result

    def count(self):
        """return number of stored points """
        return len(self.cordinates)

    def clear(self):
        self.x = self.y = 0
        self.cordinates = []
#+END_SRC

Let's add a new method to our camera class, which will convert mouse clicks in 2 dimensional space into 3 dimensional coordinates so we can position something on the screen.The 'get_click_point' method below takes a tuple containing the x and y coordinates from the mouse. It then converts the y position because in opengl '0' is at the bottom, but drawing area widget has '0' at the top. Finally it uses gluUnProject to convert from 2d space to 3d space.

#+BEGIN_SRC python
    def get_click_point(self, pos):
        """ convert 2d click in the viewport to 3d point in space"""
        viewport = glGetIntegerv(GL_VIEWPORT)
        modelview = glGetDoublev(GL_MODELVIEW_MATRIX)
        projection = glGetDoublev(GL_PROJECTION_MATRIX)

        #convert screen ccordinate to opengl cordinates, this means modifying the y axes only
        x, y = pos[0], self.viewport[1] - pos[1]

        #use unproject to calculate the point and store the resutl in a point object
        return createpoint(gluUnProject(x, y, 0.20, modelview, projection, viewport), (250, 0, 0))
#+END_SRC

Let's handle the mouse click events in the drawing area; we will store the 2d x and y coordinates of a mouse click in our new mouse_state_class. Now we adjust the on_draw method to draw a point at the location we clicked on the page, which gives us visual feedback that everything is working correctly.

#+BEGIN_SRC python
    def mouse_click(self, widget, event):
        self.mouse.append(event.x, event.y, event.button)
        self.test_point = self.camera.get_click_point(self.mouse.get_click_position())
        self.on_draw()

    def on_draw(self, *args):
        """ Test code to make sure we can draw a pixel successfully,
        also test we can position our new camera class to lookat the pixel"""
        #lets not look directly at the point we are drawing, demonstrating we can lookat points in space
        self.camera.lookat.x = 20
        self.camera.lookat.y = 20
        self.camera.lookat.z = -20

        #recalculate our camera based on the new settings
        self.camera.update()

        glClearColor(0.0, 0.0, 0.0, 0.0)
        glClear(GL_COLOR_BUFFER_BIT)
        self.glwrap.draw_start()

        #place a point we can lookat that will be positioned in our field of view.
        self.test_point.draw()

        self.glwrap.draw_finish()
#+END_SRC
