Started show_complex_3D() dev
authorDmitriy Morozov <dmitriy@mrzv.org>
Thu, 07 Jun 2012 10:11:01 -0700
branchdev
changeset 260 27c47fc33468
parent 259 84c100980206
child 261 7b846a522bed
Started show_complex_3D()
bindings/python/dionysus/viewer/PyGLWidget.py
bindings/python/dionysus/viewer/__init__.py
bindings/python/dionysus/viewer/complex3d.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/python/dionysus/viewer/PyGLWidget.py	Thu Jun 07 10:11:01 2012 -0700
@@ -0,0 +1,309 @@
+# -*- coding: utf-8 -*-
+#===============================================================================
+#
+# PyGLWidget.py
+#
+# A simple GL Viewer.
+#
+# Copyright (c) 2011, Arne Schmitz <arne.schmitz@gmx.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the <organization> nor the
+#       names of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+#===============================================================================
+
+from PyQt4 import QtCore, QtGui, QtOpenGL
+import math
+import numpy
+import numpy.linalg as linalg
+import OpenGL
+OpenGL.ERROR_CHECKING = True
+from OpenGL.GL import *
+from OpenGL.GLU import *
+
+class PyGLWidget(QtOpenGL.QGLWidget):
+
+    # Qt signals
+    signalGLMatrixChanged = QtCore.pyqtSignal()
+    rotationBeginEvent = QtCore.pyqtSignal()
+    rotationEndEvent = QtCore.pyqtSignal()
+
+    def __init__(self, parent = None):
+        format = QtOpenGL.QGLFormat()
+        format.setSampleBuffers(True)
+        QtOpenGL.QGLWidget.__init__(self, format, parent)
+        self.setCursor(QtCore.Qt.OpenHandCursor)
+        self.setMouseTracking(True)
+
+        self.modelview_matrix_  = []
+        self.translate_vector_  = [0.0, 0.0, 0.0]
+        self.viewport_matrix_   = []
+        self.projection_matrix_ = []
+        self.near_   = 0.1
+        self.far_    = 100.0
+        self.fovy_   = 45.0
+        self.radius_ = 5.0
+        self.last_point_2D_ = QtCore.QPoint()
+        self.last_point_ok_ = False
+        self.last_point_3D_ = [1.0, 0.0, 0.0]
+        self.isInRotation_  = False
+
+        # connections
+        #self.signalGLMatrixChanged.connect(self.printModelViewMatrix)
+
+    @QtCore.pyqtSlot()
+    def printModelViewMatrix(self):
+        print self.modelview_matrix_
+
+    def initializeGL(self):
+        # OpenGL state
+        glClearColor(0.0, 0.0, 0.0, 0.0)
+        glEnable(GL_DEPTH_TEST)
+        self.reset_view()
+
+    def resizeGL(self, width, height):
+        glViewport( 0, 0, width, height );
+        self.set_projection( self.near_, self.far_, self.fovy_ );
+        self.updateGL()
+
+    def paintGL(self):
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+
+        glMatrixMode(GL_MODELVIEW)
+        glLoadMatrixd(self.modelview_matrix_)
+
+    def set_projection(self, _near, _far, _fovy):
+        self.near_ = _near
+        self.far_ = _far
+        self.fovy_ = _fovy
+        self.makeCurrent()
+        glMatrixMode( GL_PROJECTION )
+        glLoadIdentity()
+        gluPerspective( self.fovy_, float(self.width()) / float(self.height()),
+                        self.near_, self.far_ )
+        self.updateGL()
+
+    def set_center(self, _cog):
+        self.center_ = _cog
+        self.view_all()
+
+    def set_radius(self, _radius):
+        self.radius_ = _radius
+        self.set_projection(_radius / 100.0, _radius * 100.0, self.fovy_)
+        self.reset_view()
+        self.translate([0, 0, -_radius * 2.0])
+        self.view_all()
+        self.updateGL()
+
+    def reset_view(self):
+        # scene pos and size
+        glMatrixMode( GL_MODELVIEW )
+        glLoadIdentity();
+        self.modelview_matrix_ = glGetDoublev( GL_MODELVIEW_MATRIX )
+        self.set_center([0.0, 0.0, 0.0])
+
+    def reset_rotation(self):
+        self.modelview_matrix_[0] = [1.0, 0.0, 0.0, 0.0]
+        self.modelview_matrix_[1] = [0.0, 1.0, 0.0, 0.0]
+        self.modelview_matrix_[2] = [0.0, 0.0, 1.0, 0.0]
+        glMatrixMode(GL_MODELVIEW)
+        glLoadMatrixd(self.modelview_matrix_)
+        self.updateGL()
+   
+    def translate(self, _trans):
+        # Translate the object by _trans
+        # Update modelview_matrix_
+        self.makeCurrent()
+        glMatrixMode(GL_MODELVIEW)
+        glLoadIdentity()
+        glTranslated(_trans[0], _trans[1], _trans[2])
+        glMultMatrixd(self.modelview_matrix_)
+        self.modelview_matrix_ = glGetDoublev(GL_MODELVIEW_MATRIX)
+        self.translate_vector_[0] = self.modelview_matrix_[3][0]
+        self.translate_vector_[1] = self.modelview_matrix_[3][1]
+        self.translate_vector_[2] = self.modelview_matrix_[3][2]
+        self.signalGLMatrixChanged.emit()
+
+    def rotate(self, _axis, _angle):
+        t = [self.modelview_matrix_[0][0] * self.center_[0] +
+             self.modelview_matrix_[1][0] * self.center_[1] +
+             self.modelview_matrix_[2][0] * self.center_[2] +
+             self.modelview_matrix_[3][0],
+             self.modelview_matrix_[0][1] * self.center_[0] +
+             self.modelview_matrix_[1][1] * self.center_[1] +
+             self.modelview_matrix_[2][1] * self.center_[2] +
+             self.modelview_matrix_[3][1],
+             self.modelview_matrix_[0][2] * self.center_[0] +
+             self.modelview_matrix_[1][2] * self.center_[1] +
+             self.modelview_matrix_[2][2] * self.center_[2] +
+             self.modelview_matrix_[3][2]]
+
+        self.makeCurrent()
+        glLoadIdentity()
+        glTranslatef(t[0], t[1], t[2])
+        glRotated(_angle, _axis[0], _axis[1], _axis[2])
+        glTranslatef(-t[0], -t[1], -t[2])
+        glMultMatrixd(self.modelview_matrix_)
+        self.modelview_matrix_ = glGetDoublev(GL_MODELVIEW_MATRIX)
+        self.signalGLMatrixChanged.emit()
+
+    def view_all(self):
+        self.translate( [ -( self.modelview_matrix_[0][0] * self.center_[0] +
+                             self.modelview_matrix_[0][1] * self.center_[1] +
+                             self.modelview_matrix_[0][2] * self.center_[2] +
+                             self.modelview_matrix_[0][3]),
+                           -( self.modelview_matrix_[1][0] * self.center_[0] +
+                              self.modelview_matrix_[1][1] * self.center_[1] +
+                              self.modelview_matrix_[1][2] * self.center_[2] +
+                              self.modelview_matrix_[1][3]),
+                           -( self.modelview_matrix_[2][0] * self.center_[0] +
+                              self.modelview_matrix_[2][1] * self.center_[1] +
+                              self.modelview_matrix_[2][2] * self.center_[2] +
+                              self.modelview_matrix_[2][3] +
+                              self.radius_ / 2.0 )])
+
+    def map_to_sphere(self, _v2D):
+        _v3D = [0.0, 0.0, 0.0]
+        # inside Widget?
+        if (( _v2D.x() >= 0 ) and ( _v2D.x() <= self.width() ) and
+            ( _v2D.y() >= 0 ) and ( _v2D.y() <= self.height() ) ):
+            # map Qt Coordinates to the centered unit square [-0.5..0.5]x[-0.5..0.5]
+            x  = float( _v2D.x() - 0.5 * self.width())  / self.width()
+            y  = float( 0.5 * self.height() - _v2D.y()) / self.height()
+
+            _v3D[0] = x;
+            _v3D[1] = y;
+            # use Pythagoras to comp z-coord (the sphere has radius sqrt(2.0*0.5*0.5))
+            z2 = 2.0*0.5*0.5-x*x-y*y;
+            # numerical robust sqrt
+            _v3D[2] = math.sqrt(max( z2, 0.0 ))
+
+            # normalize direction to unit sphere
+            n = linalg.norm(_v3D)
+            _v3D = numpy.array(_v3D) / n
+
+            return True, _v3D
+        else:
+            return False, _v3D
+
+    def wheelEvent(self, _event):
+        # Use the mouse wheel to zoom in/out
+        
+        d = - float(_event.delta()) / 200.0 * self.radius_
+        self.translate([0.0, 0.0, d])
+        self.updateGL()
+        _event.accept()
+
+    def mousePressEvent(self, _event):
+        self.last_point_2D_ = _event.pos()
+        self.last_point_ok_, self.last_point_3D_ = self.map_to_sphere(self.last_point_2D_)
+
+    def mouseMoveEvent(self, _event):
+        newPoint2D = _event.pos()
+
+        if ((newPoint2D.x() < 0) or (newPoint2D.x() > self.width()) or
+            (newPoint2D.y() < 0) or (newPoint2D.y() > self.height())):
+            return
+        
+        # Left button: rotate around center_
+        # Middle button: translate object
+        # Left & middle button: zoom in/out
+
+        value_y = 0
+        newPoint_hitSphere, newPoint3D = self.map_to_sphere(newPoint2D)
+
+        dx = float(newPoint2D.x() - self.last_point_2D_.x())
+        dy = float(newPoint2D.y() - self.last_point_2D_.y())
+
+        w  = float(self.width())
+        h  = float(self.height())
+
+        # enable GL context
+        self.makeCurrent()
+
+        # move in z direction
+        if (((_event.buttons() & QtCore.Qt.LeftButton) and (_event.buttons() & QtCore.Qt.MidButton))
+            or (_event.buttons() & QtCore.Qt.LeftButton and _event.modifiers() & QtCore.Qt.ControlModifier)):
+            value_y = self.radius_ * dy * 2.0 / h;
+            self.translate([0.0, 0.0, value_y])
+        # move in x,y direction
+        elif (_event.buttons() & QtCore.Qt.MidButton
+              or (_event.buttons() & QtCore.Qt.LeftButton and _event.modifiers() & QtCore.Qt.ShiftModifier)):
+            z = - (self.modelview_matrix_[0][2] * self.center_[0] +
+                   self.modelview_matrix_[1][2] * self.center_[1] +
+                   self.modelview_matrix_[2][2] * self.center_[2] +
+                   self.modelview_matrix_[3][2]) / (self.modelview_matrix_[0][3] * self.center_[0] +
+                                                    self.modelview_matrix_[1][3] * self.center_[1] +
+                                                    self.modelview_matrix_[2][3] * self.center_[2] +
+                                                    self.modelview_matrix_[3][3])
+
+            fovy   = 45.0
+            aspect = w / h
+            n      = 0.01 * self.radius_
+            up     = math.tan(fovy / 2.0 * math.pi / 180.0) * n
+            right  = aspect * up
+
+            self.translate( [2.0 * dx / w * right / n * z,
+                             -2.0 * dy / h * up / n * z,
+                             0.0] )
+
+    
+        # rotate
+        elif (_event.buttons() & QtCore.Qt.LeftButton):
+            if (not self.isInRotation_):
+                self.isInRotation_ = True
+                self.rotationBeginEvent.emit()
+       
+            axis = [0.0, 0.0, 0.0]
+            angle = 0.0
+
+            if (self.last_point_ok_ and newPoint_hitSphere):
+                axis = numpy.cross(self.last_point_3D_, newPoint3D)
+                cos_angle = numpy.dot(self.last_point_3D_, newPoint3D)
+                if (abs(cos_angle) < 1.0):
+                    angle = math.acos(cos_angle) * 180.0 / math.pi
+                    angle *= 2.0
+                self.rotate(axis, angle)
+
+        # remember this point
+        self.last_point_2D_ = newPoint2D
+        self.last_point_3D_ = newPoint3D
+        self.last_point_ok_ = newPoint_hitSphere
+
+        # trigger redraw
+        self.updateGL()
+
+        def mouseReleaseEvent(self, _event):
+            if (isInRotation_):
+                isInRotation_ = false
+                self.rotationEndEvent.emit()
+            last_point_ok_ = False
+
+#===============================================================================
+#
+# Local Variables:
+# mode: Python
+# indent-tabs-mode: nil
+# End:
+#
+#===============================================================================
--- a/bindings/python/dionysus/viewer/__init__.py	Wed Jun 06 23:15:57 2012 -0700
+++ b/bindings/python/dionysus/viewer/__init__.py	Thu Jun 07 10:11:01 2012 -0700
@@ -1,2 +1,3 @@
-from diagram import *
-from complex import *
+from diagram    import *
+from complex    import *
+from complex3d  import *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings/python/dionysus/viewer/complex3d.py	Thu Jun 07 10:11:01 2012 -0700
@@ -0,0 +1,71 @@
+from    PyQt4       import QtGui, QtCore
+from    PyGLWidget  import PyGLWidget
+from    OpenGL.GL   import *
+from    dionysus    import Simplex
+
+class ComplexViewer3D(PyGLWidget):
+    def __init__(self, points, complex = None, values = None):
+        PyGLWidget.__init__(self)
+
+        self.points = points
+        if complex:
+            self.complex = [s for s in complex]
+        else:
+            # Create vertex simplices if no complex provided
+            self.complex = [Simplex([i]) for i in xrange(len(self.points))]
+
+        if not values:
+            values = [0]*len(self.points)
+        self.maxval, self.minval = max(values), min(values)
+
+    def paintGL(self):
+        PyGLWidget.paintGL(self)
+        self.complex.sort(lambda s1, s2: -cmp(s1.dimension(), s2.dimension()))
+        for s in self.complex:
+            vertices = [v for v in s.vertices]
+        #    if s.dimension() == 0:              # point
+        #        p = points[vertices[0]]
+        #        v = values[vertices[0]]
+        #        item = QtGui.QGraphicsEllipseItem(p[0] - radius/2,p[1] - radius/2,radius,radius)
+        #        color = self.colormap(v)
+        #        item.setBrush(QtGui.QBrush(color))
+        #        item.setPen(QtGui.QPen(color))
+            if s.dimension() == 1:            # edge
+                p0 = self.points[vertices[0]]
+                p1 = self.points[vertices[1]]
+
+                glColor3f(0,0,1)
+                glBegin(GL_LINES)
+                glVertex3f(p0[0],p0[1],p0[2])
+                glVertex3f(p1[0],p1[1],p1[2])
+                glEnd()
+            elif s.dimension() == 2:
+                p0 = self.points[vertices[0]]
+                p1 = self.points[vertices[1]]
+                p2 = self.points[vertices[2]]
+
+                glColor3f(1,0,0)
+                glBegin(GL_TRIANGLES)
+                glVertex3f(p0[0],p0[1],p0[2])
+                glVertex3f(p1[0],p1[1],p1[2])
+                glVertex3f(p2[0],p2[1],p2[2])
+                glEnd()
+
+
+    def colormap(self, v):
+        if self.maxval <= self.minval:
+            t = 0
+        else:
+            t = (v - self.minval)/(self.maxval - self.minval)
+        c = QtGui.QColor()
+        c.setHsv(int(t*255), 255, 255)
+        return c
+
+# TODO: cycle
+def show_complex_3D(points, complex = None, values = None):
+    app = QtGui.QApplication([])
+    view = ComplexViewer3D(points, complex, values)
+    view.show()
+    view.raise_()
+    app.exec_()
+