2015-09-28 08:41:18 -05:00

523 lines
14 KiB
Python

#
# Code under the MIT license by Alexander Pruss
#
import mcpi.minecraft as minecraft
import mcpi.block as block
from mcpi.block import *
from mcpi.entity import *
from math import *
from numbers import Number,Integral
class V3(tuple):
def __new__(cls,*args):
if len(args) == 1:
return tuple.__new__(cls,tuple(*args))
else:
return tuple.__new__(cls,args)
def dot(self,other):
return self[0]*other[0]+self[1]*other[1]+self[2]*other[2]
@property
def x(self):
return self[0]
@property
def y(self):
return self[1]
@property
def z(self):
return self[2]
def __add__(self,other):
other = tuple(other)
return V3(self[0]+other[0],self[1]+other[1],self[2]+other[2])
def __radd__(self,other):
other = tuple(other)
return V3(self[0]+other[0],self[1]+other[1],self[2]+other[2])
def __sub__(self,other):
other = tuple(other)
return V3(self[0]-other[0],self[1]-other[1],self[2]-other[2])
def __rsub__(self,other):
other = tuple(other)
return V3(other[0]-self[0],other[1]-self[1],other[2]-self[2])
def __neg__(self):
return V3(-self[0],-self[1],-self[2])
def __pos__(self):
return self
def len2(self):
return self[0]*self[0]+self[1]*self[1]+self[2]*self[2]
def __abs__(self):
return sqrt(self.len2())
def __div__(self,other):
if isinstance(other,Number):
y = float(other)
return V3(self[0]/y,self[1]/y,self[2]/y)
else:
return NotImplemented
def __mul__(self,other):
if isinstance(other,Number):
return V3(self[0]*other,self[1]*other,self[2]*other)
else:
other = tuple(other)
# cross product
return V3(self[1]*other[2]-self[2]*other[1],self[2]*other[0]-self[0]*other[2],self[0]*other[1]-self[1]*other[0])
def __rmul__(self,other):
return self.__mul__(other)
def __repr__(self):
return "V3"+repr(tuple(self))
def ifloor(self):
return V3(int(floor(self[0])),int(floor(self[1])),int(floor(self[2])))
def iceil(self):
return V3(int(ceil(self[0])),int(ceil(self[1])),int(ceil(self[2])))
TO_RADIANS = pi / 180.
TO_DEGREES = 180. / pi
ICOS = [1,0,-1,0]
ISIN = [0,1,0,-1]
def makeMatrix(compass,vertical,roll):
m0 = matrixMultiply(yawMatrix(compass), pitchMatrix(vertical))
return matrixMultiply(m0, rollMatrix(roll))
def applyMatrix(m,v):
if m is None:
return v
else:
return V3(m[i][0]*v[0]+m[i][1]*v[1]+m[i][2]*v[2] for i in range(3))
def matrixDistanceSquared(m1,m2):
d2 = 0.
for i in range(3):
for j in range(3):
d2 += (m1[i][j]-m2[i][j])**2
return d2
def iatan2(y,x):
"""One coordinate must be zero"""
if x == 0:
return 90 if y > 0 else -90
else:
return 0 if x > 0 else 180
def icos(angleDegrees):
"""Calculate a cosine of an angle that must be a multiple of 90 degrees"""
return ICOS[(angleDegrees % 360) // 90]
def isin(angleDegrees):
"""Calculate a sine of an angle that must be a multiple of 90 degrees"""
return ISIN[(angleDegrees % 360) // 90]
def matrixMultiply(a,b):
c = [[0,0,0],[0,0,0],[0,0,0]]
for i in range(3):
for j in range(3):
c[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j] + a[i][2]*b[2][j]
return c
def yawMatrix(angleDegrees):
if isinstance(angleDegrees, Integral) and angleDegrees % 90 == 0:
return [[icos(angleDegrees), 0, -isin(angleDegrees)],
[0, 1, 0],
[isin(angleDegrees), 0, icos(angleDegrees)]]
else:
theta = angleDegrees * TO_RADIANS
return [[cos(theta), 0., -sin(theta)],
[0., 1., 0.],
[sin(theta), 0., cos(theta)]]
def rollMatrix(angleDegrees):
if isinstance(angleDegrees, Integral) and angleDegrees % 90 == 0:
return [[icos(angleDegrees), -isin(angleDegrees), 0],
[isin(angleDegrees), icos(angleDegrees),0],
[0, 0, 1]]
else:
theta = angleDegrees * TO_RADIANS
return [[cos(theta), -sin(theta), 0.],
[sin(theta), cos(theta),0.],
[0., 0., 1.]]
def pitchMatrix(angleDegrees):
if isinstance(angleDegrees, Integral) and angleDegrees % 90 == 0:
return [[1, 0, 0],
[0, icos(angleDegrees),isin(angleDegrees)],
[0, -isin(angleDegrees),icos(angleDegrees)]]
else:
theta = angleDegrees * TO_RADIANS
return [[1., 0., 0.],
[0., cos(theta),sin(theta)],
[0., -sin(theta),cos(theta)]]
def get2DTriangle(a,b,c):
"""get the points of the 2D triangle"""
minX = {}
maxX = {}
for line in (traverse2D(a,b), traverse2D(b,c), traverse2D(a,c)):
for p in line:
minX0 = minX.get(p[1])
if minX0 == None:
minX[p[1]] = p[0]
maxX[p[1]] = p[0]
yield(p)
elif p[0] < minX0:
for x in xrange(p[0],minX0):
yield(x,p[1])
minX[p[1]] = p[0]
else:
maxX0 = maxX[p[1]]
if maxX0 < p[0]:
for x in range(maxX0,p[0]):
yield(x,p[1])
maxX[p[1]] = p[0]
def getFace(vertices):
if len(vertices) < 1:
raise StopIteration
if len(vertices) <= 2:
for p in traverse(V3(vertices[0]), V3(vertices[1])):
yield p
v0 = V3(vertices[0])
for i in range(2,len(vertices)):
for p in traverse(V3(vertices[i-1]), V3(vertices[i])):
for q in traverse(p, v0):
yield q
def getTriangle(p1, p2, p3):
"""
Note that this will return many duplicate poitns
"""
p1,p2,p3 = V3(p1),V3(p2),V3(p3)
for u in traverse(p2,p3):
for w in traverse(p1,u):
yield w
def frac(x):
return x - floor(x)
def traverse2D(a,b):
"""
equation of line: a + t(b-a), t from 0 to 1
Based on Amantides and Woo's ray traversal algorithm with some help from
http://stackoverflow.com/questions/12367071/how-do-i-initialize-the-t-variables-in-a-fast-voxel-traversal-algorithm-for-ray
"""
inf = float("inf")
if b[0] == a[0]:
if b[1] == a[1]:
yield (int(floor(a[0])),int(floor(a[1])))
return
tMaxX = inf
tDeltaX = 0
else:
tDeltaX = 1./abs(b[0]-a[0])
tMaxX = tDeltaX * (1. - frac(a[0]))
if b[1] == a[1]:
tMaxY = inf
tDeltaY = 0
else:
tDeltaY = 1./abs(b[1]-a[1])
tMaxY = tDeltaY * (1. - frac(a[1]))
endX = int(floor(b[0]))
endY = int(floor(b[1]))
x = int(floor(a[0]))
y = int(floor(a[1]))
if x <= endX:
stepX = 1
else:
stepX = -1
if y <= endY:
stepY = 1
else:
stepY = -1
yield (x,y)
if x == endX:
if y == endY:
return
tMaxX = inf
if y == endY:
tMaxY = inf
while True:
if tMaxX < tMaxY:
x += stepX
yield (x,y)
if x == endX:
tMaxX = inf
else:
tMaxX += tDeltaX
else:
y += stepY
yield (x,y)
if y == endY:
tMaxY = inf
else:
tMaxY += tDeltaY
if tMaxX == inf and tMaxY == inf:
return
def traverse(a,b):
"""
equation of line: a + t(b-a), t from 0 to 1
Based on Amantides and Woo's ray traversal algorithm with some help from
http://stackoverflow.com/questions/12367071/how-do-i-initialize-the-t-variables-in-a-fast-voxel-traversal-algorithm-for-ray
"""
inf = float("inf")
if b.x == a.x:
if b.y == a.y and b.z == a.z:
yield a.ifloor()
return
tMaxX = inf
tDeltaX = 0
else:
tDeltaX = 1./abs(b.x-a.x)
tMaxX = tDeltaX * (1. - frac(a.x))
if b.y == a.y:
tMaxY = inf
tDeltaY = 0
else:
tDeltaY = 1./abs(b.y-a.y)
tMaxY = tDeltaY * (1. - frac(a.y))
if b.z == a.z:
tMaxZ = inf
tDeltaZ = 0
else:
tDeltaZ = 1./abs(b.z-a.z)
tMaxZ = tDeltaZ * (1. - frac(a.z))
end = b.ifloor()
x = int(floor(a.x))
y = int(floor(a.y))
z = int(floor(a.z))
if x <= end.x:
stepX = 1
else:
stepX = -1
if y <= end.y:
stepY = 1
else:
stepY = -1
if z <= end.z:
stepZ = 1
else:
stepZ = -1
yield V3(x,y,z)
if x == end.x:
if y == end.y and z == end.z:
return
tMaxX = inf
if y == end.y:
tMaxY = inf
if z == end.z:
tMaxZ = inf
while True:
if tMaxX < tMaxY:
if tMaxX < tMaxZ:
x += stepX
yield V3(x,y,z)
if x == end.x:
tMaxX = inf
else:
tMaxX += tDeltaX
else:
z += stepZ
yield V3(x,y,z)
if z == end.z:
tMaxZ = inf
else:
tMaxZ += tDeltaZ
else:
if tMaxY < tMaxZ:
y += stepY
yield V3(x,y,z)
if y == end.y:
tMaxY = inf
else:
tMaxY += tDeltaY
else:
z += stepZ
yield V3(x,y,z)
if z == end.z:
tMaxZ = inf
else:
tMaxZ += tDeltaZ
if tMaxX == inf and tMaxY == inf and tMaxZ == inf:
return
# Brasenham's algorithm
def getLine(x1, y1, z1, x2, y2, z2):
line = []
x1 = int(floor(x1))
y1 = int(floor(y1))
z1 = int(floor(z1))
x2 = int(floor(x2))
y2 = int(floor(y2))
z2 = int(floor(z2))
point = [x1,y1,z1]
dx = x2 - x1
dy = y2 - y1
dz = z2 - z1
x_inc = -1 if dx < 0 else 1
l = abs(dx)
y_inc = -1 if dy < 0 else 1
m = abs(dy)
z_inc = -1 if dz < 0 else 1
n = abs(dz)
dx2 = l << 1
dy2 = m << 1
dz2 = n << 1
if l >= m and l >= n:
err_1 = dy2 - l
err_2 = dz2 - l
for i in range(0,l-1):
line.append(V3(point[0],point[1],point[2]))
if err_1 > 0:
point[1] += y_inc
err_1 -= dx2
if err_2 > 0:
point[2] += z_inc
err_2 -= dx2
err_1 += dy2
err_2 += dz2
point[0] += x_inc
elif m >= l and m >= n:
err_1 = dx2 - m;
err_2 = dz2 - m;
for i in range(0,m-1):
line.append(V3(point[0],point[1],point[2]))
if err_1 > 0:
point[0] += x_inc
err_1 -= dy2
if err_2 > 0:
point[2] += z_inc
err_2 -= dy2
err_1 += dx2
err_2 += dz2
point[1] += y_inc
else:
err_1 = dy2 - n;
err_2 = dx2 - n;
for i in range(0, n-1):
line.append(V3(point[0],point[1],point[2]))
if err_1 > 0:
point[1] += y_inc
err_1 -= dz2
if err_2 > 0:
point[0] += x_inc
err_2 -= dz2
err_1 += dy2
err_2 += dx2
point[2] += z_inc
line.append(V3(point[0],point[1],point[2]))
if point[0] != x2 or point[1] != y2 or point[2] != z2:
line.append(V3(x2,y2,z2))
return line
class Drawing:
TO_RADIANS = pi / 180.
TO_DEGREES = 180. / pi
def __init__(self,mc=None):
if mc:
self.mc = mc
else:
self.mc = minecraft.Minecraft()
self.width = 1
self.nib = [(0,0,0)]
def penwidth(self,w):
self.width = int(w)
if self.width == 0:
self.nib = []
elif self.width == 1:
self.nib = [(0,0,0)]
elif self.width == 2:
self.nib = []
for x in range(-1,1):
for y in range(0,2):
for z in range(-1,1):
self.nib.append((x,y,z))
else:
self.nib = []
r2 = self.width * self.width / 4.
for x in range(-self.width//2 - 1,self.width//2 + 1):
for y in range(-self.width//2 - 1, self.width//2 + 1):
for z in range(-self.width//2 -1, self.width//2 + 1):
if x*x + y*y + z*z <= r2:
self.nib.append((x,y,z))
def point(self, x, y, z, block):
for p in self.nib:
self.mc.setBlock(x+p[0],y+p[1],z+p[2],block)
def face(self, vertices, block):
self.drawPoints(getFace(vertices), block)
def line(self, x1,y1,z1, x2,y2,z2, block):
self.drawPoints(getLine(x1,y1,z1, x2,y2,z2), block)
def drawPoints(self, points, block):
if self.width == 1:
done = set()
for p in points:
if p not in done:
self.mc.setBlock(p, block)
done.add(p)
else:
done = set()
for p in points:
for point in self.nib:
x0 = p[0]+point[0]
y0 = p[1]+point[1]
z0 = p[2]+point[2]
if (x0,y0,z0) not in self.done:
self.mc.setBlock(x0,y0,z0,block)
done.add((x0,y0,z0))
if __name__ == "__main__":
d = Drawing()
pos = d.mc.player.getPos()
d.face([(pos.x,pos.y,pos.z),(pos.x+20,pos.y+20,pos.z),(pos.x+20,pos.y+20,pos.z+20),
(pos.x,pos.y,pos.z+20)], GLASS)
n = 20
for t in range(0,n):
(x1,z1) = (100*cos(t*2*pi/n),80*sin(t*2*pi/n))
for p in traverse(V3(pos.x,pos.y-1,pos.z),V3(pos.x+x1,pos.y-1,pos.z+z1)):
d.mc.setBlock(p,OBSIDIAN)
n = 40
vertices = []
for t in range(0,n):
(x1,z1) = (100*cos(t*2*pi/n),80*sin(t*2*pi/n))
vertices.append((pos.x+x1,pos.y,pos.z+z1))
d.face(vertices, STAINED_GLASS_BLUE)