main.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. #!/usr/bin/env python
  2. # Author: Shao Zhang and Phil Saltzman
  3. # Models: Eddie Canaan
  4. # Last Updated: 2015-03-13
  5. #
  6. # This tutorial shows how to determine what objects the mouse is pointing to
  7. # We do this using a collision ray that extends from the mouse position
  8. # and points straight into the scene, and see what it collides with. We pick
  9. # the object with the closest collision
  10. from direct.showbase.ShowBase import ShowBase
  11. from panda3d.core import CollisionTraverser, CollisionNode
  12. from panda3d.core import CollisionHandlerQueue, CollisionRay
  13. from panda3d.core import AmbientLight, DirectionalLight, LightAttrib
  14. from panda3d.core import TextNode
  15. from panda3d.core import LPoint3, LVector3, BitMask32
  16. from direct.gui.OnscreenText import OnscreenText
  17. from direct.showbase.DirectObject import DirectObject
  18. from direct.task.Task import Task
  19. import sys
  20. # First we define some constants for the colors
  21. BLACK = (0, 0, 0, 1)
  22. WHITE = (1, 1, 1, 1)
  23. HIGHLIGHT = (0, 1, 1, 1)
  24. PIECEBLACK = (.15, .15, .15, 1)
  25. # Now we define some helper functions that we will need later
  26. # This function, given a line (vector plus origin point) and a desired z value,
  27. # will give us the point on the line where the desired z value is what we want.
  28. # This is how we know where to position an object in 3D space based on a 2D mouse
  29. # position. It also assumes that we are dragging in the XY plane.
  30. #
  31. # This is derived from the mathematical of a plane, solved for a given point
  32. def PointAtZ(z, point, vec):
  33. return point + vec * ((z - point.getZ()) / vec.getZ())
  34. # A handy little function for getting the proper position for a given square1
  35. def SquarePos(i):
  36. return LPoint3((i % 8) - 3.5, int(i // 8) - 3.5, 0)
  37. # Helper function for determining whether a square should be white or black
  38. # The modulo operations (%) generate the every-other pattern of a chess-board
  39. def SquareColor(i):
  40. if (i + ((i // 8) % 2)) % 2:
  41. return BLACK
  42. else:
  43. return WHITE
  44. class ChessboardDemo(ShowBase):
  45. def __init__(self):
  46. # Initialize the ShowBase class from which we inherit, which will
  47. # create a window and set up everything we need for rendering into it.
  48. ShowBase.__init__(self)
  49. # This code puts the standard title and instruction text on screen
  50. self.title = OnscreenText(text="Panda3D: Tutorial - Mouse Picking",
  51. style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),
  52. pos=(0.8, -0.95), scale = .07)
  53. self.escapeEvent = OnscreenText(
  54. text="ESC: Quit", parent=base.a2dTopLeft,
  55. style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.1),
  56. align=TextNode.ALeft, scale = .05)
  57. self.mouse1Event = OnscreenText(
  58. text="Left-click and drag: Pick up and drag piece",
  59. parent=base.a2dTopLeft, align=TextNode.ALeft,
  60. style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.16), scale=.05)
  61. self.accept('escape', sys.exit) # Escape quits
  62. self.disableMouse() # Disble mouse camera control
  63. camera.setPosHpr(0, -12, 8, 0, -35, 0) # Set the camera
  64. self.setupLights() # Setup default lighting
  65. # Since we are using collision detection to do picking, we set it up like
  66. # any other collision detection system with a traverser and a handler
  67. self.picker = CollisionTraverser() # Make a traverser
  68. self.pq = CollisionHandlerQueue() # Make a handler
  69. # Make a collision node for our picker ray
  70. self.pickerNode = CollisionNode('mouseRay')
  71. # Attach that node to the camera since the ray will need to be positioned
  72. # relative to it
  73. self.pickerNP = camera.attachNewNode(self.pickerNode)
  74. # Everything to be picked will use bit 1. This way if we were doing other
  75. # collision we could separate it
  76. self.pickerNode.setFromCollideMask(BitMask32.bit(1))
  77. self.pickerRay = CollisionRay() # Make our ray
  78. # Add it to the collision node
  79. self.pickerNode.addSolid(self.pickerRay)
  80. # Register the ray as something that can cause collisions
  81. self.picker.addCollider(self.pickerNP, self.pq)
  82. # self.picker.showCollisions(render)
  83. # Now we create the chess board and its pieces
  84. # We will attach all of the squares to their own root. This way we can do the
  85. # collision pass just on the squares and save the time of checking the rest
  86. # of the scene
  87. self.squareRoot = render.attachNewNode("squareRoot")
  88. # For each square
  89. self.squares = [None for i in range(64)]
  90. self.pieces = [None for i in range(64)]
  91. for i in range(64):
  92. # Load, parent, color, and position the model (a single square
  93. # polygon)
  94. self.squares[i] = loader.loadModel("models/square")
  95. self.squares[i].reparentTo(self.squareRoot)
  96. self.squares[i].setPos(SquarePos(i))
  97. self.squares[i].setColor(SquareColor(i))
  98. # Set the model itself to be collideable with the ray. If this model was
  99. # any more complex than a single polygon, you should set up a collision
  100. # sphere around it instead. But for single polygons this works
  101. # fine.
  102. self.squares[i].find("**/polygon").node().setIntoCollideMask(
  103. BitMask32.bit(1))
  104. # Set a tag on the square's node so we can look up what square this is
  105. # later during the collision pass
  106. self.squares[i].find("**/polygon").node().setTag('square', str(i))
  107. # We will use this variable as a pointer to whatever piece is currently
  108. # in this square
  109. # The order of pieces on a chessboard from white's perspective. This list
  110. # contains the constructor functions for the piece classes defined
  111. # below
  112. pieceOrder = (Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook)
  113. for i in range(8, 16):
  114. # Load the white pawns
  115. self.pieces[i] = Pawn(i, WHITE)
  116. for i in range(48, 56):
  117. # load the black pawns
  118. self.pieces[i] = Pawn(i, PIECEBLACK)
  119. for i in range(8):
  120. # Load the special pieces for the front row and color them white
  121. self.pieces[i] = pieceOrder[i](i, WHITE)
  122. # Load the special pieces for the back row and color them black
  123. self.pieces[i + 56] = pieceOrder[i](i + 56, PIECEBLACK)
  124. # This will represent the index of the currently highlited square
  125. self.hiSq = False
  126. # This wil represent the index of the square where currently dragged piece
  127. # was grabbed from
  128. self.dragging = False
  129. # Start the task that handles the picking
  130. self.mouseTask = taskMgr.add(self.mouseTask, 'mouseTask')
  131. self.accept("mouse1", self.grabPiece) # left-click grabs a piece
  132. self.accept("mouse1-up", self.releasePiece) # releasing places it
  133. # This function swaps the positions of two pieces
  134. def swapPieces(self, fr, to):
  135. temp = self.pieces[fr]
  136. self.pieces[fr] = self.pieces[to]
  137. self.pieces[to] = temp
  138. if self.pieces[fr]:
  139. self.pieces[fr].square = fr
  140. self.pieces[fr].obj.setPos(SquarePos(fr))
  141. if self.pieces[to]:
  142. self.pieces[to].square = to
  143. self.pieces[to].obj.setPos(SquarePos(to))
  144. def mouseTask(self, task):
  145. # This task deals with the highlighting and dragging based on the mouse
  146. # First, clear the current highlight
  147. if self.hiSq is not False:
  148. self.squares[self.hiSq].setColor(SquareColor(self.hiSq))
  149. self.hiSq = False
  150. # Check to see if we can access the mouse. We need it to do anything
  151. # else
  152. if self.mouseWatcherNode.hasMouse():
  153. # get the mouse position
  154. mpos = self.mouseWatcherNode.getMouse()
  155. # Set the position of the ray based on the mouse position
  156. self.pickerRay.setFromLens(self.camNode, mpos.getX(), mpos.getY())
  157. # If we are dragging something, set the position of the object
  158. # to be at the appropriate point over the plane of the board
  159. if self.dragging is not False:
  160. # Gets the point described by pickerRay.getOrigin(), which is relative to
  161. # camera, relative instead to render
  162. nearPoint = render.getRelativePoint(
  163. camera, self.pickerRay.getOrigin())
  164. # Same thing with the direction of the ray
  165. nearVec = render.getRelativeVector(
  166. camera, self.pickerRay.getDirection())
  167. self.pieces[self.dragging].obj.setPos(
  168. PointAtZ(.5, nearPoint, nearVec))
  169. # Do the actual collision pass (Do it only on the squares for
  170. # efficiency purposes)
  171. self.picker.traverse(self.squareRoot)
  172. if self.pq.getNumEntries() > 0:
  173. # if we have hit something, sort the hits so that the closest
  174. # is first, and highlight that node
  175. self.pq.sortEntries()
  176. i = int(self.pq.getEntry(0).getIntoNode().getTag('square'))
  177. # Set the highlight on the picked square
  178. self.squares[i].setColor(HIGHLIGHT)
  179. self.hiSq = i
  180. return Task.cont
  181. def grabPiece(self):
  182. # If a square is highlighted and it has a piece, set it to dragging
  183. # mode
  184. if self.hiSq is not False and self.pieces[self.hiSq]:
  185. self.dragging = self.hiSq
  186. self.hiSq = False
  187. def releasePiece(self):
  188. # Letting go of a piece. If we are not on a square, return it to its original
  189. # position. Otherwise, swap it with the piece in the new square
  190. # Make sure we really are dragging something
  191. if self.dragging is not False:
  192. # We have let go of the piece, but we are not on a square
  193. if self.hiSq is False:
  194. self.pieces[self.dragging].obj.setPos(
  195. SquarePos(self.dragging))
  196. else:
  197. # Otherwise, swap the pieces
  198. self.swapPieces(self.dragging, self.hiSq)
  199. # We are no longer dragging anything
  200. self.dragging = False
  201. def setupLights(self): # This function sets up some default lighting
  202. ambientLight = AmbientLight("ambientLight")
  203. ambientLight.setColor((.8, .8, .8, 1))
  204. directionalLight = DirectionalLight("directionalLight")
  205. directionalLight.setDirection(LVector3(0, 45, -45))
  206. directionalLight.setColor((0.2, 0.2, 0.2, 1))
  207. render.setLight(render.attachNewNode(directionalLight))
  208. render.setLight(render.attachNewNode(ambientLight))
  209. # Class for a piece. This just handles loading the model and setting initial
  210. # position and color
  211. class Piece(object):
  212. def __init__(self, square, color):
  213. self.obj = loader.loadModel(self.model)
  214. self.obj.reparentTo(render)
  215. self.obj.setColor(color)
  216. self.obj.setPos(SquarePos(square))
  217. # Classes for each type of chess piece
  218. # Obviously, we could have done this by just passing a string to Piece's init.
  219. # But if you wanted to make rules for how the pieces move, a good place to start
  220. # would be to make an isValidMove(toSquare) method for each piece type
  221. # and then check if the destination square is acceptible during ReleasePiece
  222. class Pawn(Piece):
  223. model = "models/pawn"
  224. class King(Piece):
  225. model = "models/king"
  226. class Queen(Piece):
  227. model = "models/queen"
  228. class Bishop(Piece):
  229. model = "models/bishop"
  230. class Knight(Piece):
  231. model = "models/knight"
  232. class Rook(Piece):
  233. model = "models/rook"
  234. # Do the main initialization and start 3D rendering
  235. demo = ChessboardDemo()
  236. demo.run()