├── .gitignore ├── .landscape.yaml ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── data └── pyGameMathLogo.png ├── gem ├── __init__.py ├── common.py ├── experimental │ ├── __init__.py │ ├── bezier.py │ ├── legendre.py │ ├── sph.py │ ├── sph_irradiance_map.py │ ├── sph_object.py │ └── sph_sample.py ├── matrix.py ├── plane.py ├── quaternion.py ├── ray.py └── vector.py ├── launcher.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | dist/ 6 | gem.egg-info/ 7 | build/ 8 | 9 | .pypirc 10 | setup.sh 11 | setup.bat 12 | -------------------------------------------------------------------------------- /.landscape.yaml: -------------------------------------------------------------------------------- 1 | doc-warnings: no 2 | test-warnings: no 3 | strictness: medium 4 | max-line-length: 180 5 | 6 | pylint: 7 | disable: 8 | - too-many-arguments 9 | - too-many-locals 10 | 11 | pep8: 12 | disable: 13 | - W503 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: python 3 | python: 4 | - "2.7" 5 | - "3.2" 6 | - "3.3" 7 | - "3.4" 8 | - "3.5" 9 | install: pip install six 10 | script: 11 | - "python launcher.py test" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Alex Marinescu 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | The views and conclusions contained in the software and documentation are those 26 | of the authors and should not be interpreted as representing official policies, 27 | either expressed or implied, of the FreeBSD Project. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE .travis.yml .landscape.yaml setup.cfg setup.py 2 | prune gem/gem.egg-info -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |ScreenShot| 2 | 3 | pyGameMath |Build Status| |Code Health| |Codacy Badge| 4 | ====================================================== 5 | 6 | | This is a math library written in python for 2D/3D game development 7 | which is also compatible with pypy. I made it while I was learning 8 | more about the math used in graphics development and for personal use 9 | in OpenGL related projects. 10 | | It’s still a work in progress. 11 | 12 | Dependencies: 13 | ------------- 14 | It uses six to allow support between python2.x and python3.x. 15 | 16 | Install: 17 | -------- 18 | To install the library just do 19 | 20 | .. code:: Python 21 | 22 | pip install gem 23 | 24 | It will install the dependicies automatically. 25 | 26 | Documentation and Examples: 27 | --------------------------- 28 | The examples on how to use the library and more info are maintained on the github wiki: 29 | 30 | `Wiki Link `_ 31 | 32 | Supported features: 33 | ~~~~~~~~~~~~~~~~~~~ 34 | 35 | NxN Matrices: 36 | ''''''''''''' 37 | 38 | - Transpose 39 | - Scale 40 | - NxN Matrix Multiplication 41 | - NxN Matrix \* N Dimensions Vector Multiplication 42 | - 4x4 Perspective Projection Matrix 43 | - lookAt 4x4 Matrix 44 | - Translation (3x3, 4x4) 45 | - Rotation (2x2, 3x3, 4x4) 46 | - Shear (2x2, 3x3, 4x4) 47 | - Project 48 | - Unproject 49 | - Orthographic Projection 50 | - Perspective Projection 51 | - lookAt 4x4 matrix 52 | - Determinant 2x2, 3x3, 4x4 53 | - Inverse 2x2, 3x3, 4x4 54 | 55 | N Dimensions Vectors: 56 | ''''''''''''''''''''' 57 | 58 | - Dot Product 59 | - Cross Product (3D, No 7D as of now) 60 | - 2D get angle of vector 61 | - 2D -90 degree rotation 62 | - 2D +90 degree rotation 63 | - Refraction 64 | - Reflection 65 | - Negate 66 | - Normalize 67 | - Linear Interpolation 68 | - Max Vector/Scalar 69 | - Min Vector/Scalar 70 | - Clamp 71 | - Transform 72 | - Barycentric 73 | - isInSameDirection test 74 | - isInOppositeDirection test 75 | - 3D Vector swizzling, similar to GLSL 76 | - 3D Vector idenitities 77 | 78 | Quaternions: 79 | '''''''''''' 80 | 81 | - Normalize 82 | - Dot Product 83 | - Rotation 84 | - Conjugate 85 | - Inverse 86 | - Negate 87 | - Rotate X, Y, Z 88 | - Arbitary Axis Rotation 89 | - From angle Rotation 90 | - To Rotation Matrix (4x4) 91 | - From Rotation Matrix (4x4) 92 | - Cross Product 93 | - Vector3D, Scalar Multiplication 94 | - Logarithm 95 | - Exponential 96 | - Power 97 | - Liner Interpolation (LERP) 98 | - Spherical Interpolation (SLERP) 99 | - Spherical Interpoliaton No Invert 100 | - Quaternion Splines (SQUAD) 101 | 102 | Plane: 103 | '''''' 104 | 105 | - Define using 106 | 107 | - 3 Vectors 108 | - Point and Normal 109 | - Manual input 110 | 111 | - Dot Product 112 | - Normalize 113 | - Best fit normal and D value 114 | - Distance from plane to a point 115 | - Point location 116 | - Output 117 | - Flip 118 | 119 | Ray: 120 | '''' 121 | 122 | - Rotate using Matrix 123 | - Rotate using Quaternions 124 | - Translate 125 | - Output 126 | 127 | Legendre Polynomial (Experimental, not complete): 128 | ''''''''''''''''''''''''''''''''''''''''''''''''' 129 | 130 | - For spherical harmonics 131 | - (l - m)PML(x) = x(2l - 1)PML-1(x 132 | - Irradiance maps 133 | 134 | .. |ScreenShot| image:: https://raw.github.com/AlexMarinescu/pyGameMath/master/data/pyGameMathLogo.png 135 | .. |Build Status| image:: https://travis-ci.org/explosiveduck/pyGameMath.svg?branch=master 136 | :target: https://travis-ci.org/explosiveduck/pyGameMath 137 | .. |Code Health| image:: https://landscape.io/github/explosiveduck/pyGameMath/master/landscape.svg?style=flat 138 | :target: https://landscape.io/github/explosiveduck/pyGameMath/master 139 | .. |Codacy Badge| image:: https://api.codacy.com/project/badge/907e4230379f40a8bedcfc0a9a0ed43c 140 | :target: https://www.codacy.com -------------------------------------------------------------------------------- /data/pyGameMathLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexMarinescu/pyGameMath/5257291431bb45db0274dc48edf24694ecfe2e2d/data/pyGameMathLogo.png -------------------------------------------------------------------------------- /gem/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexMarinescu/pyGameMath/5257291431bb45db0274dc48edf24694ecfe2e2d/gem/__init__.py -------------------------------------------------------------------------------- /gem/common.py: -------------------------------------------------------------------------------- 1 | import math 2 | import six.moves as sm 3 | import ctypes as ct 4 | 5 | #OpenGL Types, this is needed because Matrix class is supported by OpenGL 6 | GLfloat = ct.c_float 7 | 8 | def convertArr(l, n): 9 | ''' Convert a 1D array to 2D array. ''' 10 | return [l[i:i+n] for i in sm.range(0, len(l), n)] 11 | 12 | # Multiply each element in the 4 length arrays eg. [a, b, c, d] x [e, f, g, h] = [axe, bxf, cxg, dxh] 13 | def mulV4(v1, v2): 14 | ''' Multiply two 1D 4 Length Arrays.''' 15 | return [v1[0] * v2[0], v1[1] * v2[1], v1[2] * v2[2], v1[3] * v2[3]] 16 | 17 | def conv_list(listIn, cType): 18 | ''' Convert a python list into a ctypes array ''' 19 | return (cType * len(listIn))(*listIn) 20 | 21 | def conv_list_2d(listIn, cType): 22 | ''' Convert a python 2d list into a ctypes 2d array ''' 23 | xlength = len(listIn) 24 | ylength = len(listIn[0]) 25 | 26 | arrayOut = (cType * ylength * xlength)() 27 | 28 | for x in sm.range(xlength): 29 | for y in sm.range(ylength): 30 | arrayOut[x][y] = listIn[x][y] 31 | 32 | return arrayOut 33 | 34 | def list_2d_to_1d(inlist): 35 | ''' Convert a python 2D list to python 1D list. ''' 36 | sizeX = len(inlist[0]) 37 | sizeY = len(inlist) 38 | 39 | rtnList = [None for x in sm.range(sizeY*sizeX)] 40 | 41 | for x in sm.range(sizeY): 42 | for y in sm.range(sizeX): 43 | rtnList[y + x * sizeX] = inlist[x][y] 44 | 45 | return rtnList 46 | 47 | def convertM4to3(matrix): 48 | ''' Convert a 4x4 Matrix to 3x3. ''' 49 | temp = [[0.0, 0.0, 0.0], 50 | [0.0, 0.0, 0.0], 51 | [0.0, 0.0, 0.0]] 52 | 53 | for i in sm.range(3): 54 | for j in sm.range(3): 55 | temp[i][j] = matrix[i][j] 56 | return temp 57 | 58 | # Sinc function 59 | def sinc(x): 60 | if math.fabs(x) < 1.0e-4: 61 | return 1.0 62 | else: 63 | return math.sin(x) / x 64 | 65 | # Scalar lerp 66 | def scalarLerp(a, b, time): 67 | ''' Iterpolate a number over time. ''' 68 | return a + time * (b - a) 69 | 70 | # Returns the view port coordinates 71 | def getViewPort(coords, width, height): 72 | ''' A version of glViewPort except it returns the coords. ''' 73 | coordsN = coords.normalize() 74 | x = (coordsN[0] + 1) * (width / 2) + coords[0] 75 | y = (coordsN[1] + 1) * (height / 2) + coords[1] 76 | return [x,y,width,height] 77 | 78 | # Radians to degree 79 | def radiansToDegrees(degrees): 80 | return (degrees * 3.14) / 180.0 81 | 82 | # Degrees to radians 83 | def degreesToRadians(radians): 84 | return (radians * 180.0) / 3.14 85 | 86 | # Returns the sign 87 | def sign(x): 88 | if x >= 0.0: 89 | return 1.0 90 | else: 91 | return -1.0 -------------------------------------------------------------------------------- /gem/experimental/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gem/experimental/bezier.py: -------------------------------------------------------------------------------- 1 | import math 2 | import six.moves as sm 3 | 4 | 5 | # Cubic Bezier Cruve 6 | def cubicBezierPoint(t, p0, p1, p2, p3): 7 | u = 1 - t 8 | tt = t * t 9 | uu = u * u 10 | uuu = uu * u 11 | ttt = tt * t 12 | 13 | p = uuu * p0 14 | p += 3 * uu * t * p1 15 | p += 3 * u * tt * p2 16 | p += ttt + p3 17 | 18 | return p 19 | 20 | # Quadratic Bezier Curve 21 | def quadraticBezierPoint(t, p0, p1, p2): 22 | u = 1 - t 23 | tt = t * t 24 | uu = u * u 25 | 26 | p = uu * p0 27 | p += 2 * u * t * p1 28 | p += tt * p2 29 | 30 | return p 31 | 32 | class BezierPath(object): 33 | def __init__(self): 34 | self.segments_per_curve = 10 35 | self.minimum_sqr_distance = 0.01 36 | self.divison_threshold = -0.99 37 | 38 | self.controlPoints = [] 39 | self.curveCount = 0 40 | 41 | def setControlPoints(self, newControlPoints): 42 | self.controlPoints = newControlPoints 43 | self.curveCount = (len(self.controlPoints) - 1) / 3 44 | 45 | def getControlPoints(self): 46 | return self.controlPoints 47 | 48 | def interpolate(self, segmentPoints, scale): 49 | 50 | segmentPointsLen = len(segmentPoints) 51 | 52 | if segmentPointsLen < 2: 53 | return 54 | 55 | for i in sm.range(segmentPointsLen): 56 | if i == 0: 57 | p1 = segmentPoints[i] 58 | p2 = segmentPoints[i + 1] 59 | 60 | tangent = p2 - p1 61 | q1 = p1 + scale * tangent 62 | 63 | self.controlPoints.append(p1) 64 | self.controlPoints.append(q1) 65 | elif i == segmentPointsLen - 1: 66 | p0 = segmentPoints[i - 1] 67 | p1 = segmentPoints[i] 68 | 69 | tangent = p1 - p0 70 | q0 = p1 - scale * tangent 71 | 72 | self.controlPoints.append(q0) 73 | self.controlPoints.append(p1) 74 | else: 75 | p0 = segmentPoints[i - 1] 76 | p1 = segmentPoints[i] 77 | p2 = segmentPoints[i + 1] 78 | tangent = (p2 - p0).normalize() 79 | q0 = p1 - scale * tangent * (p1 - p0).magnitude() 80 | q1 = p1 + scale * tangent * (p2 - p1).magnitude() 81 | 82 | self.controlPoints.append(q0) 83 | self.controlPoints.append(p1) 84 | self.controlPoints.append(q1) 85 | 86 | self.curveCount = (len(self.controlPoints) - 1) / 3 87 | 88 | def samplePoints(self, sourcePoints, minSqrDistance, maxSqrDistance, scale): 89 | sourcePointsLen = len(sourcePoints) 90 | 91 | if (sourcePointsLen < 2): 92 | return 93 | 94 | samplePoints = [] 95 | 96 | samplePoints.append(sourcePoints[0]) 97 | 98 | potentialSamplePoint = sourcePoints[1] 99 | 100 | for i in sm.range(2, sourcePointsLen, 1): 101 | # These variables need better names 102 | pointDiff = potentialSamplePoint - sourcePoints[i] 103 | pointDIff2 = samplePoints[len(samplePoints) - 2] - sourcePoints[i] 104 | 105 | if (pointDiff.sqrMagnitude() > minSqrDistance and 106 | pointDIff2.sqrMagnitude() > maxSqrDistance): 107 | samplePoints.append(potentialSamplePoint) 108 | potentialSamplePoint = sourcePoints[i] 109 | 110 | p1 = samplePoints[len(samplePoints) - 1] 111 | p0 = samplePoints[len(samplePoints) - 2] 112 | tangent = (p0 - potentialSamplePoint).normalize() 113 | d2 = (potentialSamplePoint - p1).magnitude() 114 | d1 = (p1 - p0).magnitude() 115 | p1 = p1 + tangent * ((d1 - d2) / 2) 116 | 117 | samplePoints.append(p1) 118 | samplePoints.append(potentialSamplePoint) 119 | 120 | self.interpolate(samplePoints, scale) 121 | 122 | def calculateBezerPoint(self, curveIndex, t): 123 | nodeIndex = curveIndex * 3 124 | 125 | p0 = self.controlPoints[nodeIndex] 126 | p1 = self.controlPoints[nodeIndex + 1] 127 | p2 = self.controlPoints[nodeIndex + 2] 128 | p3 = self.controlPoints[nodeIndex + 3] 129 | 130 | return cubicBezierPoint(t, p0, p1, p2, p3) 131 | 132 | # Find the drawing points of the curve using recusive divison 133 | # Less points but same accuracy 134 | def getDrawingPoints(self): 135 | drawingPoints = [] 136 | 137 | for i in sm.range(self.curveCount): 138 | bezierCurveDrawingPoints = self.findDrawingPoints(i) 139 | 140 | if i != 0: 141 | del bezierCurveDrawingPoints[0] 142 | 143 | drawingPoints.append(bezierCurveDrawingPoints) 144 | 145 | return drawingPoints 146 | 147 | def findDrawingPoints(self, curveIndex): 148 | pointList = [] 149 | 150 | left = self.calculateBezerPoint(curveIndex, 0) 151 | right = self.calculateBezerPoint(curveIndex, 1) 152 | 153 | pointList.append(left) 154 | pointList.append(right) 155 | 156 | self.findDrawingPointsAdded(curveIndex, 0, 1, pointList, 1) 157 | 158 | return pointList 159 | 160 | def findDrawingPointsAdded(self, curveIndex, t0, t1, pointList, insertionIndex): 161 | left = self.calculateBezerPoint(curveIndex, t0) 162 | right = self.calculateBezerPoint(curveIndex, t1) 163 | 164 | if (left - right).sqrMagnitude() < self.minimum_sqr_distance: 165 | return 0 166 | 167 | tMid = (t0 - t1) / 2 168 | mid = self.calculateBezerPoint(curveIndex, tMid) 169 | 170 | leftDirection = (left - mid).normalize() 171 | rightDirection = (right - mid).normalize() 172 | 173 | if (leftDirection.dot(rightDirection) > self.divison_threshold or 174 | math.abs(tMid - 0.5) < 0.0001): 175 | pointsAddedCount = 0 176 | 177 | pointsAddedCount += self.findDrawingPointsAdded(curveIndex, t0, tMid, pointList, insertionIndex) 178 | pointList.append(insertionIndex + pointsAddedCount, mid) 179 | pointsAddedCount += 1 180 | pointsAddedCount += self.findDrawingPointsAdded(curveIndex, tMid, t1, pointList, insertionIndex + pointsAddedCount) 181 | 182 | return pointsAddedCount 183 | 184 | return 0 185 | -------------------------------------------------------------------------------- /gem/experimental/legendre.py: -------------------------------------------------------------------------------- 1 | import math 2 | import six.moves as sm 3 | 4 | # Legendre Polynomial Class 5 | class Legendre (object): 6 | 7 | def __init__(self, l, m, x): 8 | self.l = l 9 | self.m = m 10 | self.x = x 11 | self.P = 1.0 12 | self.PM1 = 0.0 13 | self.PML = 0.0 14 | 15 | def mGreaterThan0(self): 16 | if self.m > 0: 17 | sqrtOneMinusXSquared = math.sqrt((1.0 - self.x) * (1.0 + self.x)) 18 | f = 1.0 19 | for _ in sm.range(1, self.m + 1): 20 | self.P *= (-f) * sqrtOneMinusXSquared 21 | f += 2.0 22 | else: 23 | self.P = 1.0 24 | 25 | def calculatePM1(self): 26 | self.mGreaterThan0() 27 | self.PM1 = (self.x) * (2.0 * self.m + 1.0) * self.P 28 | 29 | def calculatePML(self, i): 30 | self.calculatePM1() 31 | self.PML = (((self.x) * (2.0 * i - 1.0) * self.PM1 - (i + self.m - 1.0) * self.P)) / (i - self.m) 32 | 33 | def run(self): 34 | if self.l == self.m: 35 | self.mGreaterThan0() 36 | return self.P 37 | 38 | elif self.l == (self.m + 1): 39 | self.calculatePM1() 40 | return self.PM1 41 | 42 | else: 43 | for i in sm.range(self.m + 2, self.l + 1): 44 | self.calculatePML(i) 45 | self.P = self.PM1 46 | self.PM1 = self.PML 47 | return self.PML 48 | -------------------------------------------------------------------------------- /gem/experimental/sph.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from gem.experimental.legendre import Legendre 4 | 5 | 6 | # n! where n >= 0 7 | def Factorial(n): 8 | if n <= 1: 9 | return 1 10 | 11 | result = n 12 | 13 | while n > 1: 14 | n -= 1 15 | result *= n 16 | 17 | return result 18 | 19 | # Normalization constant for a Spherical Harmonic function 20 | def K(l, m): 21 | K = ((2.0 * l + 1.0) * Factorial(l - m)) / ((4.0 * math.pi) * Factorial(l + m)) 22 | return math.sqrt(K) 23 | 24 | 25 | # Sample a Spherical Harmonic function Y(l, m) at a point on the unit sphere 26 | def SPH(l, m, theta, phi): 27 | root2 = math.sqrt(2.0) 28 | if m == 0: 29 | return K(l, 0) * Legendre(l, m, math.cos(theta)).run() 30 | elif m > 0: 31 | return root2 * K(l, m) * math.cos(m * phi) * Legendre(l, m, math.cos(theta)).run() 32 | elif m < 0: 33 | return root2 * K(l, -m) * math.sin(-m * phi) * Legendre(l, -m, math.cos(theta)).run() 34 | else: 35 | print ("WTF... The m is ...") 36 | return 0 37 | -------------------------------------------------------------------------------- /gem/experimental/sph_irradiance_map.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | import math 3 | import struct 4 | import six.moves as sm 5 | 6 | from gem.common import sinc 7 | 8 | 9 | # Calculate Spherical Harmonics coefficients using irradiance maps 10 | class SPH_IrradianceMapCoeff(object): 11 | 12 | def __init__(self, fileU, width, height): 13 | # File details 14 | self.file = fileU 15 | self.width = width 16 | self.height = height 17 | # Alocate space for the image 18 | self.hdr = [[[0 for _ in sm.range(3)] for _ in sm.range(self.width)] for _ in sm.range(self.height)] 19 | # Alocate space for the coefficients 20 | self.coeffs = [[0 for _ in sm.range(3)] for _ in sm.range(9)] 21 | # Load the image 22 | self.load() 23 | 24 | # Load the file 25 | def load(self): 26 | with open(self.file, 'rb') as f: 27 | for i in sm.range(self.width): 28 | for j in sm.range(self.height): 29 | # The depth is 3 (R,G,B) 30 | for k in sm.range(3): 31 | # Decode the 32 bit binary into float point number 32 | # 4 bytes 33 | self.hdr[i][j][k] = struct.unpack('f', f.read(4))[0] 34 | 35 | # Calculate coefficients 36 | self.calculateCoefficients() 37 | 38 | def calculateCoefficients(self): 39 | for i in sm.range(self.width): 40 | for j in sm.range(self.height): 41 | v = (self.width / 2.0 - i) / (self.width / 2.0) 42 | u = (j - self.width / 2.0) / (self.width / 2.0) 43 | r = math.sqrt(u * u + v * v) 44 | # Only withn a unit circle 45 | if r <= 1.0: 46 | theta = math.pi * r 47 | phi = math.atan2(v, u) 48 | 49 | x = math.sin(theta) * math.cos(phi) 50 | y = math.sin(theta) * math.sin(phi) 51 | z = math.cos(theta) 52 | 53 | domega = (2 * math.pi / self.width) * (2.0 * math.pi / self.width) * sinc(theta) 54 | 55 | self.updateCoefficients(self.hdr[i][j], domega, x, y, z) 56 | 57 | def updateCoefficients(self, hdr, domega, x, y, z): 58 | for col in sm.range(3): 59 | 60 | c = 0.282095 61 | self.coeffs[0][col] += hdr[col] * c * domega 62 | 63 | c = 0.488603 64 | self.coeffs[1][col] += hdr[col] * (c * y) * domega 65 | self.coeffs[2][col] += hdr[col] * (c * z) * domega 66 | self.coeffs[3][col] += hdr[col] * (c * x) * domega 67 | 68 | c = 1.092548 69 | self.coeffs[4][col] += hdr[col] * (c * x * y) * domega 70 | self.coeffs[5][col] += hdr[col] * (c * y * z) * domega 71 | self.coeffs[7][col] += hdr[col] * (c * x * z) * domega 72 | 73 | c = 0.315392 74 | self.coeffs[6][col] += hdr[col] * (c * (3 * z * z - 1)) * domega 75 | 76 | c = 0.546274 77 | self.coeffs[8][col] += hdr[col] * (c * (x * x - y * y)) * domega 78 | 79 | def output(self): 80 | pprint.pprint(self.coeffs) 81 | 82 | # Example 83 | #test_irradiance_map = SPH_IrradianceMapCoeff("uffizi_probe.float", 1500, 1500) 84 | #test_irradiance_map.output() 85 | -------------------------------------------------------------------------------- /gem/experimental/sph_object.py: -------------------------------------------------------------------------------- 1 | import math 2 | import six.moves as sm 3 | 4 | # Spherical Harmonics Vertex 5 | class SPHVertex(object): 6 | 7 | def __init__(self, position, normal): 8 | # Value of a vertex 9 | self.position = position 10 | 11 | # calc normal from position self.normal 12 | self.normal = normal 13 | # or can be specified from outside of function (from file parser) 14 | 15 | # Arrays; length: undefined 16 | self.unshadowedCoeffs = None 17 | self.shadowedCoeffs = None 18 | 19 | 20 | # Spherical Harmonics Object 21 | class SPHObject(object): 22 | 23 | def __init__(self, indices, vertices): 24 | # Arrays 25 | self.vertices = vertices 26 | self.indices = indices 27 | # Booleans 28 | # CollisionResponse = False # wasnt being used so commented it out 29 | 30 | 31 | # Generate Coefficients 32 | # objects is an array that holds the objects in the scene 33 | def GenereateCoeffs(numSamples, numBands, samples, objects): 34 | 35 | numFunctions = numBands * numBands 36 | numObjects = len(objects) 37 | 38 | # Create space for the SH coefficients in each vertex 39 | for i in sm.range(numObjects): 40 | 41 | currentObject = objects[i] 42 | 43 | numVertices = len(currentObject.vertices) 44 | 45 | for j in sm.range(numVertices): 46 | 47 | currentVertex = currentObject.vertices[j] 48 | 49 | # Create new unshadowedCoeffs array 50 | # Create new shadowedCoeffs array 51 | 52 | for i in sm.range(numObjects): 53 | 54 | currentObject = object[i] 55 | 56 | numVertices = len(currentObject.vertices) 57 | 58 | for j in sm.range(numVertices): 59 | 60 | currentVertex = currentObject.vertices[j] 61 | 62 | for k in sm.range(numFunctions): 63 | currentVertex.unshadowedCoeffs[k] = 0.0 64 | currentVertex.shadowedCoeffs[k] = 0.0 65 | 66 | for k in sm.range(numSamples): 67 | dotProduct = samples[k].dir.dot(currentVertex.normal) 68 | 69 | # Clamp to [0,1] 70 | if dotProduct > 0.0: 71 | 72 | # Ray structure 73 | 74 | # Collision check 75 | 76 | for l in sm.range(numFunctions): 77 | contribution = dotProduct * samples[k].values[l] 78 | currentVertex.unshadowedCoeffs[l] += contribution 79 | #if not rayBlocked: 80 | # currentVertex.shadowedCoeffs[l] += contribution 81 | 82 | # Rescale the coefficients 83 | for k in sm.range(numFunctions): 84 | currentVertex.unshadowedCoeffs[k] *= 4 * math.pi / numSamples 85 | currentVertex.shadowedCoeffs[k] += 4 * math.pi / numSamples 86 | -------------------------------------------------------------------------------- /gem/experimental/sph_sample.py: -------------------------------------------------------------------------------- 1 | import random 2 | import six.moves as sm 3 | import math 4 | from gem import vector 5 | from gem.experimental import sph 6 | 7 | 8 | # Spherical Harmonics Sample Class 9 | class SPHSample (object): 10 | def __init__(self, theta, phi, dirc, sampleNumber): 11 | # Spherical coordinates 12 | self.theta = theta 13 | self.phi = phi 14 | 15 | # Values of SH function at this point 16 | self.values = [] 17 | for _ in sm.range(sampleNumber): 18 | self.values.append(0.0) 19 | 20 | # Direction (Vector3D) 21 | if isinstance(dirc, vector.Vector): 22 | self.dir = dirc 23 | else: 24 | self.dir = vector.Vector(3, data=[0.0, 0.0, 0.0]) 25 | 26 | 27 | # Generate the samples 28 | def GenerateSamples(sqrtNumSamples, numBands): 29 | x = 0 30 | y = 0 31 | theta = 0 32 | phi = 0 33 | ii = 0 34 | index = 0 35 | 36 | numSamples = sqrtNumSamples * sqrtNumSamples 37 | numFunctions = numBands * numBands 38 | invertedNumSamples = 1.0 / sqrtNumSamples 39 | 40 | # Create the array to hold the samples 41 | samples = [] 42 | for x in sm.range(numSamples): 43 | samples.append(SPHSample(0.0, 0.0, vector.Vector(3, data=[0.0, 0.0, 0.0]), numFunctions)) 44 | 45 | print ("Generating Samples...") 46 | # Loop through a grid of numSamples X numSamples 47 | for i in sm.range(sqrtNumSamples): 48 | for j in sm.range(sqrtNumSamples): 49 | x = (i + random.random()) * invertedNumSamples 50 | y = (j + random.random()) * invertedNumSamples 51 | 52 | # Spherical Angles 53 | theta = 2.0 * math.acos(math.sqrt(1.0 - x)) 54 | phi = 2.0 * math.PI * y 55 | 56 | samples[ii].theta = theta 57 | samples[ii].phi = phi 58 | 59 | # Vec is an array and dir is a vector 60 | samples[ii].dir.vec = [math.sin(theta) * math.cos(phi), 61 | math.sin(theta) * math.sin(phi), 62 | math.cos(theta)] 63 | 64 | # Calculate SH coefficients of current sample 65 | for l in sm.range(numBands): 66 | for m in sm.range(-l, l + 1): 67 | index = l * (l + 1) + m 68 | samples[ii].values[index] = sph.SPH(l, m, theta, phi) 69 | ii += 1 70 | 71 | # Return the samples array 72 | return samples 73 | -------------------------------------------------------------------------------- /gem/matrix.py: -------------------------------------------------------------------------------- 1 | import math 2 | import six.moves as sm 3 | from gem import vector 4 | from gem import common 5 | 6 | def zero_matrix(size): 7 | ''' Return zero filled matrix list of the requested size''' 8 | return [[0.0] * size for _ in sm.range(size)] 9 | 10 | def identity(size): 11 | ''' Return an identity matrix list of the requested size ''' 12 | return [[1.0 if x==y else 0.0 for y in sm.range(size)] for x in sm.range(size)] 13 | 14 | def scale(size, value): 15 | return [[(value[x] if x < 3 else 1.0) if x==y else 0.0 for y in sm.range(size)] for x in sm.range(size)] 16 | 17 | def matrix_multiply(matrixA, matrixB): 18 | ''' Multiply matrixA with matrixB ''' 19 | sizeA = len(matrixA) 20 | matOut = zero_matrix(sizeA) 21 | crange = list(sm.range(sizeA)) 22 | for i in crange: 23 | for k in crange: 24 | for j in crange: 25 | matOut[i][j] += matrixA[i][k] * matrixB[k][j] 26 | return matOut 27 | 28 | def matrix_vector_multiply(matrix, vec): 29 | ''' Multiply matrix with vector ''' 30 | matSize = len(matrix) 31 | vecSize = vec.size 32 | vecOut = vector.Vector(vecSize) 33 | crange = list(sm.range(matSize)) 34 | for i in crange: 35 | for j in crange: 36 | vecOut.vector[j] += vec.vector[i] * matrix[i][j] 37 | return vecOut 38 | 39 | def matrix_div(mat, scalar): 40 | ''' Divide a matrix by a scalar. ''' 41 | size = len(mat) 42 | matOut = zero_matrix(size) 43 | crange = list(sm.range(size)) 44 | for i in crange: 45 | for j in crange: 46 | matOut[i][j] = mat[j][i] / scalar 47 | return matOut 48 | 49 | def transpose(mat): 50 | '''Transposes a NxN matrix.''' 51 | size = len(mat) 52 | out = zero_matrix(size) 53 | crange = list(sm.range(size)) 54 | for i in crange: 55 | for j in crange: 56 | out[i][j]= mat[j][i] 57 | return out 58 | 59 | ###### Shear functions #### 60 | def shearXY3(x, y): 61 | ''' Shear on XY. ''' 62 | out = identity(3) 63 | out[0][3] = x 64 | out[1][3] = y 65 | return out 66 | 67 | def shearYZ3(y, z): 68 | ''' Shear on YZ. ''' 69 | out = identity(3) 70 | out[1][0] = y 71 | out[2][0] = z 72 | return out 73 | 74 | def shearXZ3(x, z): 75 | ''' Shear on XZ. ''' 76 | out = identity(3) 77 | out[0][1] = x 78 | out[2][1] = z 79 | return out 80 | 81 | def shearXY4(x, y): 82 | ''' Shear on XY. ''' 83 | out = identity(4) 84 | out[0][3] = x 85 | out[1][3] = y 86 | return out 87 | 88 | def shearYZ4(y, z): 89 | ''' Shear on YZ. ''' 90 | out = identity(4) 91 | out[1][0] = y 92 | out[2][0] = z 93 | return out 94 | 95 | def shearXZ4(x, z): 96 | ''' Shear on XZ. ''' 97 | out = identity(4) 98 | out[0][1] = x 99 | out[2][1] = z 100 | return out 101 | 102 | ###### Translate functions ##### 103 | 104 | def translate2(vector): 105 | ''' Translate by a vector.''' 106 | translate = identity(3) 107 | translate[2][0] = vector[0] 108 | translate[2][1] = vector[1] 109 | return translate 110 | 111 | def translate3(vector): 112 | ''' Translate by a vector.''' 113 | translate = identity(3) 114 | translate[2][0] = vector[0] 115 | translate[2][1] = vector[1] 116 | translate[2][2] = vector[2] 117 | return translate 118 | 119 | def translate4(vector): 120 | ''' Translate by a vector.''' 121 | translate = identity(4) 122 | translate[3][0] = vector[0] 123 | translate[3][1] = vector[1] 124 | translate[3][2] = vector[2] 125 | return translate 126 | 127 | 128 | ###### Rotate functions ##### 129 | 130 | def rotate2(point, theta): 131 | ''' Rotate around an axis.''' 132 | c = math.cos(math.radians(theta)) 133 | s = math.sin(math.radians(theta)) 134 | 135 | x1 = (point[0] - c) * (point[0]) - (-s * point[1]) 136 | y1 = (point[1] - s) * (point[0]) - ( c * point[1]) 137 | 138 | container = [[c, s, 0.0], 139 | [-s, c, 0.0], 140 | [x1, y1, 1.0]] 141 | 142 | return container 143 | 144 | def rotate3(axis, theta): 145 | ''' Rotate around an axis.''' 146 | c = math.cos(math.radians(theta)) 147 | s = math.sin(math.radians(theta)) 148 | 149 | oneMinusCos = (1.0 - c) 150 | 151 | nAxis = vector.normalize(3, axis) 152 | 153 | x2 = nAxis[0] * nAxis[0] 154 | y2 = nAxis[1] * nAxis[1] 155 | z2 = nAxis[2] * nAxis[2] 156 | 157 | container = [[c + x2 * oneMinusCos, ((nAxis[1] * nAxis[0]) * oneMinusCos) + (nAxis[2] * s), ((nAxis[2] * nAxis[0]) * oneMinusCos) - (nAxis[1] * s)], 158 | [((nAxis[0] * nAxis[1]) * oneMinusCos) - (nAxis[2] * s), c + y2 * oneMinusCos, ((nAxis[2] * nAxis[1]) * oneMinusCos) + (nAxis[0] * s)], 159 | [((nAxis[0] * nAxis[2]) * oneMinusCos) + (nAxis[1] * s), ((nAxis[1] * nAxis[2]) * oneMinusCos) - (nAxis[0] * s), c + z2 * oneMinusCos]] 160 | return container 161 | 162 | def rotate4(axis, theta): 163 | ''' Rotate around an axis.''' 164 | c = math.cos(math.radians(theta)) 165 | s = math.sin(math.radians(theta)) 166 | 167 | oneMinusCos = (1.0 - c) 168 | 169 | nAxis = vector.normalize(3, axis) 170 | 171 | x2 = nAxis[0] * nAxis[0] 172 | y2 = nAxis[1] * nAxis[1] 173 | z2 = nAxis[2] * nAxis[2] 174 | 175 | container = [[c + x2 * oneMinusCos, ((nAxis[1] * nAxis[0]) * oneMinusCos) + (nAxis[2] * s), ((nAxis[2] * nAxis[0]) * oneMinusCos) - (nAxis[1] * s), 0.0], 176 | [((nAxis[0] * nAxis[1]) * oneMinusCos) - (nAxis[2] * s), c + y2 * oneMinusCos, ((nAxis[2] * nAxis[1]) * oneMinusCos) + (nAxis[0] * s), 0.0], 177 | [((nAxis[0] * nAxis[2]) * oneMinusCos) + (nAxis[1] * s), ((nAxis[1] * nAxis[2]) * oneMinusCos) - (nAxis[0] * s), c + z2 * oneMinusCos, 0.0], 178 | [0.0, 0.0, 0.0, 1.0]] 179 | return container 180 | 181 | def rotate_origin2(theta): 182 | container = identity(3) 183 | container[0][0] = math.cos(theta) 184 | container[0][1] = math.sin(theta) 185 | container[1][0] = -math.sin(theta) 186 | container[1][1] = math.cos(theta) 187 | return container 188 | 189 | ###### Determinant functions ###### 190 | 191 | def det2(mat): 192 | ''' Determinant of a 2x2 matrix. ''' 193 | return mat[0][0] * mat[1][1] - mat[1][0] * mat[0][1] 194 | 195 | def det3(mat): 196 | ''' Determinant of a 3x3 matrix. ''' 197 | return ( mat[0][0] * (mat[1][1] * mat[2][2] - mat[2][1] * mat[1][2]) - 198 | mat[0][1] * (mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]) + 199 | mat[0][2] * (mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]) ) 200 | 201 | def det4(mat): 202 | ''' Determinant of a 4x4 matrix. ''' 203 | 204 | sf00 = mat[2][2] * mat[3][3] - mat[3][2] * mat[2][3] 205 | sf01 = mat[2][1] * mat[3][3] - mat[3][1] * mat[2][3] 206 | sf02 = mat[2][1] * mat[3][2] - mat[3][1] * mat[2][2] 207 | sf03 = mat[2][0] * mat[3][3] - mat[3][0] * mat[2][3] 208 | sf04 = mat[2][0] * mat[3][2] - mat[3][0] * mat[2][2] 209 | sf05 = mat[2][0] * mat[3][1] - mat[3][0] * mat[2][1] 210 | sf06 = mat[1][2] * mat[3][3] - mat[3][2] * mat[1][3] 211 | sf07 = mat[1][1] * mat[3][3] - mat[3][1] * mat[1][3] 212 | sf08 = mat[1][1] * mat[3][2] - mat[3][1] * mat[1][2] 213 | sf09 = mat[1][0] * mat[3][3] - mat[3][0] * mat[1][3] 214 | sf10 = mat[1][0] * mat[3][2] - mat[3][0] * mat[1][2] 215 | sf11 = mat[1][1] * mat[3][3] - mat[3][1] * mat[1][3] 216 | sf12 = mat[1][0] * mat[3][1] - mat[3][0] * mat[1][1] 217 | sf13 = mat[1][2] * mat[2][3] - mat[2][2] * mat[1][3] 218 | sf14 = mat[1][1] * mat[2][3] - mat[2][1] * mat[1][3] 219 | sf15 = mat[1][1] * mat[2][2] - mat[2][1] * mat[1][2] 220 | sf16 = mat[1][0] * mat[2][3] - mat[2][0] * mat[1][3] 221 | sf17 = mat[1][0] * mat[2][2] - mat[2][0] * mat[1][2] 222 | sf18 = mat[1][0] * mat[2][1] - mat[2][0] * mat[1][1] 223 | 224 | inverse = zero_matrix(4) 225 | inverse[0][0] = + (mat[1][1] * sf00 - mat[1][2] * sf01 + mat[1][3] * sf02) 226 | inverse[0][1] = - (mat[1][0] * sf00 - mat[1][2] * sf03 + mat[1][3] * sf04) 227 | inverse[0][2] = + (mat[1][0] * sf01 - mat[1][1] * sf03 + mat[1][3] * sf05) 228 | inverse[0][3] = - (mat[1][0] * sf02 - mat[1][1] * sf04 + mat[1][2] * sf05) 229 | 230 | inverse[1][0] = - (mat[0][1] * sf00 - mat[0][2] * sf01 + mat[0][3] * sf02) 231 | inverse[1][1] = + (mat[0][0] * sf00 - mat[0][2] * sf03 + mat[0][3] * sf04) 232 | inverse[1][2] = - (mat[0][0] * sf01 - mat[0][1] * sf03 + mat[0][3] * sf05) 233 | inverse[1][3] = + (mat[0][0] * sf02 - mat[0][1] * sf04 + mat[0][2] * sf05) 234 | 235 | inverse[2][0] = + (mat[0][1] * sf06 - mat[0][2] * sf07 + mat[0][3] * sf08) 236 | inverse[2][1] = - (mat[0][0] * sf06 - mat[0][2] * sf09 + mat[0][3] * sf10) 237 | inverse[2][2] = + (mat[0][0] * sf11 - mat[0][1] * sf09 + mat[0][3] * sf12) 238 | inverse[2][3] = - (mat[0][0] * sf08 - mat[0][1] * sf10 + mat[0][2] * sf12) 239 | 240 | inverse[3][0] = - (mat[0][1] * sf13 - mat[0][2] * sf14 + mat[0][3] * sf15) 241 | inverse[3][1] = + (mat[0][0] * sf13 - mat[0][2] * sf16 + mat[0][3] * sf17) 242 | inverse[3][2] = - (mat[0][0] * sf14 - mat[0][1] * sf16 + mat[0][3] * sf18) 243 | inverse[3][3] = + (mat[0][0] * sf15 - mat[0][1] * sf17 + mat[0][2] * sf18) 244 | 245 | return ( mat[0][0] * inverse[0][0] 246 | + mat[0][1] * inverse[0][1] 247 | + mat[0][2] * inverse[0][2] 248 | + mat[0][3] * inverse[0][3]) 249 | 250 | ###### Inverse functions ##### 251 | 252 | def inverse2(mat): 253 | ''' Inverse of a 2x2 matrix ''' 254 | det = mat[0][0] * mat[1][1] - mat[1][0] * mat[0][1] 255 | 256 | inverse = zero_matrix(2) 257 | inverse[0][0] = mat[1][1] / det 258 | inverse[0][1] = - mat[0][1] / det 259 | inverse[0][0] = mat[1][0] / det 260 | inverse[0][1] = - mat[0][0] / det 261 | 262 | return inverse 263 | 264 | def inverse3(mat): 265 | ''' Inverse of a 3x3 matrix.''' 266 | det = ( mat[0][0] * (mat[1][1] * mat[2][2] - mat[2][1] * mat[1][2]) - 267 | mat[0][1] * (mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]) + 268 | mat[0][2] * (mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]) ) 269 | 270 | invDet = 1 / det; 271 | 272 | temp = zero_matrix(3) 273 | 274 | temp[0][0] = (mat[1][1] * mat[2][2] - mat[2][1] * mat[1][2]) * invDet 275 | temp[0][1] = (mat[0][2] * mat[2][1] - mat[0][1] * mat[2][2]) * invDet 276 | temp[0][2] = (mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]) * invDet 277 | temp[1][0] = (mat[1][2] * mat[2][0] - mat[1][0] * mat[2][2]) * invDet 278 | temp[1][1] = (mat[0][0] * mat[2][2] - mat[0][2] * mat[2][0]) * invDet 279 | temp[1][2] = (mat[1][0] * mat[0][2] - mat[0][0] * mat[1][2]) * invDet 280 | temp[2][0] = (mat[1][0] * mat[2][1] - mat[2][0] * mat[1][1]) * invDet 281 | temp[2][1] = (mat[2][0] * mat[0][1] - mat[0][0] * mat[2][1]) * invDet 282 | temp[2][2] = (mat[0][0] * mat[1][1] - mat[1][0] * mat[0][1]) * invDet 283 | 284 | return temp 285 | 286 | def inverse4(mat): 287 | ''' Inverse of a 4x4 matrix ''' 288 | 289 | sf00 = mat[2][2] * mat[3][3] - mat[3][2] * mat[2][3] 290 | sf01 = mat[2][1] * mat[3][3] - mat[3][1] * mat[2][3] 291 | sf02 = mat[2][1] * mat[3][2] - mat[3][1] * mat[2][2] 292 | sf03 = mat[2][0] * mat[3][3] - mat[3][0] * mat[2][3] 293 | sf04 = mat[2][0] * mat[3][2] - mat[3][0] * mat[2][2] 294 | sf05 = mat[2][0] * mat[3][1] - mat[3][0] * mat[2][1] 295 | sf06 = mat[1][2] * mat[3][3] - mat[3][2] * mat[1][3] 296 | sf07 = mat[1][1] * mat[3][3] - mat[3][1] * mat[1][3] 297 | sf08 = mat[1][1] * mat[3][2] - mat[3][1] * mat[1][2] 298 | sf09 = mat[1][0] * mat[3][3] - mat[3][0] * mat[1][3] 299 | sf10 = mat[1][0] * mat[3][2] - mat[3][0] * mat[1][2] 300 | sf11 = mat[1][1] * mat[3][3] - mat[3][1] * mat[1][3] 301 | sf12 = mat[1][0] * mat[3][1] - mat[3][0] * mat[1][1] 302 | sf13 = mat[1][2] * mat[2][3] - mat[2][2] * mat[1][3] 303 | sf14 = mat[1][1] * mat[2][3] - mat[2][1] * mat[1][3] 304 | sf15 = mat[1][1] * mat[2][2] - mat[2][1] * mat[1][2] 305 | sf16 = mat[1][0] * mat[2][3] - mat[2][0] * mat[1][3] 306 | sf17 = mat[1][0] * mat[2][2] - mat[2][0] * mat[1][2] 307 | sf18 = mat[1][0] * mat[2][1] - mat[2][0] * mat[1][1] 308 | 309 | inverse = zero_matrix(4) 310 | inverse[0][0] = + (mat[1][1] * sf00 - mat[1][2] * sf01 + mat[1][3] * sf02) 311 | inverse[0][1] = - (mat[1][0] * sf00 - mat[1][2] * sf03 + mat[1][3] * sf04) 312 | inverse[0][2] = + (mat[1][0] * sf01 - mat[1][1] * sf03 + mat[1][3] * sf05) 313 | inverse[0][3] = - (mat[1][0] * sf02 - mat[1][1] * sf04 + mat[1][2] * sf05) 314 | 315 | inverse[1][0] = - (mat[0][1] * sf00 - mat[0][2] * sf01 + mat[0][3] * sf02) 316 | inverse[1][1] = + (mat[0][0] * sf00 - mat[0][2] * sf03 + mat[0][3] * sf04) 317 | inverse[1][2] = - (mat[0][0] * sf01 - mat[0][1] * sf03 + mat[0][3] * sf05) 318 | inverse[1][3] = + (mat[0][0] * sf02 - mat[0][1] * sf04 + mat[0][2] * sf05) 319 | 320 | inverse[2][0] = + (mat[0][1] * sf06 - mat[0][2] * sf07 + mat[0][3] * sf08) 321 | inverse[2][1] = - (mat[0][0] * sf06 - mat[0][2] * sf09 + mat[0][3] * sf10) 322 | inverse[2][2] = + (mat[0][0] * sf11 - mat[0][1] * sf09 + mat[0][3] * sf12) 323 | inverse[2][3] = - (mat[0][0] * sf08 - mat[0][1] * sf10 + mat[0][2] * sf12) 324 | 325 | inverse[3][0] = - (mat[0][1] * sf13 - mat[0][2] * sf14 + mat[0][3] * sf15) 326 | inverse[3][1] = + (mat[0][0] * sf13 - mat[0][2] * sf16 + mat[0][3] * sf17) 327 | inverse[3][2] = - (mat[0][0] * sf14 - mat[0][1] * sf16 + mat[0][3] * sf18) 328 | inverse[3][3] = + (mat[0][0] * sf15 - mat[0][1] * sf17 + mat[0][2] * sf18) 329 | 330 | det = ( mat[0][0] * inverse[0][0] 331 | + mat[0][1] * inverse[0][1] 332 | + mat[0][2] * inverse[0][2] 333 | + mat[0][3] * inverse[0][3]) 334 | 335 | inverse = matrix_div(inverse, det) 336 | 337 | return inverse 338 | 339 | class Matrix(object): 340 | ''' 341 | Matrix class wrapper. 342 | All operators are implemented in functions, and this class mainly 343 | acts as data validation/sorting based on input types. 344 | ''' 345 | def __init__(self, size, data=None): 346 | self.size = size 347 | 348 | if data is None: 349 | self.matrix = identity(self.size) 350 | else: 351 | self.matrix = data 352 | 353 | self.c_matrix = common.conv_list_2d(self.matrix, common.GLfloat) 354 | 355 | 356 | def __mul__(self, other): 357 | if isinstance(other, vector.Vector): 358 | result = matrix_vector_multiply(self.matrix, other) 359 | return result 360 | 361 | elif isinstance(other, Matrix): 362 | if other.size != self.size: 363 | errText = 'size {}, expected {}'.format(other.size, self.size) 364 | raise ValueError(errText) 365 | else: 366 | result = matrix_multiply(self.matrix, other.matrix) 367 | return Matrix(self.size, data=result) 368 | else: 369 | return NotImplemented 370 | 371 | 372 | def __imul__(self, other): 373 | if isinstance(other, Matrix): 374 | if other.size != self.size: 375 | errText = 'size {}, expected {}'.format(other.size, self.size) 376 | raise ValueError(errText) 377 | else: 378 | self.matrix = matrix_multiply(self.matrix, other.matrix) 379 | self.c_matrix = common.conv_list_2d(self.matrix, common.GLfloat) 380 | return self 381 | else: 382 | return NotImplemented 383 | 384 | def __div__(self, other): 385 | if isinstance(other, float): 386 | result = matrix_div(self.matrix, other) 387 | return Matrix(self.size, data=result) 388 | else: 389 | return NotImplemented 390 | 391 | def __idiv__(self, other): 392 | if isinstance(other, float): 393 | self.matrix = matrix_div(self.matrix, other) 394 | return self 395 | else: 396 | return NotImplemented 397 | 398 | def i_scale(self, value): 399 | ''' Scale matrix instance in-place by Vector. ''' 400 | if not isinstance(value, vector.Vector): 401 | raise TypeError('Expected Vector, got {}.'.format(type(value))) 402 | if isinstance(value, vector.Vector): 403 | scaleMatlst = scale(self.size, value.vector) 404 | self *= Matrix(self.size, data=scaleMatlst) 405 | return self 406 | else: 407 | raise TypeError('Expected type: Vector') 408 | 409 | def scale(self, value): 410 | ''' Scale matrix instance by Vector, and return new matrix. ''' 411 | if not isinstance(value, vector.Vector): 412 | raise TypeError('Expected Vector, got {}.'.format(type(value))) 413 | if isinstance(value, vector.Vector): 414 | scaleMatlst = scale(self.size, value.vector) 415 | return self * Matrix(self.size, data=scaleMatlst) 416 | else: 417 | raise TypeError('Expected type: Vector') 418 | 419 | def det(self): 420 | ''' Calculate the determinant of a Matrix in-place. ''' 421 | if self.size == 2: 422 | detT = det2(self.matrix) 423 | elif self.size == 3: 424 | detT = det3(self.matrix) 425 | elif self.size == 4: 426 | detT = det4(self.matrix) 427 | else: 428 | raise NotImplementedError('Matrix determinant of size {} is not implemented'.format(self.size)) 429 | return detT 430 | 431 | def i_inverse(self): 432 | ''' Calculate the inverse of Matrix instance in-place. ''' 433 | if self.size == 2: 434 | self.matrix = inverse2(self.matrix) 435 | elif self.size == 3: 436 | self.matrix = inverse3(self.matrix) 437 | elif self.size == 4: 438 | self.matrix = inverse4(self.matrix) 439 | else: 440 | raise NotImplementedError('Matrix inverse of size {} not implemented.'.format(self.size)) 441 | 442 | self.c_matrix = common.conv_list_2d(self.matrix, common.GLfloat) 443 | return self 444 | 445 | def inverse(self): 446 | ''' Calculate the inverse of Matrix instance and return new Matrix. ''' 447 | if self.size == 2: 448 | inv= inverse2(self.matrix) 449 | elif self.size == 3: 450 | inv = inverse3(self.matrix) 451 | elif self.size == 4: 452 | inv = inverse4(self.matrix) 453 | else: 454 | raise NotImplementedError('Matrix inverse of size {} not implemented.'.format(self.size)) 455 | return Matrix(self.size, data=inv) 456 | 457 | def i_rotate(self, axis, theta): 458 | ''' Rotate Matrix instance in-place. ''' 459 | if not isinstance(axis, vector.Vector): 460 | raise TypeError('Expected Vector, got {}.'.format(type(axis))) 461 | 462 | if self.size == 2: 463 | rotMatList = rotate2(axis.vector, theta) 464 | elif self.size == 3: 465 | rotMatList = rotate3(axis.vector, theta) 466 | elif self.size == 4: 467 | rotMatList = rotate4(axis.vector, theta) 468 | else: 469 | raise NotImplementedError('Matrix rotate of size {} not implemented.'.format(self.size)) 470 | self *= Matrix(self.size, data=rotMatList) 471 | return self 472 | 473 | def rotate(self, axis, theta): 474 | ''' Rotate Matrix instance and return new Matrix. ''' 475 | if not isinstance(axis, vector.Vector): 476 | raise TypeError('Expected Vector, got {}.'.format(type(axis))) 477 | 478 | if self.size == 2: 479 | rotMatList = rotate2(axis.vector, theta) 480 | elif self.size == 3: 481 | rotMatList = rotate3(axis.vector, theta) 482 | elif self.size == 4: 483 | rotMatList = rotate4(axis.vector, theta) 484 | else: 485 | raise NotImplementedError('Matrix rotate of size {} not implemented.'.format(self.size)) 486 | return self * Matrix(self.size, data=rotMatList) 487 | 488 | def i_translate(self, vecA): 489 | ''' Translate Matrix instance in-place. ''' 490 | if not isinstance(vecA, vector.Vector): 491 | raise TypeError('Expected Vector, got {}.'.format(type(vecA))) 492 | 493 | if self.size == 2: 494 | raise NotImplementedError('Matrix translate of size {} not available.'.format(self.size)) 495 | elif self.size == 3: 496 | if vecA.size == 3: 497 | transMatList = translate3(vecA.vector) 498 | elif vecA.size == 2: 499 | transMatList = translate2(vecA.vector) 500 | elif self.size == 4: 501 | if vecA.size == 3: 502 | transMatList = translate3(vecA.vector) 503 | elif vecA.size == 4: 504 | transMatList = translate4(vecA.vector) 505 | else: 506 | raise NotImplementedError('Matrix translate of size {} not implemented.'.format(self.size)) 507 | self *= Matrix(self.size, data=transMatList) 508 | return self 509 | 510 | def translate(self, vecA): 511 | ''' Translate Matrix instance and return new Matrix. ''' 512 | if not isinstance(vecA, vector.Vector): 513 | raise TypeError('Expected Vector, got {}.'.format(type(vecA))) 514 | 515 | if self.size == 2: 516 | raise NotImplementedError('Matrix translate of size {} not available.'.format(self.size)) 517 | elif self.size == 3: 518 | if vecA.size == 3: 519 | transMatList = translate3(vecA.vector) 520 | elif vecA.size == 2: 521 | transMatList = translate2(vecA.vector) 522 | elif self.size == 4: 523 | transMatList = translate4(vecA.vector) 524 | else: 525 | raise NotImplementedError('Matrix translate of size {} not implemented.'.format(self.size)) 526 | return self * Matrix(self.size, data=transMatList) 527 | 528 | def i_transpose(self): 529 | ''' Transpose Matrix instance in-place. ''' 530 | self.matrix = transpose(self.matrix) 531 | 532 | self.c_matrix = common.conv_list_2d(self.matrix, common.GLfloat) 533 | return self 534 | 535 | def transpose(self): 536 | ''' Transpose Matrix instance and return new Matrix. ''' 537 | return Matrix(self.size, data=transpose(self.matrix) ) 538 | 539 | def shearXY(self, x, y): 540 | ''' Shear on XY and return new Matrix. ''' 541 | if self.size == 3: 542 | transMatList = shearXY3(x, y) 543 | elif self.size == 4: 544 | transMatList = shearXY4(x, y) 545 | else: 546 | raise NotImplementedError('Matrix shearXY of size {} not implemented.'.format(self.size)) 547 | return self * Matrix(self.size, data=transMatList) 548 | 549 | def i_shearXY(self, x, y): 550 | ''' Shear on XY in-place. ''' 551 | if self.size == 3: 552 | transMatList = shearXY3(x, y) 553 | elif self.size == 4: 554 | transMatList = shearXY4(x, y) 555 | else: 556 | raise NotImplementedError('Matrix shearXY of size {} not implemented.'.format(self.size)) 557 | self *= Matrix(self.size, data=transMatList) 558 | return self 559 | 560 | def shearYZ(self, y, z): 561 | ''' Shear on YZ and return a new Matrix. ''' 562 | if self.size == 3: 563 | transMatList = shearYZ3(y, z) 564 | elif self.size == 4: 565 | transMatList = shearYZ4(y, z) 566 | else: 567 | raise NotImplementedError('Matrix shearYZ of size {} not implemented.'.format(self.size)) 568 | return self * Matrix(self.size, data=transMatList) 569 | 570 | def i_shearYZ(self, y, z): 571 | ''' Shear on YZ and return a new Matrix. ''' 572 | if self.size == 3: 573 | transMatList = shearYZ3(y, z) 574 | elif self.size == 4: 575 | transMatList = shearYZ4(y, z) 576 | else: 577 | raise NotImplementedError('Matrix shearYZ of size {} not implemented.'.format(self.size)) 578 | self *= Matrix(self.size, data=transMatList) 579 | return self 580 | 581 | def shearXZ(self, x, z): 582 | ''' Shear on XZ and return a new Matrix. ''' 583 | if self.size == 3: 584 | transMatList = shearXZ3(x, z) 585 | elif self.size == 4: 586 | transMatList = shearXZ4(x, z) 587 | else: 588 | raise NotImplementedError('Matrix shearXZ of size {} not implemented.'.format(self.size)) 589 | return self * Matrix(self.size, data=transMatList) 590 | 591 | def i_shearXZ(self, x, z): 592 | ''' Shear on XZ and return a new Matrix. ''' 593 | if self.size == 3: 594 | transMatList = shearXZ3(x, z) 595 | elif self.size == 4: 596 | transMatList = shearXZ4(x, z) 597 | else: 598 | raise NotImplementedError('Matrix shearXZ of size {} not implemented.'.format(self.size)) 599 | self *= Matrix(self.size, data=transMatList) 600 | return self 601 | 602 | # Matrix-based functions the use the class instead of the functions directly 603 | 604 | def orthographic(left, right, bottom, top, zNear, zFar): 605 | ''' Orthographic Projection ''' 606 | rtnMat = zero_matrix(4) 607 | rtnMat[0][0] = 2.0 / (right - left) 608 | rtnMat[1][1] = 2.0 / (top - bottom) 609 | rtnMat[2][2] = -2.0 / (zFar - zNear) 610 | rtnMat[3][0] = -(right + left) / (right - left) 611 | rtnMat[3][1] = -(top + bottom) / (top - bottom) 612 | rtnMat[3][2] = - (zFar + zNear) / (zFar - zNear) 613 | rtnMat[3][3] = 1 614 | return Matrix(4, data=rtnMat) 615 | 616 | def perspective(fov, aspect, znear, zfar): 617 | ''' Perspective projection matrix 4x4. FOVY''' 618 | rad = math.radians(fov) 619 | 620 | tanHalfFovy = math.tan(rad / 2.0) 621 | 622 | a = 1.0 / (aspect * tanHalfFovy) 623 | b = 1.0 / (tanHalfFovy) 624 | c = - (zfar + znear) / (zfar - znear) 625 | d = - (2.0 * zfar * znear) / (zfar - znear) 626 | 627 | out = [[ a, 0.0, 0.0, 0.0], 628 | [0.0, b, 0.0, 0.0], 629 | [0.0, 0.0, c,-1.0], 630 | [0.0, 0.0, d, 0.0]] 631 | return Matrix(4, data=out) 632 | 633 | 634 | def perspectiveX(fov, aspect, znear, zfar): 635 | ''' Perspective projection matrix 4x4. FOVX''' 636 | f = znear * math.tan((fov * math.pi / 360.0)) 637 | 638 | xmax = f 639 | xmin = -f 640 | 641 | ymin = xmin / aspect 642 | ymax = xmax / aspect 643 | 644 | a = (2.0 * znear) / (xmax - xmin) 645 | b = (2.0 * znear) / (ymax - ymin) 646 | c = -(zfar + znear) / (zfar - znear) 647 | d = -(2.0 * zfar * znear) / (zfar - znear) 648 | e = (xmax + xmin) / (xmax - xmin) 649 | f = (ymax + ymin) / (ymax - ymin) 650 | 651 | out = [[ a, 0.0, 0.0, 0.0], 652 | [0.0, b, 0.0, 0.0], 653 | [ e, f, c,-1.0], 654 | [0.0, 0.0, d, 0.0]] 655 | return Matrix(4, data=out) 656 | 657 | def lookAt(eye, center, up): 658 | ''' Matrix 4x4 lookAt function.''' 659 | f = (center - eye).normalize() 660 | u = up.normalize() 661 | s = vector.cross(f, u).normalize() 662 | u = vector.cross(s, f) 663 | 664 | output = [[s.vector[0], u.vector[0], -f.vector[0], 0.0], 665 | [s.vector[1], u.vector[1], -f.vector[1], 0.0], 666 | [s.vector[2], u.vector[2], -f.vector[2], 0.0], 667 | [-s.dot(eye), -u.dot(eye), f.dot(eye), 1.0]] 668 | return Matrix(4, data=output) 669 | 670 | def project(obj, model, proj, viewport): 671 | ''' The most hacked together project code in the world. :| It works tho. :3 ''' 672 | projM = common.list_2d_to_1d(proj) 673 | modelM = common.list_2d_to_1d(model) 674 | 675 | T = Matrix(4) 676 | for r in sm.range(4): 677 | for c in sm.range(4): 678 | T[r][c] = 0.0 679 | for i in sm.range(4): 680 | T[r][c] += projM[r + i * 4] * modelM[i + c *4] 681 | 682 | result = vector.Vector(4) 683 | 684 | for r in sm.range(4): 685 | result.vector[r] = vector.Vector(4, data=T[r]).dot(obj) 686 | 687 | rhw = 1.0 / result.vector[3] 688 | 689 | return vector.Vector(3, data=[(1 + result.vector[0] * rhw) * viewport[2] / 2.0 + viewport[0], 690 | (1 + result.vector[1] * rhw) * viewport[3] / 2.0 + viewport[1], 691 | (result.vector[2] * rhw) * (1 - 0) + 0, rhw]) 692 | 693 | def unproject(winx, winy, winz, modelview, projection, viewport): 694 | ''' Unproject a point from the screen and return the object coordinates. ''' 695 | m = Matrix(4) 696 | IN = vector.Vector(4).zero() 697 | objCoord = vector.Vector(3).zero() 698 | 699 | A = projection * modelview 700 | m = A.inverse() 701 | 702 | IN.vector[0] = (winx - viewport[0]) / viewport[2] * 2.0 - 1.0 703 | IN.vector[1] = (winy - viewport[1]) / viewport[3] * 2.0 - 1.0 704 | IN.vector[2] = 2.0 * winz - 1.0 705 | IN.vector[3] = 1.0 706 | 707 | OUT = m * IN 708 | if(OUT.vector[3] == 0.0): 709 | return vector.Vector(3).zero() 710 | 711 | OUT.vector[3] = 1.0 / OUT.vector[3] 712 | objCoord.vector[0] = OUT.vector[0] * OUT.vector[3] 713 | objCoord.vector[1] = OUT.vector[1] * OUT.vector[3] 714 | objCoord.vector[2] = OUT.vector[2] * OUT.vector[3] 715 | return objCoord 716 | -------------------------------------------------------------------------------- /gem/plane.py: -------------------------------------------------------------------------------- 1 | import six.moves as sm 2 | from gem import vector 3 | 4 | def flip(plane): 5 | ''' Flips the plane.''' 6 | fA = -plane[0] 7 | fB = -plane[1] 8 | fC = -plane[2] 9 | fD = -plane[3] 10 | fNormal = -plane[4] 11 | return [fA, fB, fC, fD, fNormal] 12 | 13 | def normalize(pdata): 14 | ''' Return the normalized plane.''' 15 | vec = vector.Vector(3, data=pdata) 16 | vecN = vec.normalize() 17 | 18 | length = vecN.magnitude() 19 | 20 | if length is not 0: 21 | return vecN.vector[0], vecN.vector[1], vecN.vector[2], pdata[3] / length 22 | else: 23 | print("Plane fail to normalize due to zero division.") 24 | return 0.0, 0.0, 0.0, 0.0 25 | 26 | class Plane(object): 27 | def __init__(self): 28 | ''' Plane class constructor. ''' 29 | self.normal = vector.Vector(3, data=[0.0, 0.0, 0.0]) 30 | self.a = 0 31 | self.b = 0 32 | self.c = 0 33 | self.d = 0 34 | 35 | def clone(self): 36 | '''Create a new Plane with similar propertise.''' 37 | nPlane = Plane() 38 | nPlane.normal = self.normal.clone() 39 | nPlane.a = self.a 40 | nPlane.b = self.b 41 | nPlane.c = self.c 42 | nPlane.d = self.d 43 | return nPlane 44 | 45 | def fromCoeffs(self, a, b, c, d): 46 | ''' Create the plane from A,B,C,D. ''' 47 | self.a = a 48 | self.b = b 49 | self.c = c 50 | self.d = d 51 | self.normal = vector.cross(b - a, c - a).normalize() 52 | 53 | def fromPoints(self, a, b, c): 54 | '''Calculate the plane from A,B,C.''' 55 | self.a = a 56 | self.b = b 57 | self.c = c 58 | self.normal = vector.cross(b - a, c - a).normalize() 59 | self.d = self.normal.dot(self.a) 60 | 61 | def i_flip(self): 62 | ''' Flip the plane in its place. ''' 63 | data = flip([self.a, self.b, self.c, self.d, self.normal]) 64 | self.a = data[0] 65 | self.b = data[1] 66 | self.c = data[2] 67 | self.d = data[3] 68 | self.normal = data[4] 69 | return self 70 | 71 | def flip(self): 72 | ''' Return a flipped plane. ''' 73 | nPlane = Plane() 74 | data = flip([self.a, self.b, self.c, self.d, self.normal]) 75 | nPlane.a = data[0] 76 | nPlane.b = data[1] 77 | nPlane.c = data[2] 78 | nPlane.d = data[3] 79 | nPlane.normal = data[4] 80 | return nPlane 81 | 82 | def dot(self, vec): 83 | ''' Return the dot product between a plane and 4D vector. ''' 84 | return self.a * vec.vector[0] + self.b * vec.vector[1] + self.c * vec.vector[2] + self.d * vec.vector[3] 85 | 86 | def i_normalize(self): 87 | ''' Normalize the vector in place. ''' 88 | pdata = [self.a, self.b, self.c, self.d] 89 | self.a, self.b, self.c, self.d = normalize(pdata) 90 | return self 91 | 92 | def normalize(self): 93 | ''' Return the normalized plane.''' 94 | nPlane = Plane().clone() 95 | pdata = [self.a, self.b, self.c, self.d] 96 | nPlane.a, nPlane.b, nPlane.c, nPlane.d = normalize(pdata) 97 | return nPlane 98 | 99 | def bestFitNormal(self, vecList): 100 | ''' Pass in a list of vectors to find the best fit normal. ''' 101 | output = vector.Vector(3).zero() 102 | for i in sm.range(len(vecList)): 103 | output.vector[0] += (vecList[i].vector[2] + vecList[i + 1].vector[2]) * (vecList[i].vector[1] - vecList[i + 1].vector[1]) 104 | output.vector[1] += (vecList[i].vector[0] + vecList[i + 1].vector[0]) * (vecList[i].vector[2] - vecList[i + 1].vector[2]) 105 | output.vector[2] += (vecList[i].vector[1] + vecList[i + 1].vector[1]) * (vecList[i].vector[0] - vecList[i + 1].vector[0]) 106 | return output.normalize() 107 | 108 | def bestFitD(self, vecList, bestFitNormal): 109 | ''' Returns the best fit D from a list of vectors using the best fit normal. ''' 110 | val = 0.0 111 | for vec in vecList: 112 | val += vec.dot(bestFitNormal) 113 | return val / len(vecList) 114 | 115 | def point_location(self, plane, point): 116 | ''' Returns the location of the point. Point is a tuple. ''' 117 | # If s > 0 then the point is on the same side as the normal. (front) 118 | # If s < 0 then the point is on the opposide side of the normal. (back) 119 | # If s = 0 then the point lies on the plane. 120 | s = plane.a * point[0] + plane.b * point[1] + plane.c * point[2] + plane.d 121 | 122 | if s > 0: 123 | return 1 124 | elif s < 0: 125 | return -1 126 | elif s == 0: 127 | return 0 128 | else: 129 | print("Not a clue where the point is.") 130 | 131 | -------------------------------------------------------------------------------- /gem/quaternion.py: -------------------------------------------------------------------------------- 1 | import math 2 | import six.moves as sm 3 | from gem import vector 4 | from gem import matrix 5 | 6 | def quat_identity(): 7 | ''' Returns the quaternion identity. ''' 8 | return [1.0, 0.0, 0.0, 0.0] 9 | 10 | def quat_add(quat, quat1): 11 | ''' Add two quaternions. ''' 12 | return [quat[0] + quat1[0], quat[1] + quat1[1], quat[2] + quat1[2], quat[3] + quat1[3]] 13 | 14 | def quat_sub(quat, quat1): 15 | ''' Subtract two quaternions. ''' 16 | return [quat[0] - quat1[0], quat[1] - quat1[1], quat[2] - quat1[2], quat[3] - quat1[3]] 17 | 18 | def quat_mul_quat(quat, quat1): 19 | ''' Multiply a quaternion with a quaternion. ''' 20 | w = quat[0] * quat1[0] - quat[1] * quat1[1] - quat[2] * quat1[2] - quat[3] * quat1[3] 21 | x = quat[0] * quat1[1] + quat[1] * quat1[0] + quat[2] * quat1[3] - quat[3] * quat1[2] 22 | y = quat[0] * quat1[2] + quat[2] * quat1[0] + quat[3] * quat1[1] - quat[1] * quat1[3] 23 | z = quat[0] * quat1[3] + quat[3] * quat1[0] + quat[1] * quat1[2] - quat[2] * quat1[1] 24 | return [w, x, y, z] 25 | 26 | def quat_mul_vect(quat, vect): 27 | ''' Multiply a quaternion with a vector. ''' 28 | w = -quat[1] * vect[0] - quat[2] * vect[1] - quat[3] * vect[2] 29 | x = quat[0] * vect[0] + quat[2] * vect[2] - quat[3] * vect[1] 30 | y = quat[0] * vect[1] + quat[3] * vect[0] - quat[1] * vect[2] 31 | z = quat[0] * vect[2] + quat[1] * vect[1] - quat[2] * vect[0] 32 | return [w, x, y, z] 33 | 34 | def quat_mul_float(quat, scalar): 35 | ''' Multiply a quaternion with a scalar (float). ''' 36 | return [quat[0] * scalar, quat[1] * scalar, quat[2] * scalar, quat[3] * scalar] 37 | 38 | def quat_div_float(quat, scalar): 39 | ''' Divide a quaternion with a scalar (float). ''' 40 | return [quat[0] / scalar, quat[1] / scalar, quat[2] / scalar, quat[3] / scalar] 41 | 42 | def quat_neg(quat): 43 | ''' Negate the elements of a quaternion. ''' 44 | return [-quat[0], -quat[1], -quat[2], -quat[3]] 45 | 46 | def quat_dot(quat1, quat2): 47 | ''' Dot product between two quaternions. Returns a scalar. ''' 48 | rdp= 0 49 | for i in sm.range(4): 50 | rdp += quat1[i] * quat2[i] 51 | return rdp 52 | 53 | def quat_magnitude(quat): 54 | ''' Compute magnitude of a quaternion. Returns a scalar. ''' 55 | rmg = 0 56 | for i in sm.range(4): 57 | rmg += quat[i] * quat[i] 58 | return math.sqrt(rmg) 59 | 60 | def quat_normalize(quat): 61 | ''' Returns a normalized quaternion. ''' 62 | length = quat_magnitude(quat) 63 | oquat = quat_identity() 64 | if length is not 0: 65 | for i in sm.range(4): 66 | oquat[i] = quat[i] / length 67 | return oquat 68 | 69 | def quat_conjugate(quat): 70 | ''' Returns the conjugate of a quaternion. ''' 71 | idquat = quat_identity() 72 | for i in sm.range(4): 73 | idquat[i] = -quat[i] 74 | idquat[0] = -idquat[0] 75 | return idquat 76 | 77 | def quat_inverse(quat): 78 | ''' Returns the inverse of a quaternion. ''' 79 | lengthSquared = quat[0] * quat[0] + quat[1] * quat[1] + quat[2] * quat[2] + quat[3] * quat[3] 80 | 81 | return [quat[0] / lengthSquared, 82 | quat[1] / lengthSquared, 83 | quat[2] / lengthSquared, 84 | quat[3] / lengthSquared] 85 | 86 | def quat_from_axis_angle(axis, theta): 87 | ''' Returns a quaternion from a given axis and a angle. ''' 88 | thetaOver2 = theta * 0.5 89 | sto2 = math.sin(math.radians(thetaOver2)) 90 | cto2 = math.cos(math.radians(thetaOver2)) 91 | 92 | quat1List = [] 93 | if isinstance(axis, vector.Vector): 94 | axis.i_normalize() 95 | quat1List = [cto2, axis.vector[0] * sto2, axis.vector[1] * sto2, axis.vector[2] * sto2] 96 | elif isinstance(axis, list): 97 | naxis = axis.normalize() 98 | quat1List = (cto2, naxis[0] * sto2, naxis[1] * sto2, naxis[2] * sto2) 99 | else: 100 | return NotImplemented 101 | 102 | return Quaternion(data=quat1List) 103 | 104 | def quat_rotate(origin, axis, theta): 105 | ''' Returns a vector that is rotated around an axis. ''' 106 | thetaOver2 = theta * 0.5 107 | sinThetaOver2 = math.sin(math.radians(thetaOver2)) 108 | cosThetaOver2 = math.cos(math.radians(thetaOver2)) 109 | quat = Quaternion(data = [cosThetaOver2, axis[0] * sinThetaOver2, axis[1] * sinThetaOver2, axis[2] * sinThetaOver2]) 110 | rotation = (quat * origin) * quat.conjugate() 111 | return vector.Vector(3, data=[rotation.data[1], rotation.data[2], rotation.data[3]]) 112 | 113 | def quat_rotate_x_from_angle(theta): 114 | ''' Creates a quaternion that rotates around X axis given an angle. ''' 115 | thetaOver2 = theta * 0.5 116 | cto2 = math.cos(thetaOver2) 117 | sto2 = math.sin(thetaOver2) 118 | return [cto2, sto2, 0.0, 0.0] 119 | 120 | def quat_rotate_y_from_angle(theta): 121 | ''' Creates a quaternion that rotates around Y axis given an angle. ''' 122 | thetaOver2 = theta * 0.5 123 | cto2 = math.cos(thetaOver2) 124 | sto2 = math.sin(thetaOver2) 125 | return [cto2, 0.0, sto2, 0.0] 126 | 127 | def quat_rotate_z_from_angle(theta): 128 | ''' Creates a quaternion that rotates around Z axis given an angle. ''' 129 | thetaOver2 = theta * 0.5 130 | cto2 = math.cos(thetaOver2) 131 | sto2 = math.sin(thetaOver2) 132 | return [cto2, 0.0, 0.0, sto2] 133 | 134 | def quat_rotate_from_axis_angle(axis, theta): 135 | ''' Creates a quaternion that rotates around an arbitary axis given an angle. ''' 136 | thetaOver2 = theta * 0.5 137 | sto2 = math.sin(math.radians(thetaOver2)) 138 | cto2 = math.cos(math.radians(thetaOver2)) 139 | 140 | quat1List = [] 141 | if isinstance(axis, vector.Vector): 142 | axis.i_normalize() 143 | quat1List = [cto2, axis.vector[0] * sto2, axis.vector[1] * sto2, axis.vector[2] * sto2] 144 | elif isinstance(axis, list): 145 | naxis = axis.normalize() 146 | quat1List = (cto2, naxis[0] * sto2, naxis[1] * sto2, naxis[2] * sto2) 147 | else: 148 | return NotImplemented 149 | 150 | quat1 = Quaternion(data=quat1List) 151 | rotation = (quat1 * axis) * quat1.conjugate() 152 | return rotation 153 | 154 | def quat_rotate_vector(quat, vec): 155 | ''' Rotates a vector by a quaternion, returns a vector. ''' 156 | outQuat = (quat * vec) * quat.conjugate() 157 | return vector.Vector(3, data=[outQuat.data[1], outQuat.data[2], outQuat.data[3]]) 158 | 159 | def quat_pow(quat, exp): 160 | ''' Returns a quaternion to the power of N. ''' 161 | quatExp = Quaternion() 162 | 163 | if quat.data[0] is not 0.0: 164 | angle = math.acos(quat.data[0]) 165 | newAngle = angle * exp 166 | quatExp.data[0] = math.cos(newAngle) 167 | divAngle = math.sin(newAngle) / math.sin(angle) 168 | quatExp.data[1] *= divAngle 169 | quatExp.data[2] *= divAngle 170 | quatExp.data[3] *= divAngle 171 | return quatExp 172 | 173 | def quat_log(quat): 174 | ''' Returns the logatithm of a quaternion. ''' 175 | alpha = math.acos(quat.data[0]) 176 | sinAlpha = math.sin(alpha) 177 | 178 | outList = [1.0, 0.0, 0.0, 0.0] 179 | 180 | if sinAlpha > 0.0: 181 | outList[1] = quat.data[1] * alpha / sinAlpha 182 | outList[2] = quat.data[2] * alpha / sinAlpha 183 | outList[3] = quat.data[3] * alpha / sinAlpha 184 | else: 185 | outList = quat.data 186 | 187 | return outList 188 | 189 | def quat_lerp(quat0, quat1, t): 190 | ''' Linear interpolation between two quaternions. ''' 191 | k0 = 1.0 - t 192 | k1 = t 193 | 194 | output = Quaternion() 195 | output = (quat0 * k0) + (quat1 * k1) 196 | 197 | return output 198 | 199 | def quat_slerp(quat0, quat1, t): 200 | ''' Spherical interpolation between two quaternions. ''' 201 | k0 = 0.0 202 | k1 = 0.0 203 | 204 | output = Quaternion() 205 | quat1Neg = Quaternion() 206 | cosTheta = quat0.dot(quat1) 207 | 208 | if cosTheta < 0.0: 209 | quat1Neg = quat1.negate() 210 | cosTheta = -cosTheta 211 | else: 212 | quat1Neg = quat1 213 | 214 | if cosTheta > 0.999: 215 | k0 = 1.0 - t 216 | k1 = t 217 | else: 218 | theta = math.acos(cosTheta) 219 | oneOverSinTheta = 1.0 / math.sin(theta) 220 | k0 = math.sin((1.0 - t) * theta) * oneOverSinTheta 221 | k1 = math.sin(t * theta) * oneOverSinTheta 222 | 223 | output = (quat0 * k0) + (quat1Neg * k1) 224 | 225 | return output 226 | 227 | def quat_slerp_no_invert(quat0, quat1, t): 228 | ''' Spherical interpolation between two quaternions, it does not check for theta > 90. Used by SQUAD. ''' 229 | dotP = quat0.dot(quat1) 230 | 231 | output = Quaternion() 232 | 233 | if (dotP > -0.95) and (dotP < 0.95): 234 | angle = math.acos(dotP) 235 | k0 = math.sin(angle * (1.0 - t)) / math.sin(angle) 236 | k1 = math.sin(t * angle) / math.sin(angle) 237 | 238 | output = (quat0 * k0) + (quat1 * k1) 239 | else: 240 | output = quat_lerp(quat0, quat1, t) 241 | 242 | return output 243 | 244 | def quat_squad(quat0, quat1, quat2, t): 245 | ''' Quaternion splines. ''' 246 | return quat_slerp_no_invert(quat_slerp_no_invert(quat0, quat2, t), quat_slerp_no_invert(quat0, quat1, t), 2 * t(1 - t)) 247 | 248 | def quat_to_matrix(quat): 249 | ''' Converts a quaternion to a rotational 4x4 matrix. ''' 250 | x2 = quat.data[1] * quat.data[1] 251 | y2 = quat.data[2] * quat.data[2] 252 | z2 = quat.data[3] * quat.data[3] 253 | xy = quat.data[1] * quat.data[2] 254 | xz = quat.data[1] * quat.data[3] 255 | yz = quat.data[2] * quat.data[3] 256 | wx = quat.data[0] * quat.data[1] 257 | wy = quat.data[0] * quat.data[2] 258 | wz = quat.data[0] * quat.data[3] 259 | 260 | outputMatrix = matrix.Matrix(4) 261 | 262 | outputMatrix.matrix[0][0] = 1.0 - 2.0 * y2 - 2.0 * z2 263 | outputMatrix.matrix[0][1] = 2.0 * xy + 2.0 * wz 264 | outputMatrix.matrix[0][2] = 2.0 * xz - 2.0 * wy 265 | outputMatrix.matrix[0][3] = 0.0 266 | 267 | outputMatrix.matrix[1][0] = 2.0 * xy - 2.0 * wz 268 | outputMatrix.matrix[1][1] = 1.0 - 2.0 * x2 - 2.0 * z2 269 | outputMatrix.matrix[1][2] = 2.0 * yz + 2.0 * wx 270 | outputMatrix.matrix[1][3] = 0.0 271 | 272 | outputMatrix.matrix[2][0] = 2.0 * xz + 2.0 * wy 273 | outputMatrix.matrix[2][1] = 2.0 * yz - 2.0 * wx 274 | outputMatrix.matrix[2][2] = 1.0 - 2.0 * x2 - 2.0 * y2 275 | outputMatrix.matrix[2][3] = 0.0 276 | 277 | return outputMatrix 278 | 279 | class Quaternion(object): 280 | 281 | def __init__(self, data=None): 282 | 283 | if data is None: 284 | self.data = quat_identity() 285 | else: 286 | self.data = data 287 | 288 | def __add__(self, other): 289 | if isinstance(other, Quaternion): 290 | return Quaternion(quat_add(self.data, other.data)) 291 | else: 292 | return NotImplemented 293 | 294 | def __iadd__(self, other): 295 | if isinstance(other, Quaternion): 296 | self.data = quat_add(self.data, other.data) 297 | return self 298 | else: 299 | return NotImplemented 300 | 301 | def __sub__(self, other): 302 | if isinstance(other, Quaternion): 303 | return Quaternion(quat_sub(self.data, other.data)) 304 | else: 305 | return NotImplemented 306 | 307 | def __isub__(self, other): 308 | if isinstance(other, Quaternion): 309 | self.data = quat_sub(self.data, other.data) 310 | return self 311 | else: 312 | return NotImplemented 313 | 314 | def __mul__(self, other): 315 | if isinstance(other, Quaternion): 316 | return Quaternion(quat_mul_quat(self.data, other.data)) 317 | elif isinstance(other, vector.Vector): 318 | return Quaternion(quat_mul_vect(self.data, other.vector)) 319 | elif isinstance(other, float): 320 | return Quaternion(quat_mul_float(self.data, other)) 321 | else: 322 | return NotImplemented 323 | 324 | def __imul__(self, other): 325 | if isinstance(other, Quaternion): 326 | self.data = quat_mul_quat(self.data, other.data) 327 | return self 328 | elif isinstance(other, vector.Vector): 329 | self.data = quat_mul_vect(self.data, other.data) 330 | return self 331 | elif isinstance(other, float): 332 | self.data = quat_mul_float(self.data, other) 333 | return self 334 | else: 335 | return NotImplemented 336 | 337 | def __div__(self, other): 338 | if isinstance(other, float): 339 | return Quaternion(quat_div_float(self.data, other)) 340 | else: 341 | return NotImplemented 342 | 343 | def __idiv__(self, other): 344 | if isinstance(other, float): 345 | self.data = quat_div_float(self.data, other) 346 | return self 347 | else: 348 | return NotImplemented 349 | 350 | def i_negate(self): 351 | self.data = quat_neg(self.data) 352 | return self 353 | 354 | def negate(self): 355 | quatList = quat_neg(self.data) 356 | return Quaternion(quatList) 357 | 358 | def i_identity(self): 359 | self.data = quat_identity() 360 | return self 361 | 362 | def identity(self): 363 | quatList = quat_identity() 364 | return Quaternion(quatList) 365 | 366 | def magnitude(self): 367 | return quat_magnitude(self.data) 368 | 369 | def dot(self, quat2): 370 | if isinstance(quat2, Quaternion): 371 | return quat_dot(self.data, quat2.data) 372 | else: 373 | return NotImplemented 374 | 375 | def i_normalize(self): 376 | self.data = quat_normalize(self.data) 377 | return self 378 | 379 | def normalize(self): 380 | quatList = quat_normalize(self.data) 381 | return Quaternion(quatList) 382 | 383 | def i_conjugate(self): 384 | self.data = quat_conjugate(self.data) 385 | return self 386 | 387 | def conjugate(self): 388 | quatList = quat_conjugate(self.data) 389 | return Quaternion(quatList) 390 | 391 | def inverse(self): 392 | quatList = quat_inverse(self.data) 393 | return Quaternion(quatList) 394 | 395 | def pow(self, e): 396 | exponent = e 397 | return quat_pow(self, exponent) 398 | 399 | def log(self): 400 | return quat_log(self) 401 | 402 | def lerp(self, quat1, time): 403 | return quat_lerp(self, quat1, time) 404 | 405 | def slerp(self, quat1, time): 406 | return quat_slerp(self, quat1, time) 407 | 408 | def slerp_no_invert(self, quat1, time): 409 | return quat_slerp_no_invert(self, quat1, time) 410 | 411 | def squad(self, quat1, quat2, time): 412 | return quat_squad(self, quat1, quat2, time) 413 | 414 | def toMatrix(self): 415 | return quat_to_matrix(self) 416 | 417 | # The following are used for orientation and motion 418 | def getForward(self): 419 | ''' Returns the forward vector. ''' 420 | return quat_rotate_vector(self, vector.Vector(3, data=[0.0, 0.0, 1.0])) 421 | 422 | def getBack(self): 423 | ''' Returns the backwards vector. ''' 424 | return quat_rotate_vector(self, vector.Vector(3, data=[0.0, 0.0, -1.0])) 425 | 426 | def getLeft(self): 427 | ''' Returns the left vector. ''' 428 | return quat_rotate_vector(self, vector.Vector(3, data=[-1.0, 0.0, 0.0])) 429 | 430 | def getRight(self): 431 | ''' Returns the right vector. ''' 432 | return quat_rotate_vector(self, vector.Vector(3, data=[1.0, 0.0, 0.0])) 433 | 434 | def getUp(self): 435 | ''' Returns the up vector. ''' 436 | return quat_rotate_vector(self, vector.Vector(3, data=[0.0, 1.0, 0.0])) 437 | 438 | def getDown(self): 439 | ''' Returns the down vector. ''' 440 | return quat_rotate_vector(self, vector.Vector(3, data=[0.0, -1.0, 0.0])) 441 | 442 | def quat_from_matrix(matrix): 443 | ''' Converts a 4x4 rotational matrix to quaternion. ''' 444 | fourXSquaredMinus1 = matrix.matrix[0][0] - matrix.matrix[1][1] - matrix.matrix[2][2] 445 | fourYSquaredMinus1 = matrix.matrix[1][1] - matrix.matrix[0][0] - matrix.matrix[2][2] 446 | fourZSquaredMinus1 = matrix.matrix[2][2] - matrix.matrix[0][0] - matrix.matrix[1][1] 447 | fourWSquaredMinus1 = matrix.matrix[0][0] + matrix.matrix[1][1] + matrix.matrix[2][2] 448 | 449 | biggestIndex = 0 450 | 451 | fourBiggestSquaredMinus1 = fourWSquaredMinus1 452 | 453 | if (fourXSquaredMinus1 > fourBiggestSquaredMinus1): 454 | biggestIndex = 1 455 | elif(fourYSquaredMinus1 > fourBiggestSquaredMinus1): 456 | biggestIndex = 2 457 | elif(fourZSquaredMinus1 > fourBiggestSquaredMinus1): 458 | biggestIndex = 3 459 | 460 | biggestVal = math.sqrt(fourBiggestSquaredMinus1 + 1) * 0.5 461 | mult = 0.25 / biggestVal 462 | 463 | rquat = Quaternion() 464 | 465 | if biggestIndex is 0: 466 | rquat.data[0] = biggestVal 467 | rquat.data[1] = (matrix.matrix[1][2] - matrix.matrix[2][1]) * mult 468 | rquat.data[2] = (matrix.matrix[2][0] - matrix.matrix[0][2]) * mult 469 | rquat.data[3] = (matrix.matrix[0][1] - matrix.matrix[1][0]) * mult 470 | return rquat 471 | 472 | if biggestIndex is 1: 473 | rquat.data[0] = (matrix.matrix[1][2] - matrix.matrix[2][1]) * mult 474 | rquat.data[1] = biggestVal 475 | rquat.data[2] = (matrix.matrix[0][1] + matrix.matrix[1][0]) * mult 476 | rquat.data[3] = (matrix.matrix[2][0] + matrix.matrix[0][2]) * mult 477 | return rquat 478 | 479 | if biggestIndex is 2: 480 | rquat.data[0] = (matrix.matrix[2][0] - matrix.matrix[0][2]) * mult 481 | rquat.data[1] = (matrix.matrix[0][1] + matrix.matrix[1][0]) * mult 482 | rquat.data[2] = biggestVal 483 | rquat.data[3] = (matrix.matrix[1][2] + matrix.matrix[2][1]) * mult 484 | return rquat 485 | 486 | if biggestIndex is 3: 487 | rquat.data[0] = (matrix.matrix[0][1] - matrix.matrix[1][0]) * mult 488 | rquat.data[1] = (matrix.matrix[2][0] + matrix.matrix[0][2]) * mult 489 | rquat.data[2] = (matrix.matrix[1][2] + matrix.matrix[2][1]) * mult 490 | rquat.data[3] = biggestVal 491 | return rquat 492 | -------------------------------------------------------------------------------- /gem/ray.py: -------------------------------------------------------------------------------- 1 | import gem.vector as vec 2 | 3 | # Ray Class 4 | class Ray(object): 5 | def __init__(self, startVector, dirVector): 6 | ''' Initiated a ray with the start vector and direction vector. ''' 7 | self.start = startVector 8 | self.dir = dirVector 9 | self.distance = self.dir.magnitude() 10 | self.dir.i_normalize() 11 | # The end is used when intersections are added so 12 | # we can know where the ray stops. 13 | self.end = vec.Vector(3) 14 | 15 | def duplicate(self): 16 | ''' Make a duplicate of the ray. ''' 17 | return Ray(self.start, self.dir) 18 | 19 | def roateUsingMatrix(self, matrix): 20 | ''' Rotate the ray using a matrix. ''' 21 | self.start = matrix * self.start 22 | self.dir = matrix * self.dir 23 | self.dir.i_normalize() 24 | 25 | def rotateUsingQuaternion(self, quat1): 26 | ''' Rotate the ray using a quaternion. ''' 27 | self.start = quat1 * self.start 28 | self.dir = quat1 * self.dir 29 | self.dir.i_normalize() 30 | 31 | def translate(self, matrix): 32 | ''' Translate the ray using a matrix. ''' 33 | self.dir = matrix * self.dir 34 | self.distance = self.dir.magnitude() 35 | self.dir.i_normalize() 36 | 37 | def output(self): 38 | ''' Show information regarding the ray's behaviour. ''' 39 | print ("Ray:") 40 | print ("Start:") 41 | print (self.start.vector) 42 | print ("Dir:") 43 | print (self.dir.vector) 44 | -------------------------------------------------------------------------------- /gem/vector.py: -------------------------------------------------------------------------------- 1 | import math 2 | import six.moves as sm 3 | 4 | REFRENCE_VECTOR_2 = [0.0 for x in sm.range(2)] 5 | REFRENCE_VECTOR_3 = [0.0 for x in sm.range(3)] 6 | REFRENCE_VECTOR_4 = [0.0 for x in sm.range(4)] 7 | 8 | def zero_vector(size): 9 | ''' Return a zero filled vector list of the requested size ''' 10 | # Return a copy of the reference vector, this is faster than making a new one 11 | if size == 2: 12 | return REFRENCE_VECTOR_2[:] 13 | if size == 3: 14 | return REFRENCE_VECTOR_3[:] 15 | if size == 4: 16 | return REFRENCE_VECTOR_4[:] 17 | else: 18 | return [0.0 for _ in sm.range(size)] 19 | 20 | IREFRENCE_VECTOR_2 = [1.0 for x in sm.range(2)] 21 | IREFRENCE_VECTOR_3 = [1.0 for x in sm.range(3)] 22 | IREFRENCE_VECTOR_4 = [1.0 for x in sm.range(4)] 23 | 24 | def one_vector(size): 25 | ''' Return a one filled vector list of the requested size ''' 26 | # Return a copy of the reference vector, this is faster than making a new one 27 | if size == 2: 28 | return IREFRENCE_VECTOR_2[:] 29 | if size == 3: 30 | return IREFRENCE_VECTOR_3[:] 31 | if size == 4: 32 | return IREFRENCE_VECTOR_4[:] 33 | else: 34 | return [1.0 for _ in sm.range(size)] 35 | 36 | # Vector Functions 37 | def lerp(vecA, vecB, time): 38 | '''Linear interpolation between two vectors.''' 39 | #Note: The commented out part is the precise version of linear interpolation 40 | #return (vecA * time) + (vecB * (1.0 - time)) 41 | return vecA + ((vecB - vecA) * time) 42 | 43 | def cross(vecA, vecB): 44 | ''' Cross product between two 3D vectors, returns a vector.''' 45 | vecC = Vector(3) 46 | vecC.vector[0] = vecA.vector[1] * vecB.vector[2] - vecA.vector[2] * vecB.vector[1] 47 | vecC.vector[1] = vecA.vector[2] * vecB.vector[0] - vecA.vector[0] * vecB.vector[2] 48 | vecC.vector[2] = vecA.vector[0] * vecB.vector[1] - vecA.vector[1] * vecB.vector[0] 49 | return vecC 50 | 51 | def reflect(incidentVec, normal): 52 | '''Reflect a vector''' 53 | return incidentVec - (normal * (2.0 * incidentVec.dot(normal))) 54 | 55 | def refract(IOR, incidentVec, normal): 56 | ''' Refract a vector. ''' 57 | dotNI = normal.dot(incidentVec) 58 | k = 1.0 - IOR * IOR * IOR * (1.0 - dotNI * dotNI) 59 | 60 | if k < 0.0: 61 | return Vector(normal.size) 62 | else: 63 | scalar = IOR * dotNI + math.sqrt(k) 64 | return (IOR * incidentVec) - (scalar * normal) 65 | 66 | # 2D - get angle of the vector 67 | def toAngle(vector): 68 | return math.atan2(vector[1], vector[0]) 69 | 70 | # 2D - 90 degree rotation 71 | def lperp(vector): 72 | return Vector(2, data = [-vector[1], vector[0]]) 73 | 74 | # 2D - -90 degree rotation 75 | def rperp(vector): 76 | return Vector(2, data = [vector[1], -vector[0]]) 77 | 78 | def vec_add(size, vecA, vecB): 79 | return [(vecA[i] + vecB[i]) for i in sm.range(size)] 80 | 81 | def s_vec_add(size, vecA, scalar): 82 | return [(vecA[i] + scalar) for i in sm.range(size)] 83 | 84 | def vec_sub(size, vecA, vecB): 85 | return [(vecA[i] - vecB[i]) for i in sm.range(size)] 86 | 87 | def s_vec_sub(size, vecA, scalar): 88 | return [(vecA[i] - scalar) for i in sm.range(size)] 89 | 90 | def vec_mul(size, vecA, scalar): 91 | return [(vecA[i] * scalar) for i in sm.range(size)] 92 | 93 | def vec_div(size, vecA, scalar): 94 | return [(vecA[i] / scalar) for i in sm.range(size)] 95 | 96 | def vec_neg(size, vecA): 97 | return [(-vecA[i]) for i in sm.range(size)] 98 | 99 | def dot(size, vecA, vecB): 100 | dp = 0 101 | for i in sm.range(size): 102 | dp += vecA[i] * vecB[i] 103 | return dp 104 | 105 | def magnitude(size, vecA): 106 | mg = 0 107 | for i in sm.range(size): 108 | mg += vecA[i] * vecA[i] 109 | return math.sqrt(mg) 110 | 111 | def normalize(size, vecA): 112 | length = magnitude(size, vecA) 113 | temp = zero_vector(size) 114 | if length is not 0: 115 | for i in sm.range(size): 116 | temp[i] = vecA[i] / length 117 | return temp 118 | 119 | def maxV(size, vecA, vecB): 120 | return [vecA[i] if vecA[i] > vecB[i] else vecB[i] for i in sm.range(size)] 121 | 122 | def minV(size, vecA, vecB): 123 | return [vecA[i] if vecA[i] < vecB[i] else vecB[i] for i in sm.range(size)] 124 | 125 | def maxS(size, vecA): 126 | mScalar = vecA[0] 127 | for i in sm.range(size): 128 | if vecA[i] > mScalar: 129 | mScalar = vecA[i] 130 | return mScalar 131 | 132 | def minS(size, vecA): 133 | mScalar = vecA[0] 134 | for i in sm.range(size): 135 | if vecA[i] < mScalar: 136 | mScalar = vecA[i] 137 | return mScalar 138 | 139 | def clamp(size, value, minS, maxS): 140 | output = value 141 | for i in sm.range(size): 142 | # Check to see if greater than max 143 | output[i] = maxS[i] if output[i] > maxS[i] else output[i] 144 | #output[i] = (output[i] > max[i]) ? max[i] : output[i] 145 | # Check to see is less than min 146 | output[i] = minS[i] if output[i] < minS[i] else output[i] 147 | #output[i] = (output[i] < min[i]) ? min[i] : output[i] 148 | return Vector(size, data=output) 149 | 150 | def transform(size, position, matrix): 151 | output = zero_vector(size) 152 | for i in sm.range(size): 153 | for j in sm.range(size): 154 | output[i] += position[j] * matrix[i][j] 155 | output[i] += matrix[size-1][i] 156 | 157 | return output 158 | 159 | class Vector(object): 160 | def __init__(self, size, data=None): 161 | self.size = size 162 | 163 | if data is None: 164 | self.vector = zero_vector(self.size) 165 | else: 166 | self.vector = data 167 | 168 | def __repr__(self): 169 | return 'Vector: size:{} , data:{}'.format(self.size, self.vector) 170 | 171 | def __add__(self, other): 172 | if isinstance(other, Vector): 173 | vecList = vec_add(self.size, self.vector, other.vector) 174 | return Vector(self.size, data=vecList) 175 | elif isinstance(other, int) or isinstance(other, float): 176 | vecList = s_vec_add(self.size, self.vector, other) 177 | return Vector(self.size, data=vecList) 178 | else: 179 | return NotImplemented 180 | 181 | def __iadd__(self, other): 182 | if isinstance(other, Vector): 183 | self.vector = vec_add(self.size, self.vector, other.vector) 184 | return self 185 | elif isinstance(other, int) or isinstance(other, float): 186 | self.vector = s_vec_add(self.size, self.vector, other) 187 | return self 188 | else: 189 | return NotImplemented 190 | 191 | def __sub__(self, other): 192 | if isinstance(other, Vector): 193 | vecList = vec_sub(self.size, self.vector, other.vector) 194 | return Vector(self.size, data=vecList) 195 | elif isinstance(other, int) or isinstance(other, float): 196 | vecList = s_vec_sub(self.size, self.vector, other) 197 | return Vector(self.size, data=vecList) 198 | else: 199 | return NotImplemented 200 | 201 | def __isub__(self, other): 202 | if isinstance(other, Vector): 203 | self.vector = vec_sub(self.size, self.vector, other.vector) 204 | return self 205 | elif isinstance(other, int) or isinstance(other, float): 206 | self.vector = s_vec_sub(self.size, self.vector, other) 207 | return self 208 | else: 209 | return NotImplemented 210 | 211 | def __mul__(self, scalar): 212 | if isinstance(scalar, int) or isinstance(scalar, float): 213 | vecList = vec_mul(self.size, self.vector, scalar) 214 | return Vector(self.size, data=vecList) 215 | else: 216 | return NotImplemented 217 | 218 | def __imul__(self, scalar): 219 | if isinstance(scalar, int) or isinstance(scalar, float): 220 | self.vector = vec_mul(self.size, self.vector, scalar) 221 | return self 222 | else: 223 | return NotImplemented 224 | 225 | def __div__(self, scalar): 226 | if isinstance(scalar, int) or isinstance(scalar, float): 227 | vecList = vec_div(self.size, self.vector, scalar) 228 | return Vector(self.size, data=vecList) 229 | else: 230 | return NotImplemented 231 | 232 | def __truediv__(self, scalar): 233 | if isinstance(scalar, int) or isinstance(scalar, float): 234 | vecList = vec_div(self.size, self.vector, scalar) 235 | return Vector(self.size, data=vecList) 236 | else: 237 | return NotImplemented 238 | 239 | def __idiv__(self, scalar): 240 | if isinstance(scalar, int) or isinstance(scalar, float): 241 | self.vector = vec_div(self.size, self.vector, scalar) 242 | return self 243 | else: 244 | return NotImplemented 245 | 246 | def __itruediv__(self, scalar): 247 | if isinstance(scalar, int) or isinstance(scalar, float): 248 | self.vector = vec_div(self.size, self.vector, scalar) 249 | return self 250 | else: 251 | return NotImplemented 252 | 253 | def __eq__(self, vecB): 254 | if isinstance(vecB, Vector): 255 | for i in range(self.size): 256 | if self.vector[i] != vecB.vector[i]: 257 | return False 258 | else: 259 | return True 260 | else: 261 | return NotImplemented 262 | 263 | def __ne__(self, vecB): 264 | if isinstance(vecB, Vector): 265 | for i in range(self.size): 266 | if self.vector[i] != vecB.vector[i]: 267 | return True 268 | else: 269 | return False 270 | else: 271 | return NotImplemented 272 | 273 | def __neg__(self): 274 | vecList = vec_neg(self.size, self.vector) 275 | return Vector(self.size, data=vecList) 276 | 277 | def clone(self): 278 | return Vector(self.size, data=self.vector[:]) 279 | 280 | def one(self): 281 | self.vector = one_vector(self.size) 282 | return self 283 | 284 | def zero(self): 285 | self.vector = zero_vector(self.size) 286 | return self 287 | 288 | def negate(self): 289 | vecList = vec_neg(self.size, self.vector) 290 | return Vector(self.size, data=vecList) 291 | 292 | def maxV(self, vecB): 293 | return Vector(self.size, data=maxV(self.size, self.vector, vecB.vector)) 294 | 295 | def maxS(self): 296 | return maxS(self.size, self.vector) 297 | 298 | def minV(self, vecB): 299 | return Vector(self.size, data=minV(self.size, self.vector, vecB.vector)) 300 | 301 | def minS(self): 302 | return minS(self.size, self.vector) 303 | 304 | def magnitude(self): 305 | return magnitude(self.size, self.vector) 306 | 307 | def clamp(self, size, value, minS, maxS): 308 | ''' Returns a new clamped vector. ''' 309 | return clamp(size, value, minS, maxS) 310 | 311 | def i_clamp(self, size, value, minS, maxS): 312 | ''' Clamp the vector into place. ''' 313 | self.vector = clamp(size, value, minS, maxS).vector 314 | return self 315 | 316 | def i_normalize(self): 317 | ''' Normalize the vector in place. ''' 318 | self.vector = normalize(self.size, self.vector) 319 | return self 320 | 321 | def normalize(self): 322 | ''' Return a new normalized vector. ''' 323 | vecList = normalize(self.size, self.vector) 324 | return Vector(self.size, data=vecList) 325 | 326 | def dot(self, vecB): 327 | ''' Return the dot product between two vectors. ''' 328 | if isinstance(vecB, Vector): 329 | return dot(self.size, self.vector, vecB.vector) 330 | else: 331 | return NotImplemented 332 | 333 | def isInSameDirection(self, otherVec): 334 | ''' Return a boolean if the input vector if is in the same direction as the one it's compared against. ''' 335 | if isinstance(otherVec, Vector): 336 | return self.dot(otherVec) > 0 337 | else: 338 | return NotImplemented 339 | 340 | def isInOppositeDirection(self, otherVec): 341 | ''' Return a boolean if the input vector if is in the opposite direction as the one it's compared against. ''' 342 | if isinstance(otherVec, Vector): 343 | return self.dot(otherVec) < 0 344 | else: 345 | return NotImplemented 346 | 347 | def barycentric(self, a, b, c): 348 | ''' Compute barycentric coordinates (u, v, w) for point p with respect to triangle (a, b, c). ''' 349 | # This method is shown in Christer Ericson's Real-Time Collision Detection book 350 | # Basically is cramer's rule to solve a linear system. 351 | # Returns [u, v, w] list 352 | 353 | v0 = b - a 354 | v1 = c - a 355 | v2 = self - a 356 | 357 | d00 = v0.dot(v0) 358 | d01 = v0.dot(v1) 359 | d11 = v1.dot(v1) 360 | d20 = v2.dot(v0) 361 | d21 = v2.dot(v1) 362 | 363 | denom = d00 * d11 - d01 * d01 364 | 365 | v = (d11 * d20 - d01 * d21) / denom 366 | w = (d00 * d21 - d01 * d20) / denom 367 | u = 1.0 - v - w 368 | 369 | return [u, v, w] 370 | 371 | def transform(self, position, matrix): 372 | ''' Transform the vector via a matrix and returns a new vector. ''' 373 | vecList = transform(self.size, position, matrix) 374 | return Vector(self.size, data=vecList) 375 | 376 | def i_transform(self, position, matrix): 377 | ''' Transform the vector via a matrix in place. ''' 378 | self.vector = transform(self.size, position, matrix) 379 | return self 380 | 381 | # Return common components of the vector as a group 382 | # Vector Swizzling, similar to GLSL 383 | def xy(self): 384 | return Vector(2, [self.vector[0], self.vector[1]]) 385 | 386 | def yz(self): 387 | return Vector(2, [self.vector[1], self.vector[2]]) 388 | 389 | def xz(self): 390 | return Vector(2, [self.vector[0], self.vector[2]]) 391 | 392 | def xw(self): 393 | return Vector(2, [self.vector[0], self.vector[3]]) 394 | 395 | def yw(self): 396 | return Vector(2, [self.vector[1], self.vector[3]]) 397 | 398 | def zw(self): 399 | return Vector(2, [self.vector[2], self.vector[3]]) 400 | 401 | def xyw(self): 402 | return Vector(3, [self.vector[0], self.vector[1], self.vector[3]]) 403 | 404 | def yzw(self): 405 | return Vector(3, [self.vector[1], self.vector[2], self.vector[3]]) 406 | 407 | def xzw(self): 408 | return Vector(3, [self.vector[0], self.vector[2], self.vector[3]]) 409 | 410 | def xyz(self): 411 | return Vector(3, [self.vector[0], self.vector[1], self.vector[2]]) 412 | 413 | # 3D vector identities 414 | def right(self): 415 | return Vector(3, data=[ 1.0, 0.0, 0.0]) 416 | 417 | def left(self): 418 | return Vector(3, data=[-1.0, 0.0, 0.0]) 419 | 420 | def front(self): 421 | return Vector(3, data=[0.0, 0.0, -1.0]) 422 | 423 | def back(self): 424 | return Vector(3, data=[ 0.0, 0.0, 1.0]) 425 | 426 | def up(self): 427 | return Vector(3, data=[ 0.0, 1.0, 0.0]) 428 | 429 | def down(self): 430 | return Vector(3, data=[0.0, -1.0, 0.0]) 431 | -------------------------------------------------------------------------------- /launcher.py: -------------------------------------------------------------------------------- 1 | import six.moves as sm 2 | from gem import matrix 3 | from gem import vector 4 | 5 | # Some vector operations examples 6 | vectorA = vector.Vector(3, data=[1, 2, 3]) 7 | vectorB = vector.Vector(3, data=[4, 5, 6]) 8 | vectorC = vectorA + vectorB 9 | print("Vector C output:" , vectorC.vector) 10 | 11 | vectorD = vectorA * 2 12 | print("Vector A * 2:", vectorD.vector) 13 | 14 | print("Vector A magnitude:", vectorA.magnitude()) 15 | 16 | # Some matrix operations examples 17 | matrixA = matrix.Matrix(4, data=[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) 18 | matrixB = matrix.Matrix(4, data=[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]) 19 | matrixC = matrixA * matrixB 20 | 21 | print("Matrix C output:") 22 | for i in sm.range(matrixC.size): 23 | print(matrixC.matrix[i]) 24 | print("End of Matrix C output") -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst 3 | 4 | #[bdist_wheel] 5 | #universal = 1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | setup( 4 | name = 'gem', 5 | packages =['gem'], 6 | version = 'v0.1.12', 7 | description = 'Math library for game programming in python. ', 8 | author = 'Alex Marinescu', 9 | author_email = 'ale632007@gmail.com', 10 | license='BSD 2-Clause', 11 | url = 'https://github.com/explosiveduck/pyGameMath', 12 | download_url = 'https://github.com/explosiveduck/pyGameMath/releases', 13 | install_requires=['six'], 14 | keywords = ['math', 'game', 'library'], 15 | classifiers = [ 16 | 'Development Status :: 4 - Beta', 17 | 'Intended Audience :: Developers', 18 | 'Operating System :: MacOS :: MacOS X', 19 | 'Operating System :: Microsoft :: Windows', 20 | 'Operating System :: Unix', 21 | 'Topic :: Scientific/Engineering :: Mathematics', 22 | 'Topic :: Software Development :: Libraries', 23 | 'License :: OSI Approved :: BSD License', 24 | 25 | 'Programming Language :: Python', 26 | 'Programming Language :: Python :: 2', 27 | 'Programming Language :: Python :: 2.7', 28 | 'Programming Language :: Python :: 3', 29 | 'Programming Language :: Python :: 3.2', 30 | 'Programming Language :: Python :: 3.3', 31 | 'Programming Language :: Python :: 3.4', 32 | 'Programming Language :: Python :: 3.5', 33 | 'Programming Language :: Python :: Implementation', 34 | ], 35 | ) 36 | --------------------------------------------------------------------------------