ponycube.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. # Rotate a cube with a quaternion
  2. # Demo program
  3. # Pat Hickey, 27 Dec 10
  4. # This code is in the public domain.
  5. import pygame
  6. import pygame.draw
  7. import pygame.time
  8. from math import sin, cos, acos
  9. from euclid import *
  10. class Screen (object):
  11. def __init__(self,x=320,y=280,scale=1):
  12. self.i = pygame.display.set_mode((x,y))
  13. self.originx = self.i.get_width() / 2
  14. self.originy = self.i.get_height() / 2
  15. self.scale = scale
  16. def project(self,v):
  17. assert isinstance(v,Vector3)
  18. x = v.x * self.scale + self.originx
  19. y = v.y * self.scale + self.originy
  20. return (x,y)
  21. def depth(self,v):
  22. assert isinstance(v,Vector3)
  23. return v.z
  24. class PrespectiveScreen(Screen):
  25. # the xy projection and depth functions are really an orthonormal space
  26. # but here i just approximated it with decimals to keep it quick n dirty
  27. def project(self,v):
  28. assert isinstance(v,Vector3)
  29. x = ((v.x*0.957) + (v.z*0.287)) * self.scale + self.originx
  30. y = ((v.y*0.957) + (v.z*0.287)) * self.scale + self.originy
  31. return (x,y)
  32. def depth(self,v):
  33. assert isinstance(v,Vector3)
  34. z = (v.z*0.9205) - (v.x*0.276) - (v.y*0.276)
  35. return z
  36. class Side (object):
  37. def __init__(self,a,b,c,d,color=(50,0,0)):
  38. assert isinstance(a,Vector3)
  39. assert isinstance(b,Vector3)
  40. assert isinstance(c,Vector3)
  41. assert isinstance(d,Vector3)
  42. self.a = a
  43. self.b = b
  44. self.c = c
  45. self.d = d
  46. self.color = color
  47. def centroid(self):
  48. return ( self.a + self.b + self.c + self.d ) / 4
  49. def draw(self,screen):
  50. assert isinstance(screen,Screen)
  51. s = [ screen.project(self.a)
  52. , screen.project(self.b)
  53. , screen.project(self.c)
  54. , screen.project(self.d)
  55. ]
  56. pygame.draw.polygon(screen.i,self.color,s)
  57. def erase(self,screen,clear_color = (0,0,0)):
  58. c = self.color
  59. self.color = clear_color
  60. self.draw(screen)
  61. self.color = c
  62. class Edge (object):
  63. def __init__(self,a,b,color=(0,0,255)):
  64. assert isinstance(a,Vector3)
  65. assert isinstance(b,Vector3)
  66. self.a = a
  67. self.b = b
  68. self.color = color
  69. def centroid(self):
  70. return (self.a + self.b) / 2
  71. def draw(self,screen):
  72. assert isinstance(screen,Screen)
  73. aa = screen.project(self.a)
  74. bb = screen.project(self.b)
  75. pygame.draw.line(screen.i, self.color, aa,bb)
  76. def erase(self,screen,clear_color = (0,0,0)):
  77. c = self.color
  78. self.color = clear_color
  79. self.draw(screen)
  80. self.color = c
  81. class Cube (object):
  82. def __init__(self,a=10,b=10,c=10):
  83. self.a = a
  84. self.b = b
  85. self.c = c
  86. self.pts = [ Vector3(-a,b,c) , Vector3(a,b,c)
  87. , Vector3(a,-b,c) , Vector3(-a,-b,c)
  88. , Vector3(-a,b,-c) , Vector3(a,b,-c)
  89. , Vector3(a,-b,-c) , Vector3(-a,-b,-c) ]
  90. def origin(self):
  91. """ reset self.pts to the origin, so we can give them a new rotation """
  92. a = self.a; b = self.b; c = self.c
  93. self.pts = [ Vector3(-a,b,c) , Vector3(a,b,c)
  94. , Vector3(a,-b,c) , Vector3(-a,-b,c)
  95. , Vector3(-a,b,-c) , Vector3(a,b,-c)
  96. , Vector3(a,-b,-c) , Vector3(-a,-b,-c) ]
  97. def sides(self):
  98. """ each side is a Side object of a certain color """
  99. # leftright = (80,80,150) # color
  100. # topbot = (30,30,150)
  101. # frontback = (0,0,150)
  102. one = (255, 0, 0)
  103. two = (0, 255, 0)
  104. three = (0, 0, 255)
  105. four = (255, 255, 0)
  106. five = (0, 255, 255)
  107. six = (255, 0, 255)
  108. a, b, c, d, e, f, g, h = self.pts
  109. sides = [ Side( a, b, c, d, one) # front
  110. , Side( e, f, g, h, two) # back
  111. , Side( a, e, f, b, three) # bottom
  112. , Side( b, f, g, c, four) # right
  113. , Side( c, g, h, d, five) # top
  114. , Side( d, h, e, a, six) # left
  115. ]
  116. return sides
  117. def edges(self):
  118. """ each edge is drawn as well """
  119. ec = (0,0,255) # color
  120. a, b, c, d, e, f, g, h = self.pts
  121. edges = [ Edge(a,b,ec), Edge(b,c,ec), Edge(c,d,ec), Edge(d,a,ec)
  122. , Edge(e,f,ec), Edge(f,g,ec), Edge(g,h,ec), Edge(h,e,ec)
  123. , Edge(a,e,ec), Edge(b,f,ec), Edge(c,g,ec), Edge(d,h,ec)
  124. ]
  125. return edges
  126. def erase(self,screen):
  127. """ erase object at present rotation (last one drawn to screen) """
  128. assert isinstance(screen,Screen)
  129. sides = self.sides()
  130. edges = self.edges()
  131. erasables = sides + edges
  132. [ s.erase(screen) for s in erasables]
  133. def draw(self,screen,q=Quaternion(1,0,0,0)):
  134. """ draw object at given rotation """
  135. assert isinstance(screen,Screen)
  136. self.origin()
  137. self.rotate(q)
  138. sides = self.sides()
  139. edges = self.edges()
  140. drawables = sides + edges
  141. drawables.sort(key=lambda s: screen.depth(s.centroid()))
  142. [ s.draw(screen) for s in drawables ]
  143. def rotate(self,q):
  144. assert isinstance(q,Quaternion)
  145. R = q.get_matrix()
  146. self.pts = [R*p for p in self.pts]
  147. if __name__ == "__main__":
  148. pygame.init()
  149. screen = Screen(480,400,scale=1.5)
  150. cube = Cube(40,30,60)
  151. q = Quaternion(1,0,0,0)
  152. incr = Quaternion(0.96,0.01,0.01,0).normalized()
  153. while 1:
  154. q = q * incr
  155. cube.draw(screen,q)
  156. event = pygame.event.poll()
  157. if event.type == pygame.QUIT \
  158. or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
  159. break
  160. pygame.display.flip()
  161. pygame.time.delay(50)
  162. cube.erase(screen)