我想通过拖动鼠标使3d对象旋转
我找到的最接近的示例是https://sites.google.com/site/3dprogramminginpython/
中的这个示例# matrix.py
import math
class Vector3D:
def __init__(self, x=0.0, y=0.0, z=0.0):
self.x = x
self.y = y
self.z = z
def __str__(self):
return '[%f, %f, %f]' % (self.x, self.y, self.z)
def magnitude(self):
return math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z)
def __add__(self, v):
if isinstance(v, Vector3D):
return Vector3D(self.x+v.x, self.y+v.y, self.z+v.z)
else:
raise Exception('*** Vector3D: error, add not with a vector! ***')
def __sub__(self, v):
if isinstance(v, Vector3D):
return Vector3D(self.x-v.x, self.y-v.y, self.z-v.z)
else:
raise Exception('*** Vector3D: error, sub not with a vector! ***')
def __neg__(self):
return Vector3D(-self.x, -self.y, -self.z)
def __mul__(self, val):
if isinstance(val, int) or isinstance(val, float):
return Vector3D(self.x*val, self.y*val, self.z*val)
elif isinstance(val, Vector3D):
return Vector3D(self.x*val.x, self.y*val.y, self.z*val.z)
else:
raise Exception('*** Vector3D: error, multiplication with not vector, int or float! ***')
def __div__(self, val):
if isinstance(val, int) or isinstance(val, float):
if (val):
return Vector3D(self.x/val, self.y/val, self.z/val)
else:
raise Exception('*** Vector: error, divison with zero! ***')
elif isinstance(val, Vector3D):
if (val.x and val.y and val.z):
return Vector3D(self.x/val.x, self.y/val.y, self.z/val.z)
else:
raise Exception('*** Vector: error, divison with zero vector coord! ***')
else:
raise Exception('*** Vector: error, divison with not vector, int or float! ***')
# def __rmul__(self, other): #__mul__ is called in case of vector*int but in case of int*vector __rmul__ is called
def normalize(self):
mag = self.magnitude()
if (mag > 0.0):
# Vector3D.__mul__(self, 1/mag)
self.x /= mag
self.y /= mag
self.z /= mag
else:
raise Exception('*** Vector: error, normalizing zero vector! ***')
def dot(self, v): #dot product
if isinstance(v, Vector3D):
return self.x*v.x+self.y*v.y+self.z*v.z
else:
raise Exception('*** Vector: error, dot product not with a vector! ***')
def cross(self, v): #cross product
if isinstance(v, Vector3D):
return Vector3D(self.y*v.z-self.z*v.y, self.z*v.x-self.x*v.z, self.x*v.y-self.y*v.x)
else:
raise Exception('*** Vector: error, cross product not with a vector! ***')
#The layout of the matrix (row- or column-major) matters only when the user reads from or writes to the matrix (indexing). For example in the multiplication function we know that the first components of the Matrix-vectors need to be multiplied by the vector. The memory-layout is not important
class Matrix:
''' Column-major order '''
def __init__(self, rows, cols, createidentity=True):# (2,2) creates a 2*2 Matrix
if rows < 2 or cols < 2:
raise Exception('*** Matrix: error, getitem((row, col)), row, col problem! ***')
self.rows = rows
self.cols = cols
self.m = [[0.0]*rows for x in xrange(cols)]
#If quadratic matrix then create identity one
if self.isQuadratic() and createidentity:
for i in range(self.rows):
self.m[i][i] = 1.0
def __str__(self):
s = ''
for row in self.m:
s += '%s\n' % row
return s
def isQuadratic(self):
return self.rows == self.cols
def copy(self):
r = Matrix(self.rows, self.cols, False)
for i in range(self.rows):
for j in range(self.cols):
r.m[i][j] = self.m[i][j]
return r
def __getitem__(self, (row, col)):
''' The value at (row, col)
For example, to get the element at 1,3 say
m[(1,2)]'''
if self.rows > row and self.cols > col:
return self.m[row][col]
else:
raise Exception('*** Matrix: error, getitem((row, col)), row, col problem! ***')
def __setitem__(self, (row, col), val):
''' Sets the value at (row, col)
For example, to set the value of element at 1,3 say
m[(1,2)] = 3 '''
if self.rows > row and self.cols > col:
self.m[row][col] = val
else:
raise Exception('*** Matrix: error, setitem((row, col), val), row, col problem! ***')
def rowsNum(self):
return len(self.m)
def colsNum(self):
return len(self.m[0])
def getRow(self, i):
if i < self.rows:
return self.m[i]
else:
raise Exception('*** Matrix: error, row(i), i > number of rows! ***')
def getCol(self, j):
if j < self.cols:
return [row[j] for row in self.m] #(returns array of the vector)
else:
raise Exception('*** Matrix: error, col(j), j > columns! ***')
# def setColumn(self, j, v):
# if j < self.cols:
# self.m[0][j] = v.x
# self.m[1][j] = v.y
# self.m[2][j] = v.z
# else:
# raise Exception('*** Matrix: error, setColumn(j, v), j > columns! ***')
def __add__(self, right):
if self.rows == right.rows and self.cols == right.cols:
r = Matrix(self.rows, self.cols, False)
for i in range(self.rows):
for j in range(self.cols):
r.m[i][j] = self.m[i][j]+right.m[i][j]
return r
else:
raise Exception('*** Matrix: error, add() matrices are not of the same type ***')
def __sub__(self, right):
if self.rows == right.rows and self.cols == right.cols:
r = Matrix(self.rows, self.cols, False)
for i in range(self.rows):
for j in range(self.cols):
r.m[i][j] = self.m[i][j]-right.m[i][j]
return r
else:
raise Exception('*** Matrix: error, sub() matrices are not of the same type ***')
def __mul__(self, right):
if isinstance(right, Matrix):
if self.cols == right.rows:
r = Matrix(self.rows, right.cols, False)
for i in range(self.rows):
for j in range(right.cols):
for k in range(self.cols):
r.m[i][j] += self.m[i][k]*right.m[k][j]
return r
else:
raise Exception('*** Matrix: error, matrix multiplication with incompatible matrix! ***')
elif isinstance(right, Vector3D): #Translation: the last column of the matrix. Remains unchanged due to the the fourth coord of the vector (1).
# if self.cols == 4:
r = Vector3D()
addx = addy = addz = 0.0
if self.rows == self.cols == 4:
addx = self.m[0][3]
addy = self.m[1][3]
addz = self.m[2][3]
r.x = self.m[0][0]*right.x+self.m[0][1]*right.y+self.m[0][2]*right.z+addx
r.y = self.m[1][0]*right.x+self.m[1][1]*right.y+self.m[1][2]*right.z+addy
r.z = self.m[2][0]*right.x+self.m[2][1]*right.y+self.m[2][2]*right.z+addz
#In 3D game programming we use homogenous coordinates instead of cartesian ones in case of Vectors in order to be able to use them with a 4*4 Matrix. The 4th coord (w) is not included in the Vector-class but gets computed on the fly
if self.rows == self.cols == 4:
w = self.m[3][0]*right.x+self.m[3][1]*right.y+self.m[3][2]*right.z+self.m[3][3]
if (w != 1 and w != 0):
r.x = r.x/w;
r.y = r.y/w;
r.z = r.z/w;
return r
# else:
# raise Exception('*** Matrix: error, matrix multiplication with incompatible vector! ***')
elif isinstance(right, int) or isinstance(right, float):
r = Matrix(self.rows, self.cols, False)
for i in range(self.rows):
for j in range(self.cols):
r.m[i][j] = self.m[i][j]*right
return r
else:
raise Exception('*** Matrix: error, matrix multiply with not matrix, vector or int or float! ***')
def __div__(self, right):
if isinstance(right, int) or isinstance(right, float):
r = Matrix(self.rows, self.cols, False)
for i in range(self.rows):
for j in range(self.cols):
r.m[i][j] = self.m[i][j]/right
return r
else:
raise Exception('*** Matrix: error, matrix division with not int or float! ***')
def transpose(self):
t = Matrix(self.cols, self.rows, False)
for j in range(self.cols):
for i in range(self.rows):
t.m[j][i] = self.m[i][j]
return t
#For Quadratic Matrices:
#isSymetric(): A=Atransposed
#isNormal(): Atransposed*A = A*Atransposed
def det(self):#Only for quadratic matrices
if not self.isQuadratic():
raise Exception('*** Matrix: error, determinant of non-quadratic matrix! ***')
if self.rows == 2:
return self.m[0][0]*self.m[1][1]-self.m[0][1]*self.m[1][0]
return self.expandByMinorsOnRow(0)
def expandByMinorsOnRow(self, row):#used by det()
assert(row < self.rows)
d = 0
for col in xrange(self.cols):
d += (-1)**(row+col)*self.m[row][col]*self.minor(row, col).det()
return d
def expandByMinorsOnCol(self, col):#used by det()
assert(col < self.cols)
d = 0
for row in xrange(self.rows):
d += (-1)**(row+col)*self.m[row][col]*self.minor(row, col).det()
return d
def minor(self, i, j): #used by det()
''' A minor of the matrix. Return the minor given by
striking out row i and column j of the matrix '''
if i < 0 or i >= self.rows:
raise Exception('*** Matrix: error, Determinant-row value is out of range! ***')
if j < 0 or j >= self.cols:
raise Exception('*** Matrix: error, Determinant-col value is out of range! ***')
mat = Matrix(self.rows-1, self.cols-1)
#Loop through the matrix, skipping over the row and column specified
#by i and j
minor_row = minor_col = 0
for self_row in xrange(self.rows):
if not self_row == i: #skip row i
for self_col in xrange(self.cols):
if not self_col == j: #Skip column j
mat.m[minor_row][minor_col] = self.m[self_row][self_col]
minor_col += 1
minor_col = 0
minor_row += 1
return mat
def invert(self):
if not self.isQuadratic():
raise Exception('*** Matrix: error, determinant of non-quadratic matrix! ***')
else:
N = self.cols
mat = Matrix(N, N)
mo = self.copy()
for column in range(N):
# Swap row in case our pivot point is not working
if (mo.m[column][column] == 0):
big = column
for row in range(N):
if (math.fabs(mo.m[row][column]) > math.fabs(mo.m[big][column])):
big = row
# Print this is a singular matrix, return identity ?
if (big == column):
print("Singular matrix\n") #To stderr
# Swap rows
else:
for j in range(N):
mo.m[column][j], mo.m[big][j] = mo.m[big][j], mo.m[column][j]
mat.m[column][j], mat.m[big][j] = mat.m[big][j], mat.m[column][j]
#Set each row in the column to 0
for row in range(N):
if (row != column):
coeff = mo.m[row][column]/mo.m[column][column]
if (coeff != 0):
for j in range(N):
mo.m[row][j] -= coeff*mo.m[column][j]
mat.m[row][j] -= coeff*mat.m[column][j]
#Set the element to 0 for safety
mo.m[row][column] = 0
# Set each element of the diagonal to 1
for row in range(N):
for column in range(N):
mat.m[row][column] /= mo.m[row][row]
return mat
if __name__ == "__main__":
m1 = Matrix(3, 3)
m1[(0,0)] = 1
m1[(0,1)] = 6
m1[(0,2)] = 8
m1[(1,0)] = 2
m1[(1,1)] = 5
m1[(1,2)] = 7
m1[(2,0)] = 3
m1[(2,1)] = 4
m1[(2,2)] = 9
m2 = Matrix(3, 3)
m2[(0,0)] = 5
m2[(0,1)] = 8
m2[(0,2)] = 1
m2[(1,0)] = 6
m2[(1,1)] = 1
m2[(1,2)] = 4
m2[(2,0)] = 3
m2[(2,1)] = 2
m2[(2,2)] = 7
m = m1*m2
print str(m)
# tut1.py
import math
from Tkinter import *
import matrix
class Simulation:
''' Coordiante-system: Right-handed, Matrices are column-major ones '''
WIDTH = 640.0
HEIGHT = 640.0
RATE = 3
SPEED = 2
def __init__(self):
self.root = Tk()
self.root.resizable(False, False)
self.root.title('3D')
left = (self.root.winfo_screenwidth() - Simulation.WIDTH) / 2
top = (self.root.winfo_screenheight() - Simulation.HEIGHT) / 2
self.root.geometry('%dx%d+%d+%d' % (Simulation.WIDTH, Simulation.HEIGHT, left, top))
self.graph = Canvas(self.root, width=Simulation.WIDTH, height=Simulation.HEIGHT, background='black')
self.graph.pack()
#The vectors of the Coordinate System (CS)
self.cs = [
matrix.Vector3D(0.0, 0.0, 0.0), #Origin
matrix.Vector3D(0.5, 0.0, 0.0), #X
matrix.Vector3D(0.0, 0.5, 0.0), #Y
matrix.Vector3D(0.0 ,0.0, 0.5), #Z
]
#Let these be in World-coordinates (worldview-matrix already applied)
#In right-handed, counter-clockwise order
self.cube = [
matrix.Vector3D(-0.5,0.5,-0.5),
matrix.Vector3D(0.5,0.5,-0.5),
matrix.Vector3D(0.5,-0.5,-0.5),
matrix.Vector3D(-0.5,-0.5,-0.5),
matrix.Vector3D(-0.5,0.5,0.5),
matrix.Vector3D(0.5,0.5,0.5),
matrix.Vector3D(0.5,-0.5,0.5),
matrix.Vector3D(-0.5,-0.5,0.5)
]
self.dotx = matrix.Vector3D(-0.5, 0.0, 0.0)
self.doty = matrix.Vector3D(0.0, -0.5, 0.0)
self.dotz = matrix.Vector3D(0.0, 0.0, -0.5)
# Define the vertices that compose each of the 6 faces. These numbers are
# indices to the vertices list defined above.
self.cubefaces = [(0,1,2,3),(1,5,6,2),(5,4,7,6),(4,0,3,7),(0,4,5,1),(3,2,6,7)]
self.ang = [0.0, 0.0, 0.0] # phi(x), theta(y), psi(z)
self.trans = [0.0, 0.0, 0.0] # translation (x, y, z) (e.g. if want to move the Camera to (0, 0, 2) then (0, 0, -2) need to be entered)
#The matrices (Scale, Shear, Rotate, Translate) apply to the View/Camera
#The Scale Matrix
self.Scale = matrix.Matrix(4, 4)
Scalex = 1.0
Scaley = 1.0
Scalez = 1.0
self.Scale[(0,0)] = Scalex
self.Scale[(1,1)] = Scaley
self.Scale[(2,2)] = Scalez
#The Shear Matrix
self.Shearxy = matrix.Matrix(4, 4)
self.Shearxy[(0,2)] = 0.0
self.Shearxy[(1,2)] = 0.0
self.Shearxz = matrix.Matrix(4, 4)
self.Shearxz[(0,1)] = 0.0
self.Shearxz[(2,1)] = 0.0
self.Shearyz = matrix.Matrix(4, 4)
self.Shearyz[(1,0)] = 0.0
self.Shearyz[(2,0)] = 0.0
self.Shear = self.Shearxy*self.Shearxz*self.Shearyz
#The Rotation Matrices
self.Rotx = matrix.Matrix(4,4)
self.Roty = matrix.Matrix(4,4)
self.Rotz = matrix.Matrix(4,4)
#The Translation Matrix (will contain xoffset, yoffset, zoffset)
self.Tr = matrix.Matrix(4, 4)
#The Projection Matrix
self.Proj = matrix.Matrix(4, 4)
#foc controls how much of the screen is viewed
fov = 90.0 #between 30 and 90 ?
zfar = 100.0
znear = 0.1
S = 1/(math.tan(math.radians(fov/2)))
#1st version (Perspective Projection)
self.Proj[(0,0)] = S
self.Proj[(1,1)] = S
self.Proj[(2,2)] = -zfar/(zfar-znear)
self.Proj[(3,2)] = -1.0
self.Proj[(2,3)] = -(zfar*znear)/(zfar-znear)
# self.Proj[(3,3)] = 0.0 #this should be zero acc. to the docs but it doesn't work
#2nd version (Simple Projection)
# self.Proj[(2,3)] = -1.0
# self.Proj[(3,3)] = 0.0
#3rd version (Perspective Projection) (Dr HS Fortuna Playstation)
# A = Simulation.WIDTH/Simulation.HEIGHT
# self.Proj[(0,0)] = S/A
# self.Proj[(1,1)] = S
# self.Proj[(2,2)] = (zfar+znear)/(zfar-znear)
# self.Proj[(3,2)] = -1.0
# self.Proj[(2,3)] = -2*(zfar*znear)/(zfar-znear)
# self.Proj[(3,3)] = 0.0
#4th version (Perspective Projection) (OpenGL)
# A = Simulation.WIDTH/Simulation.HEIGHT
# self.Proj[(0,0)] = S/A
# self.Proj[(1,1)] = S
# self.Proj[(2,2)] = (zfar+znear)/(znear-zfar)
# self.Proj[(3,2)] = -1.0
# self.Proj[(2,3)] = 2*(zfar*znear)/(znear-zfar)
# self.Proj[(3,3)] = 0.0
self.lctrl_pressed = False
self.root.bind("<B1-Motion>", self.dragcallback)
self.root.bind("<ButtonRelease-1>", self.releasecallback)
self.root.bind("<Key>", self.keycallback)
self.root.bind("<KeyRelease>", self.keyreleasecallback)
self.cnt = Simulation.RATE
self.prevmouseX = 0.0
self.prevmouseY = 0.0
self.update()
mainloop()
def toScreenCoords(self, pv):
# print str(pv)
#Projection will project to [-1; 1] so the points need to be scaled on screen
px = min(((pv.x+1)*0.5*Simulation.WIDTH), Simulation.WIDTH-1)
#Reflect the Y-coordinate because the screen it goes downwards
py = min(((1-(pv.y+1)*0.5)*Simulation.HEIGHT), Simulation.HEIGHT-1)
return matrix.Vector3D(int(px), int(py), 1)
#Screen matrix
# SC = matrix.Matrix(4, 4)
# SC[(0,0)] = Simulation.WIDTH/2
# SC[(1,1)] = -Simulation.HEIGHT/2
# SC[(0,3)] = Simulation.WIDTH/2
# SC[(1,3)] = Simulation.HEIGHT/2
# return SC*pv
def update(self):
# Main simulation loop.
self.graph.delete(ALL)
self.Rotx[(1,1)] = math.cos(math.radians(self.ang[0]))
self.Rotx[(1,2)] = -math.sin(math.radians(self.ang[0]))
self.Rotx[(2,1)] = math.sin(math.radians(self.ang[0]))
self.Rotx[(2,2)] = math.cos(math.radians(self.ang[0]))
self.Roty[(0,0)] = math.cos(math.radians(self.ang[1]))
self.Roty[(0,2)] = math.sin(math.radians(self.ang[1]))
self.Roty[(2,0)] = -math.sin(math.radians(self.ang[1]))
self.Roty[(2,2)] = math.cos(math.radians(self.ang[1]))
self.Rotz[(0,0)] = math.cos(math.radians(self.ang[2]))
self.Rotz[(0,1)] = -math.sin(math.radians(self.ang[2]))
self.Rotz[(1,0)] = math.sin(math.radians(self.ang[2]))
self.Rotz[(1,1)] = math.cos(math.radians(self.ang[2]))
#The Rotation matrix
self.Rot = self.Rotx*self.Roty*self.Rotz
self.Tr[(0,3)] = self.trans[0]
self.Tr[(1,3)] = self.trans[1]
self.Tr[(2,3)] = self.trans[2]
#The Transformation matrix
self.Tsf = self.Scale*self.Shear*self.Rot*self.Tr
inviewingvolume = False
#First draw the lines of the CS
tvs = [] #transformed vectors
for v in self.cs:
r = self.Tsf*v
ps = self.Proj*r
tvs.append(self.toScreenCoords(ps))
#if only one vertex is in the screen (x[-1,1], y[-1,1], z[-1,1]) then we draw the whole CS
if (-1.0 <= ps.x <= 1.0) and (-1.0 <= ps.y <= 1.0) and (-1.0 <= ps.z <= 1.0):
inviewingvolume = True
if inviewingvolume:
self.graph.create_line(tvs[0].x, tvs[0].y, tvs[1].x, tvs[1].y, fill='red')
self.graph.create_line(tvs[0].x, tvs[0].y, tvs[2].x, tvs[2].y, fill='green')
self.graph.create_line(tvs[0].x, tvs[0].y, tvs[3].x, tvs[3].y, fill='blue')
#End of drawing of the lines of the CS
inviewingvolume = False
#Cube
for i in range(len(self.cubefaces)):
inviewingvolume = False
poly = [] #transformed polygon
for j in range(len(self.cubefaces[0])):
v = self.cube[self.cubefaces[i][j]]
# Scale, Shear, Rotate the vertex around X axis, then around Y axis, and finally around Z axis and Translate.
r = self.Tsf*v
# Transform the point from 3D to 2D
ps = self.Proj*r
# Put the screenpoint in the list of transformed vertices
p = self.toScreenCoords(ps)
x = int(p.x)
y = int(p.y)
poly.append((x, y))
#if only one vertex is in the screen (x[-1,1], y[-1,1], z[-1,1]) then draw the whole polygon
if (-1.0 <= ps.x <= 1.0) and (-1.0 <= ps.y <= 1.0) and (-1.0 <= ps.z <= 1.0):
inviewingvolume = True
if inviewingvolume:
for k in range(len(poly)-1):
self.graph.create_line(poly[k][0], poly[k][1], poly[k+1][0], poly[k+1][1], fill='white')
self.graph.create_line(poly[len(poly)-1][0], poly[len(poly)-1][1], poly[0][0], poly[0][1], fill='white')
inviewingvolume = False
#Dotx
r = self.Tsf*self.dotx
ps = self.Proj*r
if (-1.0 <= ps.x <= 1.0) and (-1.0 <= ps.y <= 1.0) and (-1.0 <= ps.z <= 1.0):
inviewingvolume = True
if inviewingvolume:
p = self.toScreenCoords(ps)
x, y = int(p.x), int(p.y)
self.graph.create_rectangle(x, y, x+10, y+10, fill="red")
inviewingvolume = False
#Doty
r = self.Tsf*self.doty
ps = self.Proj*r
if (-1.0 <= ps.x <= 1.0) and (-1.0 <= ps.y <= 1.0) and (-1.0 <= ps.z <= 1.0):
inviewingvolume = True
if inviewingvolume:
p = self.toScreenCoords(ps)
x, y = int(p.x), int(p.y)
self.graph.create_rectangle(x, y, x+10, y+10, fill="green")
inviewingvolume = False
#Dotz
r = self.Tsf*self.dotz
ps = self.Proj*r
if (-1.0 <= ps.x <= 1.0) and (-1.0 <= ps.y <= 1.0) and (-1.0 <= ps.z <= 1.0):
inviewingvolume = True
if inviewingvolume:
p = self.toScreenCoords(ps)
x, y = int(p.x), int(p.y)
self.graph.create_rectangle(x, y, x+10, y+10, fill="blue")
def dragcallback(self, event):
# It's also possible to use the angle calculated from the mousepos-change from the center of the screen:
# dx = event.x - Simulation.WIDTH/2
# dy = event.y - Simulation.HEIGHT/2
# ang = math.degrees(math.atan2(dy, dx))
# if ang < 0.0:
# ang += 360.0
self.cnt -= 1
if self.cnt == 0:
self.cnt = Simulation.RATE
diffX = event.x-self.prevmouseX
diffY = event.y-self.prevmouseY
if not self.lctrl_pressed:
self.ang[0] += diffY*Simulation.SPEED
self.ang[1] += diffX*Simulation.SPEED
if self.ang[0] >= 360.0:
self.ang[0] -= 360.0
if self.ang[0] < 0.0:
self.ang[0] += 360.0
if self.ang[1] >= 360.0:
self.ang[1] -= 360.0
if self.ang[1] < 0.0:
self.ang[1] += 360.0
else:
self.ang[2] += diffX*Simulation.SPEED
if self.ang[2] >= 360.0:
self.ang[2] -= 360.0
if self.ang[2] < 0.0:
self.ang[2] += 360.0
self.update()
self.prevmouseX = event.x
self.prevmouseY = event.y
def releasecallback(self, event):
self.cnt = Simulation.RATE
self.prevmouseX = 0.0
self.prevmouseY = 0.0
def keycallback(self, event):
# print event.char
# print event.keycode
# print event.keysym
if event.keysym == "Control_L":
self.lctrl_pressed = True
def keyreleasecallback(self, event):
if event.keysym == "Control_L":
self.lctrl_pressed = False
if __name__ == "__main__":
Simulation()
上面的示例使用tkinter和python2,在这种情况下,我想使用python3,pygame和opengl进行进一步的开发。您可以通过运行tut1.py
进行尝试我一直在摆弄鼠标事件,但是我还是听不懂。有人可以给我建议吗?