class Picker(object): ''' A class for picking (Panda3d) objects. ''' def __init__(self, app, render, camera, mouseWatcher, pickKeyOn, pickKeyOff, collideMask, pickableTag="pickable"): self.render = render self.mouseWatcher = mouseWatcher.node() self.camera = camera self.camLens = camera.node().get_lens() self.collideMask = collideMask self.pickableTag = pickableTag self.taskMgr = app.task_mgr # setup event callback for picking body self.pickKeyOn = pickKeyOn self.pickKeyOff = pickKeyOff app.accept(self.pickKeyOn, self._pickBody, [self.pickKeyOn]) app.accept(self.pickKeyOff, self._pickBody, [self.pickKeyOff]) # collision data self.collideMask = collideMask self.cTrav = CollisionTraverser() self.collisionHandler = CollisionHandlerQueue() self.pickerRay = CollisionRay() pickerNode = CollisionNode("Utilities.pickerNode") node = NodePath("PhysicsNode") node.reparentTo(render) anp = node.attachNewNode(pickerNode) base.physicsMgr.attachPhysicalNode(pickerNode) pickerNode.add_solid(self.pickerRay) pickerNode.set_from_collide_mask(self.collideMask) pickerNode.set_into_collide_mask(BitMask32.all_off()) #pickerNode.node().getPhysicsObject().setMass(10) self.cTrav.add_collider(self.render.attach_new_node(pickerNode), self.collisionHandler) # service data self.pickedBody = None self.oldPickingDist = 0.0 self.deltaDist = 0.0 self.dragging = False self.updateTask = None def _pickBody(self, event): # handle body picking if event == self.pickKeyOn: # check mouse position if self.mouseWatcher.has_mouse(): # Get to and from pos in camera coordinates pMouse = self.mouseWatcher.get_mouse() # pFrom = LPoint3f() pTo = LPoint3f() if self.camLens.extrude(pMouse, pFrom, pTo): # Transform to global coordinates rayFromWorld = self.render.get_relative_point( self.camera, pFrom) rayToWorld = self.render.get_relative_point( self.camera, pTo) # cast a ray to detect a body # traverse downward starting at rayOrigin self.pickerRay.set_direction(rayToWorld - rayFromWorld) self.pickerRay.set_origin(rayFromWorld) self.cTrav.traverse(self.render) if self.collisionHandler.get_num_entries() > 0: self.collisionHandler.sort_entries() entry0 = self.collisionHandler.get_entry(0) hitPos = entry0.get_surface_point(self.render) # get the first parent with name pickedObject = entry0.get_into_node_path() while not pickedObject.has_tag(self.pickableTag): pickedObject = pickedObject.getParent() if not pickedObject: return if pickedObject == self.render: return # self.pickedBody = pickedObject self.oldPickingDist = (hitPos - rayFromWorld).length() self.deltaDist = ( self.pickedBody.get_pos(self.render) - hitPos) print(self.pickedBody.get_name(), hitPos) if not self.dragging: self.dragging = True # create the task for updating picked body motion self.updateTask = self.taskMgr.add( self._movePickedBody, "_movePickedBody") # set sort/priority self.updateTask.set_sort(0) self.updateTask.set_priority(0) else: if self.dragging: # remove pick body motion update task self.taskMgr.remove("_movePickedBody") self.updateTask = None self.dragging = False self.pickedBody = None def _movePickedBody(self, task): # handle picked body if any if self.pickedBody and self.dragging: # check mouse position if self.mouseWatcher.has_mouse(): # Get to and from pos in camera coordinates pMouse = self.mouseWatcher.get_mouse() # pFrom = LPoint3f() pTo = LPoint3f() if self.camLens.extrude(pMouse, pFrom, pTo): # Transform to global coordinates rayFromWorld = self.render.get_relative_point( self.camera, pFrom) rayToWorld = self.render.get_relative_point( self.camera, pTo) # keep it at the same picking distance direction = (rayToWorld - rayFromWorld).normalized() direction *= self.oldPickingDist self.pickedBody.set_pos( self.render, rayFromWorld + direction + self.deltaDist) #self.pickedBody.reparentTo(np) #self.pickedBody.setMass(10.0) # return task.cont
class Raycaster(Entity): def __init__(self): super().__init__( name = 'raycaster', eternal = True ) self._picker = CollisionTraverser() # Make a traverser self._pq = CollisionHandlerQueue() # Make a handler self._pickerNode = CollisionNode('raycaster') self._pickerNP = self.attach_new_node(self._pickerNode) self._collision_ray = CollisionRay() # Make our ray self._pickerNode.addSolid(self._collision_ray) self._picker.addCollider(self._pickerNP, self._pq) self._pickerNP.show() def distance(self, a, b): return math.sqrt(sum( (a - b)**2 for a, b in zip(a, b))) def raycast(self, origin, direction=(0,0,1), dist=math.inf, traverse_target=scene, ignore=list(), debug=False): self.position = origin self.look_at(self.position + direction) # need to do this for it to work for some reason self._collision_ray.set_origin(Vec3(0,0,0)) self._collision_ray.set_direction(Vec3(0,1,0)) if debug: self._pickerNP.show() else: self._pickerNP.hide() self._picker.traverse(traverse_target) if self._pq.get_num_entries() == 0: self.hit = Hit(hit=False) return self.hit self._pq.sort_entries() self.entries = [ # filter out ignored entities e for e in self._pq.getEntries() if e.get_into_node_path().parent not in ignore ] if len(self.entries) == 0: self.hit = Hit(hit=False) return self.hit self.collision = self.entries[0] nP = self.collision.get_into_node_path().parent point = self.collision.get_surface_point(nP) point = Vec3(point[0], point[2], point[1]) world_point = self.collision.get_surface_point(render) world_point = Vec3(world_point[0], world_point[2], world_point[1]) hit_dist = self.distance(self.world_position, world_point) if hit_dist <= dist: if nP.name.endswith('.egg'): nP = nP.parent self.hit = Hit(hit=True) for e in scene.entities: if e == nP: # print('cast nP to Entity') self.hit.entity = e self.hit.point = point self.hit.world_point = world_point self.hit.distance = hit_dist normal = self.collision.get_surface_normal(self.collision.get_into_node_path().parent) self.hit.normal = (normal[0], normal[2], normal[1]) normal = self.collision.get_surface_normal(render) self.hit.world_normal = (normal[0], normal[2], normal[1]) return self.hit self.hit = Hit(hit=False) return self.hit