| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- #!/usr/bin/env python
- # Author: Shao Zhang and Phil Saltzman
- # Models: Eddie Canaan
- # Last Updated: 2015-03-13
- #
- # This tutorial shows how to determine what objects the mouse is pointing to
- # We do this using a collision ray that extends from the mouse position
- # and points straight into the scene, and see what it collides with. We pick
- # the object with the closest collision
- from direct.showbase.ShowBase import ShowBase
- from panda3d.core import CollisionTraverser, CollisionNode
- from panda3d.core import CollisionHandlerQueue, CollisionRay
- from panda3d.core import AmbientLight, DirectionalLight, LightAttrib
- from panda3d.core import TextNode
- from panda3d.core import LPoint3, LVector3, BitMask32
- from direct.gui.OnscreenText import OnscreenText
- from direct.showbase.DirectObject import DirectObject
- from direct.task.Task import Task
- import sys
- # First we define some constants for the colors
- BLACK = (0, 0, 0, 1)
- WHITE = (1, 1, 1, 1)
- HIGHLIGHT = (0, 1, 1, 1)
- PIECEBLACK = (.15, .15, .15, 1)
- # Now we define some helper functions that we will need later
- # This function, given a line (vector plus origin point) and a desired z value,
- # will give us the point on the line where the desired z value is what we want.
- # This is how we know where to position an object in 3D space based on a 2D mouse
- # position. It also assumes that we are dragging in the XY plane.
- #
- # This is derived from the mathematical of a plane, solved for a given point
- def PointAtZ(z, point, vec):
- return point + vec * ((z - point.getZ()) / vec.getZ())
- # A handy little function for getting the proper position for a given square1
- def SquarePos(i):
- return LPoint3((i % 8) - 3.5, int(i // 8) - 3.5, 0)
- # Helper function for determining whether a square should be white or black
- # The modulo operations (%) generate the every-other pattern of a chess-board
- def SquareColor(i):
- if (i + ((i // 8) % 2)) % 2:
- return BLACK
- else:
- return WHITE
- class ChessboardDemo(ShowBase):
- def __init__(self):
- # Initialize the ShowBase class from which we inherit, which will
- # create a window and set up everything we need for rendering into it.
- ShowBase.__init__(self)
- # This code puts the standard title and instruction text on screen
- self.title = OnscreenText(text="Panda3D: Tutorial - Mouse Picking",
- style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),
- pos=(0.8, -0.95), scale = .07)
- self.escapeEvent = OnscreenText(
- text="ESC: Quit", parent=base.a2dTopLeft,
- style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.1),
- align=TextNode.ALeft, scale = .05)
- self.mouse1Event = OnscreenText(
- text="Left-click and drag: Pick up and drag piece",
- parent=base.a2dTopLeft, align=TextNode.ALeft,
- style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.16), scale=.05)
- self.accept('escape', sys.exit) # Escape quits
- self.disableMouse() # Disble mouse camera control
- camera.setPosHpr(0, -12, 8, 0, -35, 0) # Set the camera
- self.setupLights() # Setup default lighting
- # Since we are using collision detection to do picking, we set it up like
- # any other collision detection system with a traverser and a handler
- self.picker = CollisionTraverser() # Make a traverser
- self.pq = CollisionHandlerQueue() # Make a handler
- # Make a collision node for our picker ray
- self.pickerNode = CollisionNode('mouseRay')
- # Attach that node to the camera since the ray will need to be positioned
- # relative to it
- self.pickerNP = camera.attachNewNode(self.pickerNode)
- # Everything to be picked will use bit 1. This way if we were doing other
- # collision we could separate it
- self.pickerNode.setFromCollideMask(BitMask32.bit(1))
- self.pickerRay = CollisionRay() # Make our ray
- # Add it to the collision node
- self.pickerNode.addSolid(self.pickerRay)
- # Register the ray as something that can cause collisions
- self.picker.addCollider(self.pickerNP, self.pq)
- # self.picker.showCollisions(render)
- # Now we create the chess board and its pieces
- # We will attach all of the squares to their own root. This way we can do the
- # collision pass just on the squares and save the time of checking the rest
- # of the scene
- self.squareRoot = render.attachNewNode("squareRoot")
- # For each square
- self.squares = [None for i in range(64)]
- self.pieces = [None for i in range(64)]
- for i in range(64):
- # Load, parent, color, and position the model (a single square
- # polygon)
- self.squares[i] = loader.loadModel("models/square")
- self.squares[i].reparentTo(self.squareRoot)
- self.squares[i].setPos(SquarePos(i))
- self.squares[i].setColor(SquareColor(i))
- # Set the model itself to be collideable with the ray. If this model was
- # any more complex than a single polygon, you should set up a collision
- # sphere around it instead. But for single polygons this works
- # fine.
- self.squares[i].find("**/polygon").node().setIntoCollideMask(
- BitMask32.bit(1))
- # Set a tag on the square's node so we can look up what square this is
- # later during the collision pass
- self.squares[i].find("**/polygon").node().setTag('square', str(i))
- # We will use this variable as a pointer to whatever piece is currently
- # in this square
- # The order of pieces on a chessboard from white's perspective. This list
- # contains the constructor functions for the piece classes defined
- # below
- pieceOrder = (Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook)
- for i in range(8, 16):
- # Load the white pawns
- self.pieces[i] = Pawn(i, WHITE)
- for i in range(48, 56):
- # load the black pawns
- self.pieces[i] = Pawn(i, PIECEBLACK)
- for i in range(8):
- # Load the special pieces for the front row and color them white
- self.pieces[i] = pieceOrder[i](i, WHITE)
- # Load the special pieces for the back row and color them black
- self.pieces[i + 56] = pieceOrder[i](i + 56, PIECEBLACK)
- # This will represent the index of the currently highlited square
- self.hiSq = False
- # This wil represent the index of the square where currently dragged piece
- # was grabbed from
- self.dragging = False
- # Start the task that handles the picking
- self.mouseTask = taskMgr.add(self.mouseTask, 'mouseTask')
- self.accept("mouse1", self.grabPiece) # left-click grabs a piece
- self.accept("mouse1-up", self.releasePiece) # releasing places it
- # This function swaps the positions of two pieces
- def swapPieces(self, fr, to):
- temp = self.pieces[fr]
- self.pieces[fr] = self.pieces[to]
- self.pieces[to] = temp
- if self.pieces[fr]:
- self.pieces[fr].square = fr
- self.pieces[fr].obj.setPos(SquarePos(fr))
- if self.pieces[to]:
- self.pieces[to].square = to
- self.pieces[to].obj.setPos(SquarePos(to))
- def mouseTask(self, task):
- # This task deals with the highlighting and dragging based on the mouse
- # First, clear the current highlight
- if self.hiSq is not False:
- self.squares[self.hiSq].setColor(SquareColor(self.hiSq))
- self.hiSq = False
- # Check to see if we can access the mouse. We need it to do anything
- # else
- if self.mouseWatcherNode.hasMouse():
- # get the mouse position
- mpos = self.mouseWatcherNode.getMouse()
- # Set the position of the ray based on the mouse position
- self.pickerRay.setFromLens(self.camNode, mpos.getX(), mpos.getY())
- # If we are dragging something, set the position of the object
- # to be at the appropriate point over the plane of the board
- if self.dragging is not False:
- # Gets the point described by pickerRay.getOrigin(), which is relative to
- # camera, relative instead to render
- nearPoint = render.getRelativePoint(
- camera, self.pickerRay.getOrigin())
- # Same thing with the direction of the ray
- nearVec = render.getRelativeVector(
- camera, self.pickerRay.getDirection())
- self.pieces[self.dragging].obj.setPos(
- PointAtZ(.5, nearPoint, nearVec))
- # Do the actual collision pass (Do it only on the squares for
- # efficiency purposes)
- self.picker.traverse(self.squareRoot)
- if self.pq.getNumEntries() > 0:
- # if we have hit something, sort the hits so that the closest
- # is first, and highlight that node
- self.pq.sortEntries()
- i = int(self.pq.getEntry(0).getIntoNode().getTag('square'))
- # Set the highlight on the picked square
- self.squares[i].setColor(HIGHLIGHT)
- self.hiSq = i
- return Task.cont
- def grabPiece(self):
- # If a square is highlighted and it has a piece, set it to dragging
- # mode
- if self.hiSq is not False and self.pieces[self.hiSq]:
- self.dragging = self.hiSq
- self.hiSq = False
- def releasePiece(self):
- # Letting go of a piece. If we are not on a square, return it to its original
- # position. Otherwise, swap it with the piece in the new square
- # Make sure we really are dragging something
- if self.dragging is not False:
- # We have let go of the piece, but we are not on a square
- if self.hiSq is False:
- self.pieces[self.dragging].obj.setPos(
- SquarePos(self.dragging))
- else:
- # Otherwise, swap the pieces
- self.swapPieces(self.dragging, self.hiSq)
- # We are no longer dragging anything
- self.dragging = False
- def setupLights(self): # This function sets up some default lighting
- ambientLight = AmbientLight("ambientLight")
- ambientLight.setColor((.8, .8, .8, 1))
- directionalLight = DirectionalLight("directionalLight")
- directionalLight.setDirection(LVector3(0, 45, -45))
- directionalLight.setColor((0.2, 0.2, 0.2, 1))
- render.setLight(render.attachNewNode(directionalLight))
- render.setLight(render.attachNewNode(ambientLight))
- # Class for a piece. This just handles loading the model and setting initial
- # position and color
- class Piece(object):
- def __init__(self, square, color):
- self.obj = loader.loadModel(self.model)
- self.obj.reparentTo(render)
- self.obj.setColor(color)
- self.obj.setPos(SquarePos(square))
- # Classes for each type of chess piece
- # Obviously, we could have done this by just passing a string to Piece's init.
- # But if you wanted to make rules for how the pieces move, a good place to start
- # would be to make an isValidMove(toSquare) method for each piece type
- # and then check if the destination square is acceptible during ReleasePiece
- class Pawn(Piece):
- model = "models/pawn"
- class King(Piece):
- model = "models/king"
- class Queen(Piece):
- model = "models/queen"
- class Bishop(Piece):
- model = "models/bishop"
- class Knight(Piece):
- model = "models/knight"
- class Rook(Piece):
- model = "models/rook"
- # Do the main initialization and start 3D rendering
- demo = ChessboardDemo()
- demo.run()
|