diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/ArcBall.py b/ArcBall.py deleted file mode 100644 index 6647b6d..0000000 --- a/ArcBall.py +++ /dev/null @@ -1,361 +0,0 @@ -""" -ArcBall.py -- Math utilities, vector, matrix types and ArcBall quaternion rotation class - ->>> unit_test_ArcBall_module () -unit testing ArcBall -Quat for first drag -[ 0.08438914 -0.08534209 -0.06240178 0.99080837] -First transform -[[ 0.97764552 -0.1380603 0.15858325 0. ] - [ 0.10925253 0.97796899 0.17787792 0. ] - [-0.17964739 -0.15657592 0.97119039 0. ] - [ 0. 0. 0. 1. ]] -LastRot at end of first drag -[[ 0.97764552 -0.1380603 0.15858325] - [ 0.10925253 0.97796899 0.17787792] - [-0.17964739 -0.15657592 0.97119039]] -Quat for second drag -[ 0.00710336 0.31832787 0.02679029 0.94757545] -Second transform -[[ 0.88022292 -0.08322023 -0.46720669 0. ] - [ 0.14910145 0.98314685 0.10578787 0. ] - [ 0.45052907 -0.16277808 0.8777966 0. ] - [ 0. 0. 0. 1.00000001]] -""" - -import numpy -import numpy.oldnumeric as Numeric -import copy -from math import sqrt - -# //assuming IEEE-754(GLfloat), which i believe has max precision of 7 bits -Epsilon = 1.0e-5 - - -class ArcBallT: - def __init__ (self, NewWidth, NewHeight): - self.m_StVec = Vector3fT () - self.m_vecStartPoint = Vector3fT () - self.m_vecEndPoint = Vector3fT () - self.m_EnVec = Vector3fT () - self.m_AdjustWidth = 1.0 - self.m_AdjustHeight = 1.0 - self.setBounds (NewWidth, NewHeight) - - def __str__ (self): - str_rep = "" - str_rep += "StVec = " + str (self.m_StVec) - str_rep += "\nEnVec = " + str (self.m_EnVec) - str_rep += "\n scale coords %f %f" % (self.m_AdjustWidth, self.m_AdjustHeight) - return str_rep - - def setBounds (self, NewWidth, NewHeight): - # //Set new bounds - assert (NewWidth > 1.0 and NewHeight > 1.0), "Invalid width or height for bounds." - # //Set adjustment factor for width/height - self.m_AdjustWidth = 1.0 / ((NewWidth - 1.0) * 0.5) - self.m_AdjustHeight = 1.0 / ((NewHeight - 1.0) * 0.5) - - def _mapToSphere (self, NewPt): - # Given a new window coordinate, will modify NewVec in place - X = 0 - Y = 1 - Z = 2 - - NewVec = Vector3fT () - # //Copy paramter into temp point - TempPt = copy.copy (NewPt) - # //Adjust point coords and scale down to range of [-1 ... 1] - TempPt [X] = (NewPt [X] * self.m_AdjustWidth) - 1.0 - TempPt [Y] = 1.0 - (NewPt [Y] * self.m_AdjustHeight) - # //Compute the square of the length of the vector to the point from the center - - length = Numeric.sum (Numeric.dot (TempPt, TempPt)) - # //If the point is mapped outside of the sphere... (length > radius squared) - if (length > 1.0): - # //Compute a normalizing factor (radius / sqrt(length)) - norm = 1.0 / sqrt (length); - - # //Return the "normalized" vector, a point on the sphere - NewVec [X] = TempPt [X] * norm; - NewVec [Y] = TempPt [Y] * norm; - NewVec [Z] = 0.0; - else: # //Else it's on the inside - # //Return a vector to a point mapped inside the sphere sqrt(radius squared - length) - NewVec [X] = TempPt [X] - NewVec [Y] = TempPt [Y] - NewVec [Z] = sqrt (1.0 - length) - - return NewVec - - def click (self, NewPt): - # //Mouse down (Point2fT - self.m_StVec = self._mapToSphere (NewPt) - self.m_vecStartPoint = copy.copy(NewPt) - return - - def pan(self, NewPt): - pass - - def zoom(self, vecNewPoint): - fZoomAmt = (vecNewPoint[1] - self.m_vecStartPoint[1]) / 200.0 - - - return fZoomAmt - - pass - - def drag (self, NewPt): - # //Mouse drag, calculate rotation (Point2fT Quat4fT) - """ drag (Point2fT mouse_coord) -> new_quaternion_rotation_vec - """ - X = 0 - Y = 1 - Z = 2 - W = 3 - - self.m_EnVec = self._mapToSphere (NewPt) - - # //Compute the vector perpendicular to the begin and end vectors - # Perp = Vector3fT () - Perp = Vector3fCross(self.m_StVec, self.m_EnVec); - - NewRot = Quat4fT () - # //Compute the length of the perpendicular vector - if (Vector3fLength(Perp) > Epsilon): # //if its non-zero - # //We're ok, so return the perpendicular vector as the transform after all - NewRot[X] = Perp[X]; - NewRot[Y] = Perp[Y]; - NewRot[Z] = Perp[Z]; - # //In the quaternion values, w is cosine (theta / 2), where theta is rotation angle - NewRot[W] = Vector3fDot(self.m_StVec, self.m_EnVec); - else: # //if its zero - # //The begin and end vectors coincide, so return a quaternion of zero matrix (no rotation) - NewRot[X] = NewRot[Y] = NewRot[Z] = NewRot[W] = 0.0; - - return NewRot - - -# ##################### Math utility ########################################## - - -def Matrix4fT (): - return Numeric.identity (4, 'f') - -def Matrix3fT (): - return Numeric.identity (3, 'f') - -def Quat4fT (): - return Numeric.zeros (4, 'f') - -def Vector3fT (): - return Numeric.zeros (3, 'f') - -def Point2fT (x = 0.0, y = 0.0): - pt = Numeric.zeros (2, 'f') - pt [0] = x - pt [1] = y - return pt - -def Vector3fDot(u, v): - # Dot product of two 3f vectors - dotprod = Numeric.dot (u,v) - return dotprod - -def Vector3fCross(u, v): - # Cross product of two 3f vectors - X = 0 - Y = 1 - Z = 2 - cross = Numeric.zeros (3, 'f') - cross [X] = (u[Y] * v[Z]) - (u[Z] * v[Y]) - cross [Y] = (u[Z] * v[X]) - (u[X] * v[Z]) - cross [Z] = (u[X] * v[Y]) - (u[Y] * v[X]) - return cross - -def Vector3fLength (u): - mag_squared = Numeric.sum(Numeric.dot (u,u)) - mag = sqrt (mag_squared) - return mag - -def Matrix3fSetIdentity (): - return Numeric.identity (3, 'f') - -def Matrix3fMulMatrix3f (matrix_a, matrix_b): - return Numeric.matrixmultiply (matrix_a, matrix_b) - - - - -def Matrix4fSVD (NewObj): - X = 0 - Y = 1 - Z = 2 - s = sqrt ( - ( (NewObj [X][X] * NewObj [X][X]) + (NewObj [X][Y] * NewObj [X][Y]) + (NewObj [X][Z] * NewObj [X][Z]) + - (NewObj [Y][X] * NewObj [Y][X]) + (NewObj [Y][Y] * NewObj [Y][Y]) + (NewObj [Y][Z] * NewObj [Y][Z]) + - (NewObj [Z][X] * NewObj [Z][X]) + (NewObj [Z][Y] * NewObj [Z][Y]) + (NewObj [Z][Z] * NewObj [Z][Z]) ) / 3.0 ) - return s - -def Matrix4fSetRotationScaleFromMatrix3f(NewObj, three_by_three_matrix): - # Modifies NewObj in-place by replacing its upper 3x3 portion from the - # passed in 3x3 matrix. - # NewObj = Matrix4fT () - NewObj [0:3,0:3] = three_by_three_matrix - return NewObj - -# /** -# * Sets the rotational component (upper 3x3) of this matrix to the matrix -# * values in the T precision Matrix3d argument; the other elements of -# * this matrix are unchanged; a singular value decomposition is performed -# * on this object's upper 3x3 matrix to factor out the scale, then this -# * object's upper 3x3 matrix components are replaced by the passed rotation -# * components, and then the scale is reapplied to the rotational -# * components. -# * @param three_by_three_matrix T precision 3x3 matrix -# */ -def Matrix4fSetRotationFromMatrix3f (NewObj, three_by_three_matrix): - scale = Matrix4fSVD (NewObj) - - NewObj = Matrix4fSetRotationScaleFromMatrix3f(NewObj, three_by_three_matrix); - scaled_NewObj = NewObj * scale # Matrix4fMulRotationScale(NewObj, scale); - return scaled_NewObj - -def Matrix3fSetRotationFromQuat4f (q1): - # Converts the H quaternion q1 into a new equivalent 3x3 rotation matrix. - X = 0 - Y = 1 - Z = 2 - W = 3 - - NewObj = Matrix3fT () - n = Numeric.sum (Numeric.dot (q1, q1)) - s = 0.0 - if (n > 0.0): - s = 2.0 / n - xs = q1 [X] * s; ys = q1 [Y] * s; zs = q1 [Z] * s - wx = q1 [W] * xs; wy = q1 [W] * ys; wz = q1 [W] * zs - xx = q1 [X] * xs; xy = q1 [X] * ys; xz = q1 [X] * zs - yy = q1 [Y] * ys; yz = q1 [Y] * zs; zz = q1 [Z] * zs - # This math all comes about by way of algebra, complex math, and trig identities. - # See Lengyel pages 88-92 - NewObj [X][X] = 1.0 - (yy + zz); NewObj [Y][X] = xy - wz; NewObj [Z][X] = xz + wy; - NewObj [X][Y] = xy + wz; NewObj [Y][Y] = 1.0 - (xx + zz); NewObj [Z][Y] = yz - wx; - NewObj [X][Z] = xz - wy; NewObj [Y][Z] = yz + wx; NewObj [Z][Z] = 1.0 - (xx + yy) - - return NewObj - - - -def getScalingV(vecScale): - X = 0 - Y = 1 - Z = 2 - W = 3 - - matScale = Matrix4fT() - matScale[X][X] = vecScale[X] - matScale[Y][Y] = vecScale[Y] - matScale[Z][Z] = vecScale[Z] - matScale[W][W] = 1 - - return matScale - -def getScalingF(fScale): - X = 0 - Y = 1 - Z = 2 - W = 3 - - matScale = Matrix4fT() - matScale[X][X] = fScale - matScale[Y][Y] = fScale - matScale[Z][Z] = fScale - matScale[W][W] = 1 - - return matScale - -def scaleWithVector(matMatrix, vecScale): - matScale = getScalingV(vecScale) - matResult = Matrix3fMulMatrix3f(matMatrix, matScale) - return matResult - -def scaleWithMatrix(matMatrix, matScale): - matResult = Matrix3fMulMatrix3f(matMatrix, matScale) - return matResult - -def unit_test_ArcBall_module (): - # Unit testing of the ArcBall calss and the real math behind it. - # Simulates a click and drag followed by another click and drag. - print "unit testing ArcBall" - Transform = Matrix4fT () - LastRot = Matrix3fT () - ThisRot = Matrix3fT () - - ArcBall = ArcBallT (640, 480) - # print "The ArcBall with NO click" - # print ArcBall - # First click - LastRot = copy.copy (ThisRot) - mouse_pt = Point2fT (500,250) - ArcBall.click (mouse_pt) - # print "The ArcBall with first click" - # print ArcBall - # First drag - mouse_pt = Point2fT (475, 275) - ThisQuat = ArcBall.drag (mouse_pt) - # print "The ArcBall after first drag" - # print ArcBall - # print - # print - print "Quat for first drag" - print ThisQuat - ThisRot = Matrix3fSetRotationFromQuat4f (ThisQuat) - # Linear Algebra matrix multiplication A = old, B = New : C = A * B - ThisRot = Matrix3fMulMatrix3f (LastRot, ThisRot) - Transform = Matrix4fSetRotationFromMatrix3f (Transform, ThisRot) - print "First transform" - print Transform - # Done with first drag - - - # second click - LastRot = copy.copy (ThisRot) - print "LastRot at end of first drag" - print LastRot - mouse_pt = Point2fT (350,260) - ArcBall.click (mouse_pt) - # second drag - mouse_pt = Point2fT (450, 260) - ThisQuat = ArcBall.drag (mouse_pt) - # print "The ArcBall" - # print ArcBall - print "Quat for second drag" - print ThisQuat - ThisRot = Matrix3fSetRotationFromQuat4f (ThisQuat) - ThisRot = Matrix3fMulMatrix3f (LastRot, ThisRot) - # print ThisRot - Transform = Matrix4fSetRotationFromMatrix3f (Transform, ThisRot) - print "Second transform" - print Transform - # Done with second drag - LastRot = copy.copy (ThisRot) - -def _test (): - # This will run doctest's unit testing capability. - # see http://www.python.org/doc/current/lib/module-doctest.html - # - # doctest introspects the ArcBall module for all docstrings - # that look like interactive python sessions and invokes - # the same commands then and there as unit tests to compare - # the output generated. Very nice for unit testing and - # documentation. - import doctest, ArcBall - return doctest.testmod (ArcBall) - #return True - -if __name__ == "__main__": - # Invoke our function that runs python's doctest unit testing tool. - _test () - -# unit_test () diff --git a/PhotoSphere.py b/PhotoSphere.py old mode 100644 new mode 100755 index f0eb59d..ae50be2 --- a/PhotoSphere.py +++ b/PhotoSphere.py @@ -1,188 +1,56 @@ -# This tool is based on NeHe Tutorial Lesson 48 pythonic version -# created by Brian Leair. -# It has been modified by Paolo Angelelli ((c) 2013) -# and released under GPL v2.0 license. -# -# Original Notes: -# ----- -# This code is not an ideal example of Pythonic coding or use of OO -# techniques. It is a simple and direct exposition of how to use the -# Open GL API in Python via the PyOpenGL package. It also uses GLUT, -# a high quality platform independent library. Due to using these APIs, -# this code is more like a C program using procedural programming. -# -# To run this example you will need: -# Python - www.python.org (v 2.3 as of 1/2004) -# PyOpenGL - pyopengl.sourceforge.net (v 2.0.1.07 as of 1/2004) -# Numeric Python - (v.22 of "numpy" as of 1/2004) numpy.sourceforge.net -# -# - -from OpenGL.GL import * -from OpenGL.GLUT import * -from OpenGL.GLU import * +#!/usr/bin/python +import os import sys -from PhotoSphereRenderer import * # Draw (), Initialize () and all the real OpenGL work. -from ArcBall import * # // *NEW* ArcBall header -from PyQt4 import QtGui, QtCore +from renderer import Window -# *********************** Globals *********************** -# Python 2.2 defines these directly -try: - True -except NameError: - True = 1==1 - False = 1==0 +fileUrl = "" -# Some api in the chain is translating the keystrokes to this octal string -# so instead of saying: ESCAPE = 27, we use the following. -ESCAPE = '\033' -# Number of the glut window. -window = 0 +def main(): + Window(640, 480).open(fileUrl) -fileUrl = "" +def qtmain(): + from PyQt4 import QtGui, QtCore + class Example(QtGui.QWidget): + def __init__(self): + super(Example, self).__init__() + self.setWindowFlags(QtCore.Qt.Dialog) -# Reshape The Window When It's Moved Or Resized -def ReSizeGLScene(Width, Height): - if Height == 0: # Prevent A Divide By Zero If The Window Is Too Small - Height = 1 + self.initUI() - glViewport(0, 0, Width, Height) # Reset The Current Viewport And Perspective Transformation - glMatrixMode(GL_PROJECTION) # // Select The Projection Matrix - glLoadIdentity() # // Reset The Projection Matrix - # // field of view, aspect ratio, near and far - # This will squash and stretch our objects as the window is resized. - # Note that the near clip plane is 1 (hither) and the far plane is 1000 (yon) - gluPerspective(g_FOV, float(Width)/float(Height), 0.00, 100.0) + def initUI(self): - glMatrixMode (GL_MODELVIEW); # // Select The Modelview Matrix - glLoadIdentity (); # // Reset The Modelview Matrix - g_ArcBall.setBounds (Width, Height) # //*NEW* Update mouse bounds for arcball - return + self.btn = QtGui.QPushButton('OK', self) + self.btn.move(20, 20) + self.btn.clicked.connect(self.showDialog) + self.le = QtGui.QLineEdit(self) + self.le.move(130, 22) -# The function called whenever a key is pressed. Note the use of Python tuples to pass in: (key, x, y) -def keyPressed(*args): - global g_quadratic - # If escape is pressed, kill everything. - key = args [0] - if key == ESCAPE: - gluDeleteQuadric (g_quadratic) - sys.exit () + self.fdBtn = QtGui.QPushButton('Choose', self) + self.fdBtn.move(280, 20) + self.fdBtn.clicked.connect(self.chooseFile) + self.setGeometry(100, 100, 390, 60) + self.setWindowTitle('Input dialog') + self.show() + def chooseFile(self): + sFileName = QtGui.QFileDialog.getOpenFileName(self, "Open File", "","Files (*.*)" ) + if not sFileName: return + self.le.setText("file:///"+sFileName) + self.showDialog() + + def showDialog(self): + global fileUrl + text = (str(self.le.text())) + fileUrl = text + QtCore.QCoreApplication.instance().quit() -def main(): - global window - # pass arguments to init - glutInit(sys.argv) - - # Select type of Display mode: - # Double buffer - # RGBA color - # Alpha components supported - # Depth buffer - glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) - - # get a 640 x 480 window - glutInitWindowSize(640, 480) - - # the window starts at the upper left corner of the screen - glutInitWindowPosition(0, 0) - - # Okay, like the C version we retain the window id to use when closing, but for those of you new - # to Python, remember this assignment would make the variable local and not global - # if it weren't for the global declaration at the start of main. - window = glutCreateWindow("PhotoSphere Python viewer -- (c) Paolo Angelelli 2013") - - # Register the drawing function with glut, BUT in Python land, at least using PyOpenGL, we need to - # set the function pointer and invoke a function to actually register the callback, otherwise it - # would be very much like the C version of the code. - glutDisplayFunc(Draw) - - # Uncomment this line to get full screen. - #glutFullScreen() - - # When we are doing nothing, redraw the scene. - glutIdleFunc(Draw) - - # Register the function called when our window is resized. - glutReshapeFunc(ReSizeGLScene) - - # Register the function called when the keyboard is pressed. - glutKeyboardFunc(keyPressed) - - - # GLUT When mouse buttons are clicked in window - glutMouseFunc (Upon_Click) - - # GLUT When the mouse mvoes - glutMotionFunc (Upon_Drag) - - - # We've told Glut the type of window we want, and we've told glut about - # various functions that we want invoked (idle, resizing, keyboard events). - # Glut has done the hard work of building up thw windows DC context and - # tying in a rendering context, so we are ready to start making immediate mode - # GL calls. - # Call to perform inital GL setup (the clear colors, enabling modes - Initialize (640, 480, fileUrl) - - # Start Event Processing Engine - glutMainLoop() - -# Print message to console, and kick off the main to get it rolling. - - - - - - - - -class Example(QtGui.QWidget): - - def __init__(self): - super(Example, self).__init__() - - self.initUI() - - def initUI(self): - - self.btn = QtGui.QPushButton('OK', self) - self.btn.move(20, 20) - self.btn.clicked.connect(self.showDialog) - - self.le = QtGui.QLineEdit(self) - self.le.move(130, 22) - - self.fdBtn = QtGui.QPushButton('Choose', self) - self.fdBtn.move(280, 20) - self.fdBtn.clicked.connect(self.chooseFile) - - self.setGeometry(300, 300, 390, 150) - self.setWindowTitle('Input dialog') - self.show() - - def chooseFile(self): - sFileName = QtGui.QFileDialog.getOpenFileName(self, "Open File", "","Files (*.*)" ) - self.le.setText("file:///"+sFileName) - self.showDialog() - - def showDialog(self): - global fileUrl - text = (str(self.le.text())) - fileUrl = text - QtCore.QCoreApplication.instance().quit() - - -def qtmain(): - app = QtGui.QApplication(sys.argv) ex = Example() ret = app.exec_() @@ -192,29 +60,17 @@ def qtmain(): if __name__ == '__main__': - ret = qtmain() - # Your code that must run when the application closes goes here - main() + if len(sys.argv) > 1: + if os.path.isfile(sys.argv[1]): + fileUrl = "file://{0}".format(os.path.abspath(sys.argv[1])) + elif sys.argv[1].startswith("file:///"): + fileUrl = sys.argv[1] + else: + ret = qtmain() + main() + ret = 0 + else: + ret = qtmain() + # Your code that must run when the application closes goes here + main() sys.exit(ret) - - - - - - - - - - - - - - - - - - -if __name__ == "__main__": - print "Hit ESC key to quit." - main() - diff --git a/PhotoSphereRenderer.py b/PhotoSphereRenderer.py deleted file mode 100644 index 9b43d6c..0000000 --- a/PhotoSphereRenderer.py +++ /dev/null @@ -1,320 +0,0 @@ - -from OpenGL.GL import * -from OpenGL.GLUT import * -from OpenGL.GLU import * -import sys -import copy -from math import cos, sin -import numpy -import PIL.Image as Image -import urllib, cStringIO - -from ArcBall import * # ArcBallT and this tutorials set of points/vectors/matrix types - -PI2 = 2.0*3.1415926535 # 2 * PI (not squared!) // PI Squared - -# *********************** Globals *********************** -# Python 2.2 defines these directly -try: - True -except NameError: - True = 1==1 - False = 1==0 - -g_Transform = Matrix4fT () -g_Rotation = Matrix4fT () -g_Translation = Matrix4fT () -g_Scaling = Matrix4fT () -g_initialRotation = Matrix3fT() - - - -if 1: - #added - g_quaternion = Quat4fT() - g_quaternion[0] = sqrt(0.5) - g_quaternion[1] = 0 - g_quaternion[2] = 0 - g_quaternion[3] = sqrt(0.5) - g_initialRotation = Matrix3fSetRotationFromQuat4f(g_quaternion) - g_Transform = Matrix4fSetRotationFromMatrix3f(g_Transform, g_initialRotation) - - - -g_LastRot = Matrix3fT () -g_ThisRot = Matrix3fT () -g_fLastScale = 1.0 -g_fThisScale = 1.0 -g_vecLastTranslation = Vector3fT() -g_vecThisTranslation = Vector3fT() - -if 1: - #added - g_ThisRot = Matrix3fSetRotationFromQuat4f(g_quaternion) - -g_ArcBall = ArcBallT (640, 480) -g_isDragging = False -g_rightDragging = False -g_midDragging = False -g_quadratic = None -g_sphereRadius = 0.1 -g_FOV = 75.0 -g_textures = numpy.zeros(1,numpy.int32) - -g_texWidth = 0 -g_texHeight = 0 -g_texData = 0 - -g_pctOffset=0.15 - - -def loadImage( imageName = "/local/photo/SR71.jpg" ): # "/local/photo/montagna.jpg" ): - global g_texWidth - global g_texHeight - global g_texData - - - - file = cStringIO.StringIO(urllib.urlopen(imageName).read()) - im = Image.open(file) - #im = Image.open(imageName) -# try: -# g_texWidth, g_texHeight, g_texData = im.size[0], im.size[1], im.tostring("raw", "RGBA", 0, -1) -# except SystemError: -# g_texWidth, g_texHeight, g_texData = im.size[0], im.size[1], im.tostring("raw", "RGBX", 0, -1) -# -# -# im = Image.open("/home/paolo/Desktop/GLBALL/GLball/wall01.tga") - width, height = im.size - - pixels = im.getdata() - pixelData = im.load() - - c = numpy.zeros(width * int (height * (1 + 2*g_pctOffset)) * 3, numpy.uint8) - rowSkip = int (height*g_pctOffset) - - curHeight = int (height * (1+2*g_pctOffset)) - - for hj in range(curHeight): - for i in range(width): - j = hj - rowSkip - if (j >= 0) and (j < height): - d = pixelData[i,j] - c[3*(hj*width + i) ] = d[0] - c[3*(hj*width + i)+1 ] = d[1] - c[3*(hj*width + i)+2 ] = d[2] - - g_texWidth, g_texHeight, g_texData = width, height, c - g_texWidth, g_texHeight, g_texData = width, curHeight, c - -# imOutput = Image.new( 'RGB', (width,height), "black") # create a new black image -# pixelsOutput = imOutput.load() # create the pixel map -# for j in range(height): -# for i in range(width): -# pixelsOutput[i,j] = (c[3*(j*width + i) ], c[3*(j*width + i)+1 ], c[3*(j*width + i)+2 ]) -# -# -# imOutput.show() - -# A general OpenGL initialization function. Sets all of the initial parameters. -def Initialize (Width, Height, fileUrl): # We call this right after our OpenGL window is created. - global g_quadratic - global g_textures - - #loadImage("file:///local/www/web/wci/uppete/ups/PANO_20130727_160023403796945.jpg") - #loadImage("http://bonas.us/uppete/ups/PANO_20130727_160023403796945.jpg") - loadImage(fileUrl) - - - - glClearColor(0.0, 0.0, 0.0, 1.0) # This Will Clear The Background Color To Black - glClearDepth(1.0) # Enables Clearing Of The Depth Buffer - glDepthFunc(GL_LEQUAL) # The Type Of Depth Test To Do - glEnable(GL_DEPTH_TEST) # Enables Depth Testing - glShadeModel (GL_FLAT); # Select Flat Shading (Nice Definition Of Objects) - #glShadeModel (GL_SMOOTH); # Select Flat Shading (Nice Definition Of Objects) - glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) # Really Nice Perspective Calculations - - g_quadratic = gluNewQuadric(); - gluQuadricOrientation(g_quadratic,GLU_INSIDE) - gluQuadricOrientation(g_quadratic,GLU_OUTSIDE) - gluQuadricNormals(g_quadratic, GLU_SMOOTH); - gluQuadricDrawStyle(g_quadratic, GLU_FILL); - gluQuadricTexture(g_quadratic, GL_TRUE); - # Why? this tutorial never maps any textures?! ? - # gluQuadricTexture(g_quadratic, GL_TRUE); # // Create Texture Coords - - glEnable (GL_LIGHT0) - glEnable (GL_LIGHTING) - - glEnable (GL_COLOR_MATERIAL) - - - glEnable ( GL_TEXTURE_2D ); - glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 ); - glGenTextures (1, g_textures); - - glBindTexture ( GL_TEXTURE_2D, g_textures[0] ); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); - glTexImage2D( - GL_TEXTURE_2D, 0, GL_RGB, g_texWidth, g_texHeight, 0, - GL_RGB, GL_UNSIGNED_BYTE, g_texData - ) - - return True - - - - -def Upon_Drag (cursor_x, cursor_y): - """ Mouse cursor is moving - Glut calls this function (when mouse button is down) - and pases the mouse cursor postion in window coords as the mouse moves. - """ - global g_isDragging, g_rightDragging, g_midDragging, g_LastRot, g_Transform, g_ThisRot, g_fLastScale, g_fThisScale , g_vecLastTranslation, g_vecThisTranslation - global g_Rotation, g_Translation, g_Scaling - - mouse_pt = Point2fT (cursor_x, cursor_y) - if (g_isDragging): - #print "g_leftDragging" - - ThisQuat = g_ArcBall.drag (mouse_pt) # // Update End Vector And Get Rotation As Quaternion - g_ThisRot = Matrix3fSetRotationFromQuat4f (ThisQuat) # // Convert Quaternion Into Matrix3fT - # Use correct Linear Algebra matrix multiplication C = A * B - g_ThisRot = Matrix3fMulMatrix3f (g_LastRot, g_ThisRot) # // Accumulate Last Rotation Into This One - g_Rotation = Matrix4fSetRotationFromMatrix3f (g_Transform, g_ThisRot) # // Set Our Final Transform's Rotation From This One - elif (g_midDragging): - #print "g_midDragging" - #panning = translation - pass - elif (g_rightDragging): - #print "g_rightDragging" - fZoomAmt = g_ArcBall.zoom(mouse_pt) - fZoom = g_fLastScale + fZoomAmt - fZoom = numpy.clip(10.0,0.05, fZoom) - g_fLastScale = fZoom - - g_Scaling = getScalingF(fZoom) - - - pass - - g_Transform = copy.copy (g_Rotation) - g_Transform = Matrix3fMulMatrix3f(g_Transform, g_Translation) - g_Transform = Matrix3fMulMatrix3f(g_Transform, g_Scaling) - return - -def Upon_Click (button, button_state, cursor_x, cursor_y): - """ Mouse button clicked. - Glut calls this function when a mouse button is - clicked or released. - """ - global g_isDragging, g_rightDragging, g_midDragging, g_LastRot, g_Transform, g_ThisRot - - g_isDragging = g_rightDragging = g_midDragging = False - if (button == GLUT_RIGHT_BUTTON and button_state == GLUT_UP): - # Right button click - g_rightDragging = False - ##g_LastRot = Matrix3fSetIdentity (); # // Reset Rotation - ##g_ThisRot = Matrix3fSetIdentity (); # // Reset Rotation - ##g_Transform = Matrix4fSetRotationFromMatrix3f (g_Transform, g_ThisRot); # // Reset Rotation - elif (button == GLUT_RIGHT_BUTTON and button_state == GLUT_DOWN): - g_rightDragging = True - mouse_pt = Point2fT (cursor_x, cursor_y) - g_ArcBall.click (mouse_pt); - pass - elif (button == GLUT_MIDDLE_BUTTON and button_state == GLUT_UP): - g_midDragging = False - pass - elif (button == GLUT_MIDDLE_BUTTON and button_state == GLUT_DOWN): - mouse_pt = Point2fT (cursor_x, cursor_y) - g_ArcBall.click (mouse_pt); - g_midDragging = True - pass - elif (button == GLUT_LEFT_BUTTON and button_state == GLUT_UP): - # Left button released - g_LastRot = copy.copy (g_ThisRot); # // Set Last Static Rotation To Last Dynamic One - elif (button == GLUT_LEFT_BUTTON and button_state == GLUT_DOWN): - # Left button clicked down - g_LastRot = copy.copy (g_ThisRot) # // Set Last Static Rotation To Last Dynamic One - g_isDragging = True # // Prepare For Dragging - mouse_pt = Point2fT (cursor_x, cursor_y) - g_ArcBall.click (mouse_pt); # // Update Start Vector And Prepare For Dragging - return - - - -def Torus(MinorRadius, MajorRadius): - # // Draw A Torus With Normals - glBegin( GL_TRIANGLE_STRIP ); # // Start A Triangle Strip - for i in xrange (20): # // Stacks - for j in xrange (-1, 20): # // Slices - # NOTE, python's definition of modulus for negative numbers returns - # results different than C's - # (a / d)*d + a % d = a - if (j < 0): - wrapFrac = (-j%20)/20.0 - wrapFrac *= -1.05 - else: - wrapFrac = (j%20)/20.0; - phi = PI2*wrapFrac; - sinphi = sin(phi); - cosphi = cos(phi); - - r = MajorRadius + MinorRadius*cosphi; - - glNormal3f (sin(PI2*(i%20+wrapFrac)/20.0)*cosphi, sinphi, cos(PI2*(i%20+wrapFrac)/20.0)*cosphi); - glVertex3f (sin(PI2*(i%20+wrapFrac)/20.0)*r, MinorRadius*sinphi, cos(PI2*(i%20+wrapFrac)/20.0)*r); - - glNormal3f (sin(PI2*(i+1%20+wrapFrac)/20.0)*cosphi, sinphi, cos(PI2*(i+1%20+wrapFrac)/20.0)*cosphi); - glVertex3f (sin(PI2*(i+1%20+wrapFrac)/20.0)*r, MinorRadius*sinphi, cos(PI2*(i+1%20+wrapFrac)/20.0)*r); - glEnd(); # // Done Torus - return - -def Draw (): - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); # // Clear Screen And Depth Buffer - glLoadIdentity(); - if 0: # // Reset The Current Modelview Matrix - glTranslatef(-1.5,0.0,-6.0); # // Move Left 1.5 Units And Into The Screen 6.0 - - glPushMatrix(); # // NEW: Prepare Dynamic Transform - glMultMatrixf(g_Transform); # // NEW: Apply Dynamic Transform - glColor3f(0.75,0.75,1.0); - Torus(0.30,1.00); - glPopMatrix(); # // NEW: Unapply Dynamic Transform - - glLoadIdentity(); # // Reset The Current Modelview Matrix - glTranslatef(0.0,0.0,-0.30); # // Move Right 1.5 Units And Into The Screen 7.0 - - glDisable(GL_LIGHTING) - glDisable(GL_CULL_FACE) - glPushMatrix(); # // NEW: Prepare Dynamic Transform - glMultMatrixf(g_Transform); # // NEW: Apply Dynamic Transform - glColor3f(1.0,0.75,0.75); - glColor3f(1.0,1.0,1.0); - #glDisable(GL_TEXTURE_2D) - glEnable(GL_TEXTURE_2D) - glBindTexture ( GL_TEXTURE_2D, g_textures[0] ); - if 1: - gluSphere(g_quadratic,g_sphereRadius,50,50); - else: - glBegin(GL_QUADS); - glColor4f(1.0,1.0,1.0,1.0); - glNormal3f(0,0,1); - glTexCoord2f(0,0); - glVertex3f(-1,-1,0); - glTexCoord2f(1,0); - glVertex3f(1,-1,0); - glTexCoord2f(1,1); - glVertex3f(1,1,0); - glTexCoord2f(0,1); - glVertex3f(-1,1,0); - glEnd(); - glBindTexture(GL_TEXTURE_2D, 0) - glPopMatrix(); # // NEW: Unapply Dynamic Transform - - glFlush (); # // Flush The GL Rendering Pipeline - glutSwapBuffers() - return - diff --git a/README.md b/README.md index f2b2655..1d8aaf1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ PythonPhotoSphere ================= -QAD Cross-Platform PhotoSphere viewer written in Python, based on a NeHe example +[PhotoSphere](https://google-developers.appspot.com/photo-sphere/) viewer written in Python. + +Install PyOpenGL to use: + + sudo easy_install PyOpenGL + + + diff --git a/install_nemo_action.sh b/install_nemo_action.sh new file mode 100755 index 0000000..d8a5d0a --- /dev/null +++ b/install_nemo_action.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cp *.nemo_action $HOME/.local/share/nemo/actions diff --git a/photo_sphere.nemo_action b/photo_sphere.nemo_action new file mode 100644 index 0000000..fa8262e --- /dev/null +++ b/photo_sphere.nemo_action @@ -0,0 +1,99 @@ +[Nemo Action] + +############################################# +#### DEBUGGING: +#### +#### Run Nemo with the environment +#### variable NEMO_ACTION_VERBOSE +#### set to get useful log output +#### for debugging your actions +#### +#### i.e. $ nemo --quit +#### $ NEMO_ACTION_VERBOSE=1 nemo +############################################# + +# Whether this action is active. For troubleshooting. +# Optional - if this field is omitted, the action will be active +Active=true + +# Standard tokens that can be used in the Name, Comment (tooltip) and Exec fields: +# +# %U - insert URI list of selection +# %F - insert path list of selection +# %P - insert path of parent (current) directory +# %f or %N (deprecated) - insert display name of first selected file +# %p - insert display name of parent directory +# %D - insert device path of file (i.e. /dev/sdb1) + + +# The name to show in the menu, locale supported with standard desktop spec. +# **** REQUIRED **** +Name=PhotoSphere + +# Tool tip, locale supported (Appears in the status bar) +Comment=Loads a photosphere image in PhotoSphere + +# What to run. Enclose in < > to run an executable that resides in the actions folder. +# **** REQUIRED **** +Exec=/home/bernd/workspace/PythonPhotoSphere/PhotoSphere.py %F + +# Icon name to use in the menu - must be a theme icon name +Icon-Name=image + +# Gtk Stock ID to use for the icon. Note if both Icon-name and Stock-Id are +# defined, the Stock-Id takes precedence. +#Stock-Id=gtk-cdrom + +# What type selection: [s]ingle, [m]ultiple, any, notnone, none (background click), or +# a number representing how many files must be selected to display. +# ****** REQUIRED ******* +Selection=s + +# What extensions to display on - this is an array, end with a semicolon +# Single entry options, ending in a semicolon: +# "dir" for directory selection +# "none" for no extension. +# "nodirs" for any selection, but not including directories. +# "any" for any file type, including directories. +# Individual specific extensions can be a semicolon-terminated list +# Extensions are NOT case sensitive. jpg will match JPG, jPg, jpg, etc.. +# **** EITHER EXTENSIONS OR MIMETYPES IS REQUIRED ***** +Extensions=jpg; + +# What mime-types to display on - this is an array, end with a semicolon +# **** EITHER EXTENSIONS OR MIMETYPES IS REQUIRED ***** +#Mimetypes=text/plain; + +# Separator to use (if any) - add a string to insert between path/url entries +# in the exec line. Optional - if you leave this out, a space is inserted. +# Note you can have trailing spaces here. +#Separator=, + +# Quote type to use (if any) - enclose paths/urls with quotes. Optional - defaults +# to no quotes. +# Can be: single, double, backtick +Quote=double + +# Dependencies - program executables required for this action to work. Nemo will +# Search in the path for these program(s) and not display the action if any are missing. +# You can also supply an absolute path to a file (i.e. /usr/lib/gvfs/gvfsd-archive) to check +# instead of or in addition to an executable in the path. +# This is an array, separate entries with semi-colon, and terminate with a semicolon. +#Dependencies=$HOME/workspace/MusicFixer/tagalyzer.py; + +# Conditions - semicolon-separated array of special conditions: +# "desktop" current (parent) folder is desktop +# "removable" target (first selection) is removable +# "gsettings " is true +# "gsettings <[eq|ne|gt|lt]> " +# "dbus " exists + +#Conditions=desktop; + +# Escape Spaces - set to true to escape spaces in filenames and uris ($U, $F, $P, $D) +# +# Sometimes this may be preferred to getting raw filenames that must be enclosed in +# quotes. +# +# Optional - by default this is false +EscapeSpaces=false diff --git a/renderer.py b/renderer.py new file mode 100644 index 0000000..40ac48e --- /dev/null +++ b/renderer.py @@ -0,0 +1,254 @@ + +from OpenGL.GL import * +from OpenGL.GLUT import * +from OpenGL.GLU import * + +import os +import sys +import copy +from math import cos, sin, pi +from traceback import print_exc + +import numpy +import PIL.Image as Image +import urllib, cStringIO + +from vector import Vector + +MAX_RADIUS_SCALE_RATIO = 0.9 +pi2 = 2.0 * pi + +# Some api in the chain is translating the keystrokes to this octal string +# so instead of saying: ESCAPE = 27, we use the following. +ESCAPE = '\033' + + +class drug(object): + def __init__(self, **kwargs): + for k in kwargs: + setattr(self, k, kwargs[k]) + + +class Renderer(object): + + def __init__(self, width, height): + self.size(width, height) + self.initialized = False + self.rot = drug(x = 90, z = 0) + v = Vector(width * 0.5, height * 0.5, 0) + self.mouse = dict( + left = drug(pos = v, pressed = False), + right = drug(pos = v.copy(), pressed = False), + middle = drug(pos = v.copy(), pressed = False)) + # gl stuff + self.textures = numpy.zeros(1, numpy.int32) + self.bgcolor = (0.0, 0.0, 0.0, 1.0) + self.color = (1.0, 1.0, 1.0) + self.tex = drug(width = 0, height = 0, data = 0) + self.pct = drug(offset = 0.15) + self.radius = 0.1 + self.scale = 0.0 # actually the way towards the sphere surface + self.maxScale = self.radius * MAX_RADIUS_SCALE_RATIO + self.depth = 1.0 + + def size(self, width, height): + self.height = float(height) + self.width = float(width) + + def load(self, filename): + if os.path.isfile(filename): + file = "file://{0}".format(os.path.abspath(filename)) + else: + file = cStringIO.StringIO(urllib.urlopen(filename).read()) + im = Image.open(file) + + im.thumbnail((8192, 4096)) + + width, height = im.size + c = numpy.asarray(im, numpy.uint8) + self.tex.width = width + self.tex.height = height + self.tex.data = c + #self.tex.data = numpy.asarray(im, dtype = 'uint8') + + def initialize(self): + if self.initialized: return + self.initialized = True + glClearColor(*self.bgcolor) + glClearDepth(self.depth) + glDepthFunc(GL_LEQUAL) + glEnable(GL_DEPTH_TEST) + glShadeModel(GL_FLAT) + glShadeModel (GL_SMOOTH) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) + + self.quadratic = gluNewQuadric() + gluQuadricOrientation(self.quadratic, GLU_INSIDE) + gluQuadricOrientation(self.quadratic, GLU_OUTSIDE) + gluQuadricNormals( self.quadratic, GLU_SMOOTH) + gluQuadricDrawStyle( self.quadratic, GLU_FILL) + gluQuadricTexture( self.quadratic, GL_TRUE) + # ---- + glEnable(GL_LIGHT0) + glEnable(GL_LIGHTING) + glEnable(GL_COLOR_MATERIAL) + glEnable(GL_TEXTURE_2D) + # ---- + glPixelStorei(GL_UNPACK_ALIGNMENT, 1) + glGenTextures(1, self.textures) + # ---- + glBindTexture( GL_TEXTURE_2D, self.textures[0]) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, + self.tex.width, self.tex.height, 0, + GL_RGB, GL_UNSIGNED_BYTE, self.tex.data) + + def destroy(self): + if not self.initialized: return + self.initialized = False + gluDeleteQuadric(self.quadratic) + + def draw(self): + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glLoadIdentity() + glDisable(GL_LIGHTING) + glDisable(GL_NORMALIZE) + glDisable(GL_CULL_FACE) + # ---- + glPushMatrix() + glTranslatef(0, 0, self.scale) + glRotatef(self.rot.x, 1, 0, 0) + glRotatef(self.rot.z, 0, 0, 1) + glColor3f(*self.color) + gluSphere(self.quadratic, self.radius, 50, 50) + glPopMatrix() + # ---- + glFlush() + glutSwapBuffers() + + def onClick(self, button, button_state, cursor_x, cursor_y): + # cursor position + if button == GLUT_LEFT_BUTTON: + b = 'left' + elif button == GLUT_RIGHT_BUTTON: + b = 'right' + elif button == GLUT_MIDDLE_BUTTON: + b = 'middle' + else: b = None + if b is not None: + self.mouse[b].pos.set(cursor_x, cursor_y, 0) + # button state + if button_state == GLUT_UP: + self.mouse[b].pressed = False + elif button_state == GLUT_DOWN: + self.mouse[b].pressed = True + elif button in [3, 4]: # wheel event + # button state + if button == 3: + self.scale += 0.001 + elif button == 4: + self.scale -= 0.001 + # bounderies + while self.scale < -self.radius: self.scale = -self.radius + while self.scale > self.radius: self.scale = self.radius + + def onDrag(self, cursor_x, cursor_y): + if self.mouse['left'].pressed: + self.rot.x += self.radius * 0.5 * (self.mouse['left'].pos.y - cursor_y) + self.rot.z += self.radius * (cursor_x - self.mouse['left'].pos.x) + self.mouse['left'].pos.set(cursor_x, cursor_y, 0) + self.clampRotation() + if self.mouse['middle'].pressed: + self.scale += 0.002 * (self.mouse['middle'].pos.y - cursor_y) + self.mouse['middle'].pos.set(cursor_x, cursor_y, 0) + self.clampScale() + + def onSpecial(self, f, x = 0, y = 0): + if f == GLUT_KEY_UP: + self.rot.x -= 10 + if f == GLUT_KEY_DOWN: + self.rot.x += 10 + if f == GLUT_KEY_LEFT: + self.rot.z += 10 + if f == GLUT_KEY_RIGHT: + self.rot.z -= 10 + if f == GLUT_KEY_PAGE_UP: + self.scale += 0.01 + if f == GLUT_KEY_PAGE_DOWN: + self.scale -= 0.01 + if f == GLUT_KEY_HOME: + self.scale - 1 + self.clampRotation() + self.clampScale() + + def clampRotation(self): + if self.rot.x < 0: self.rot.x = 0 + if self.rot.x > 180: self.rot.x = 180 + while self.rot.z < 0: self.rot.z += 360 + while self.rot.z > 360: self.rot.z -= 360 + + def clampScale(self): + if self.scale < -self.maxScale: self.scale = -self.maxScale + if self.scale > self.maxScale: self.scale = self.maxScale + +class Window(object): + + def __init__(self, width, height): + self.renderer = Renderer(width, height) + self.size(width, height) + self.id = None + # gl stuff + self.fov = 75.0 + + def size(self, width, height): + self.height = int(height) + self.width = int(width) + + def resize(self, width, height): + if height == 0: height = 1 + self.size(width, height) + self.renderer.size(width, height) + glViewport(0, 0, self.width, self.height) + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluPerspective(self.fov, + self.renderer.width / self.renderer.height, + 0.0, 100.0) + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + + def onKeyPressed(self, *keys): + if '0' in keys: + self.renderer.scale = 0.0 + if '+' in keys: + self.renderer.onSpecial(GLUT_KEY_PAGE_UP) + if '-' in keys: + self.renderer.onSpecial(GLUT_KEY_PAGE_DOWN) + if ESCAPE in keys or 'q' in keys: + self.close() + + def close(self): + self.renderer.destroy() + sys.exit() + + def open(self, filename, argv = sys.argv): + glutInit(argv) + glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) + glutInitWindowSize(self.width, self.height) + glutInitWindowPosition(0, 0) + self.id = glutCreateWindow("PhotoSphere Python viewer") + glutDisplayFunc(self.renderer.draw) + glutIdleFunc(self.renderer.draw) + glutReshapeFunc(self.resize) + glutKeyboardFunc(self.onKeyPressed) + glutSpecialFunc(self.renderer.onSpecial) + glutMouseFunc(self.renderer.onClick) + glutMotionFunc(self.renderer.onDrag) + try: + self.renderer.load(filename) + except: + print_exc() + else: + self.renderer.initialize() + glutMainLoop() diff --git a/vector.py b/vector.py new file mode 100644 index 0000000..cbe47b2 --- /dev/null +++ b/vector.py @@ -0,0 +1,156 @@ +from math import sqrt, atan2, cos, sin + +class Vector(object): + def __init__(self, *args, **kwargs): + super(Vector, self).__init__() + _ = None + values = kwargs.get('values', _) + x, y, z = kwargs.get('x', _), kwargs.get('y', _), kwargs.get('z', _) + if values is not None: self.set(values) + elif x is not None or y is not None or z is not None: + self.set(x or 0,y or 0,z or 0) + else: + self.set(*args) + if not hasattr(self, 'x'): self.reset() + + @staticmethod + def from_polar_xy(angle, length): + return from_polar_xy(angle, length) + + def set(self, *args): + a = None + if len(args) == 1: + if type(args[0]) is tuple: args = args[0] + if len(args) == 0: return self.reset() + elif isinstance(args[0], Vector): a = args[0].tuple() + if len(args) == 0: return self.reset() + elif len(args) == 3: a = args + if a is not None: self.x, self.y, self.z = a + return self + + def reset(self): + return self.set(0, 0, 0) + + def copy(self): + return Vector(*self.tuple()) + + def __add__(self, other): + return vadd(self, other) + + def __sub__(self, other): + return vsub(self, other) + + def __mul__(self, x): + if isinstance(x, Vector): + return vcross(self, x) + return vmul(x, self) + + def __truediv__(self, x): + return vmul(1/x, self) + + def __eq__(self, other): + return isinstance(other, Vector) and \ + self.x == other.x and self.y == other.y and self.z == other.z + + def __repr__(self): + return "".format(self.x, self.y, self.z) + + def __neg__(self): + return Vector(-self.x, -self.y, -self.z) + + def tuple(self): + return (self.x, self.y, self.z) + + def length(self): + return vlength(self) + + def with_length(self, l): + return self.normalize() * l + + def direction(self): + return direction(self) + + def direction_to(self, other): + return direction_to(self, other) + + def distance(self, other): + return distance(self, other) + + def sq_distance(self, other): + return squared_distance(self, other) + + def normalize(self): + return self.set(vnormalize(self)) + + def dot(self, other): + return vdot(self, other) + +def vsub(a, b): + return Vector(a.x - b.x, a.y - b.y, a.z - b.z) + +def vadd(a, b): + return Vector(a.x + b.x, a.y + b.y, a.z + b.z) + +def vmul(x, v): + return Vector(x * v.x, x * v.y, x * v.z) + +def vcross(a, b): + return Vector(a.y * b.z - a.z * b.y, + a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x) + +def direction(a): + return atan2(a.y, a.x) + +def direction_to(a, b): + return atan2(b.y - a.y, b.x - a.x) + +def sqvlength(v): + return vdot(v,v) + +def vlength(v): + return sqrt(sqvlength(v)) + +def distance(a, b): + return vlength(vsub(a, b)) + +def squared_distance(a, b): + return sqvlength(vsub(a, b)) + +def vnormalize(v): + return vmul(1/vlength(v), v) + +def from_polar_xy(angle, length): + return Vector( + x = cos(angle) * length, + y = sin(angle) * length, + z = 0) + +def vdot(a, b): # alias scalar + return a.x * b.x + a.y * b.y + a.z * b.z + +def intersection_point(point1, direction1, point2, direction2): + dir_cross = direction1 * direction2 + proof = (point2 - point1) * direction2 + multiplier = list(map(lambda t: None if t[0] == 0 else t[1] / t[0] , + zip(dir_cross.tuple(),proof.tuple()))) + if not None in multiplier: + mr = list(map(lambda f: round(f,13) ,multiplier)) + if len(set(mr)) == 1: + return point1 + (direction1 * multiplier[0]) + return None + +def minimal_distance_between_straights(point1, direction1, point2, direction2): + return abs(vdot(point2-point1, (direction1*direction2).normalize() )) + +def straight_distance(point1, direction1, point2, direction2): + u, v, w = direction1, direction2, point1 - point2 + a, b, c, d, e = u.dot(u), u.dot(v), v.dot(v), u.dot(w), v.dot(w) + D = a * c - b * b + if D < 0.00000000001: + sc, tc = 0.0, d/b if b>c else e/c + else: + sc, tc = (b * e - c * d) / D, (a * e - b * d) / D + return (point1 + u*sc , point2 + v*tc) + +