class CameraControl(DirectObject, HasKeybinds): """ adds controls to a given camera, usually base.camera""" def __init__(self,camera=None): #camera setup self.camera = camera if self.camera == None: self.camera = base.camera #XXX note, when moving cam target we need to make sure the camera doesnt move too... cameraBase = GeomNode('cameraBase') #utility node for pan targetGeom = makeCameraTarget() cameraBase.addGeom(targetGeom) self.cameraBase = render.attachNewNode(cameraBase) #self.cameraBase.setTwoSided(True) #backface culling issue with my tristrip fail self.cameraTarget = NodePath('cameraTarget') #utility node for rot, zoom, reattach self.cameraTarget.reparentTo(self.cameraBase) #self.cameraTarget.reparentTo(render) self.camera.reparentTo(self.cameraTarget) self.track = self.camera.attachNewNode('track') #hack for pointing vector self.track.setPos(LVecBase3f(0,50,0)) #nn = GeomNode('helper') #ng = makeCameraTarget() #nn.addGeom(targetGeom) #self.track.attachNewNode(nn) #keybind setup self.__ends__=defaultdict(list) #self.accept("escape", sys.exit) #no, exit_cleanup will handle this... for function_name, key in keybinds['view'].items(): #self.accept(key,taskMgr.add,(getattr(self,function),function+'Task')) self.accept(key, self.makeTask, [function_name]) # TODO split out functions that don't require tasks keytest=key.split('-')[-1] #print(keytest) if keytest in {'mouse1','mouse2','mouse3'}: self.addEndTask(keytest,function_name) self.accept(keytest+'-up', self.endTask, [keytest,function_name]) #gains #TODO tweak me! self.XGAIN = .01 self.YGAIN = .01 #window setup self.getWindowSize() self.accept('window-event', self.getWindowSize) #self.accept('mouse1') #mouse 1 by itself does selection? #self.accpet('mouse3') #pan #self.accpet('mouse2') #--camera moves relatvie to arbitrary origin-- #pan in plane #zoom #this needs to be on a log scale, linear is balls #rotate #--camera in place-- #roll camera in place #yaw #pitch #look at selection/origin/center of mass of #--camera lense changes-- #fov (for perspective) #perspective/orthographic #--worldcraft-- #z mode wasd + mouse to orient for zooming #--selection functions we need to leave space for-- #drop origin if we don't have something selected #click select #drag select, logial intersec #right click for menu self.__ch__=None self.__cp__=None self.__cr__=None self.__cth__=None self.__ctp__=None pass def getWindowSize(self,wat=None): self.__winx__ = base.win.getXSize() self.__winy__ = base.win.getYSize() #print(self.__winx__,self.__winy__) def makeTask(self, function_name): """ ye old task spawner """ if hasattr(self, function_name): if base.mouseWatcherNode.hasMouse(): x,y = base.mouseWatcherNode.getMouse() setattr(self, '__%sTask_s__'%function_name, (x,y)) #this should be faster taskMgr.add(getattr(self,function_name), function_name+'Task') else: raise KeyError('Check your keybinds, there is no function by that name here!') def addEndTask(self,key,function_name): self.__ends__[key].append(function_name) def endTask(self, key, function): for func in self.__ends__[key]: taskMgr.remove(func+'Task') setattr(self, '__%sTask_s__'%func, None) #this should be faster self.__ch__=None #FIXME this seems hackish self.__cp__=None self.__cr__=None self.__cth__=None self.__ctp__=None def getMouseDdDt(self, name): #XXX deprecated """ use gain to adjust pixels per degree this should probably be normalized to screen size actually? or no... but to what? """ if base.mouseWatcherNode.hasMouse(): x,z = base.mouseWatcherNode.getMouse() sx,sz = getattr(self,'__%s_start__'%name) print(x,sx) print(z,sz) if z != sz or x != sx: #watch out for aliasing here... norm = (((x - sx) * self.XGAIN)**2 + ((z - sz) * self.YGAIN)**2)**.5 #norm = ((x - sx) * self.X_GAIN), ((z - sz) * self.Y_GAIN) setattr(self, '__%s_start__'%name, (x,z)) return norm else: #mouse has not moved return 0 def getMouseDdDf(self,name): if base.mouseWatcherNode.hasMouse(): x,y = base.mouseWatcherNode.getMouse() sx,sy = getattr(self,'__%s_s__'%(name)) dx = (x - sx) * self.XGAIN * self.__winx__ dy = (y - sy) * self.YGAIN * self.__winy__ return dx, dy def getMouseCross(self,name): #FIXME may need to do this incrementally as we started with... if base.mouseWatcherNode.hasMouse(): x,y = base.mouseWatcherNode.getMouse() sx,sy = getattr(self,'__%s_s__'%(name)) dx = (x - sx) * self.XGAIN * self.__winx__ dy = (y - sy) * self.YGAIN * self.__winy__ norm = (dx**2 + dy**2)**.5 cross = x * sy - y * sx return cross * norm @event_callback def home(self, task): self.camera.lookAt(self.cameraBase) taskMgr.remove(task.getName()) return task.cont @event_callback def pan(self, task): """ I don't like it, it's weird! """ invert = -1 magic_number = 15 magic_number = 20 if base.mouseWatcherNode.hasMouse(): x,y = base.mouseWatcherNode.getMouse() sx,sy = getattr(self,'__%s_s__'%(task.getName())) dx = (x - sx) * self.XGAIN * self.__winx__ * magic_number * invert dy = (y - sy) * self.YGAIN * self.__winy__ * magic_number * invert #cx,cy,cz = self.camera.getPos() self.camera.setPos(self.camera,dx,0,dy) setattr(self, '__%s_s__'%task.getName(), (x,y)) #reset each frame to compensate for moving from own position #nx,ny,nz = self.camera.getPos() #dx2, dy2, dz2 = nx-cx, ny-cy, nz-cz #self.camera.setPos(cx,cz,cy) #self.cameraBase.setPos(self.cameraBase,dx2,dy2,dz2) #a hack to move cameraBase as if it were the camera #self.cameraTarget.setPos(self.cameraBase,dx2,dy2,dz2) #a hack to move cameraBase as if it were the camera return task.cont @event_callback def zoom_in_slow(self, task, speed = 10): return self.zoom_in(task, speed) #hehe this will work because it just passes the task :) @event_callback def zoom_out_slow(self, task, speed = 10): return self.zoom_out(task, speed) @event_callback def zoom_in_fast(self, task, speed = 1000): return self.zoom_in(task, speed) #hehe this will work because it just passes the task :) @event_callback def zoom_out_fast(self, task, speed = 1000): return self.zoom_out(task, speed) @event_callback def zoom_in(self, task, speed = 100): #FIXME zoom_in and zoom_out still get custom xys even thought they don't use them! self.camera.setPos(self.camera,0,speed,0) taskMgr.remove(task.getName()) return task.cont @event_callback def zoom_out(self, task, speed = 100): self.camera.setPos(self.camera,0,-speed,0) taskMgr.remove(task.getName()) #we do it this way instead of addOnce because we want to add all the tasks in one go return task.cont @event_callback def rotate(self, task): #FIXME disregard orientation acqurie proper mouse movements! dx,dy = self.getMouseDdDf(task.getName()) if self.__cth__ == None: self.__cth__ = self.cameraTarget.getH() if self.__ctp__ == None: self.__ctp__ = self.cameraTarget.getP() self.cameraTarget.setH(self.__cth__ - dx * 10) self.cameraTarget.setP(self.__ctp__ + dy * 10) return task.cont #if we are in camera mode @event_callback def pitch(self, task): dx,dy = self.getMouseDdDf(task.getName()) print('got pitch',dy) return task.cont @event_callback def look(self, task): #AKA heading in hpr dx,dy = self.getMouseDdDf(task.getName()) if self.__ch__ == None: self.__ch__ = self.camera.getH() if self.__cp__ == None: self.__cp__ = self.camera.getP() self.camera.setH(self.__ch__ - dx) self.camera.setP(self.__cp__ + dy) #FIXME when we're clicking this might should be inverted? return task.cont @event_callback def roll(self, task): """ ALWAYS roll with respect to axis of rotation""" if self.__cr__ == None: self.__cr__ = self.cameraTarget.getR() #cross product idiot cross = self.getMouseCross(task.getName()) self.cameraTarget.setR(self.__cr__ - cross * 10 ) return task.cont
class Vehicle(ActorNode): def __init__(self, parent=None): ''' Create a new Vehicle node. Physics should be initialized before any instances of Vehicle are created. arguments: parent -- A PandaNode for the vehicle to attach to. Default is None, in which case the Vehicle should be added to the scene graph via NodePath.attachNewNode(). ''' ActorNode.__init__(self, 'VehiclePhysics') base.physicsMgr.attachPhysicalNode(self) self.getPhysicsObject().setMass(MASS) if parent: self.myPath = parent.attachNewNode(self) else: self.myPath = NodePath(self) # Load vehicle model and place in the transparent bin. vehicleModel = loader.loadModel(MODEL_PATH) hull = vehicleModel.find('**/Hull') hull.setBin('transparent', 30) pwnsEnclosure = vehicleModel.find('**/Pwns_Enclosure') pwnsEnclosure.setBin('transparent', 30) self.myPath.setPos(0, 0, -0.0) selectable = self.myPath.attachNewNode(SelectableNode('vehicle sel')) vehicleModel.reparentTo(selectable) # ==== Initialize Physics ==== # thrusterForceNode = ForceNode('ThrusterForce') self.myPath.attachNewNode(thrusterForceNode) self.linearForce = LinearVectorForce(0, 0, 0) self.linearForce.setMassDependent(1) self.angularForce = AngularVectorForce(0, 0, 0) thrusterForceNode.addForce(self.linearForce) thrusterForceNode.addForce(self.angularForce) self.getPhysical(0).addLinearForce(self.linearForce) self.getPhysical(0).addAngularForce(self.angularForce) self.previousXY = (self.myPath.getX(), self.myPath.getY()) self.tm = ThrusterManager() # Add self.updatePhysics to the task manager and run this task as # frequently as possible. self.updatePhysicsTask = taskMgr.add(self.updatePhysics, 'UpdatePhysics') # ==== Initialize Cameras ==== # lens = PerspectiveLens() lens.setNearFar(0.05, 100.0) #Use either FocalLength or Fov. Fov ~40 is about what actual forward cameras are #lens.setFocalLength(0.8) lens.setFov(70, 70) camera = Camera("Forward_left", lens).getPath() camera.reparentTo(vehicleModel.find('**/Forward_Camera')) camera.setX(camera.getX() - 0.1) # Forward cameras 20cm apart camera.setY( camera.getY() + 0.05) # Move in front of torpedo tubes to avoid abstruction camera.setHpr(0, 0, 0) camera = Camera("Forward_right", lens).getPath() camera.reparentTo(vehicleModel.find('**/Forward_Camera')) camera.setX(camera.getX() + 0.1) # Forward cameras 20cm apart camera.setY( camera.getY() + 0.05) # Move in front of torpedo tubes to avoid abstruction camera.setHpr(0, 0, 0) lens = PerspectiveLens() lens.setNearFar(0.05, 100.0) lens.setFocalLength(0.8) camera = Camera("Downward", lens).getPath() camera.reparentTo(vehicleModel.find('**/Downward_Camera')) camera.setHpr(0, -90, 0) #Layout link (to access hydrophone information) self.layout = None #Hydrophone variables self.start_time = time() self.last_hydro_update = time() def setLayout(self, layout): #Add a link to the layout to allow for referencing other objects #This is necessary for the hydrophone addition self.layout = layout def getDepth(self): ''' Returns the depth of the vehicle, in meters. ''' return -0.15 - self.myPath.getZ() def getHeading(self): ''' Returns the heading of the vehicle, in clockwise degrees. ''' # Panda uses counter-clockwise degrees, with the range (-180, 180]. heading = self.myPath.getH() if heading < 0: return -heading elif heading > 0: return 360 - heading else: return 0 def updatePhysics(self, task): ''' Use the motor PWM values calculated by the controller to apply forces to the simulated vehicle. This runs at every frame, so it needs to complete quickly. ''' outputs = shm.kalman.get() self.tm.update(outputs) passive_wrench = vehicle.passive_forces(outputs, self.tm) passive_forces, passive_torques = passive_wrench[:3], \ passive_wrench[3:] # Get motor thrusts thrusts = np.array(self.tm.get_thrusts()) # Add passive forces and torques to that produced by thrusters, # converting them to sub space first. force = self.tm.total_thrust(thrusts) + \ self.tm.orientation.conjugate() * passive_forces torque = self.tm.total_torque(thrusts) + \ self.tm.orientation.conjugate() * passive_torques # Finally apply forces and torques to the model # we also need to account for panda3d's strange coordinate system # (x and y are flipped and z points up (instead of down)) self.linearForce.setVector(force_subspace[1], \ force_subspace[0], \ -force_subspace[2]) # We're supposed to use axis angle here, but I'm being sneaky # and using the torque vector directly, i.e. non normalized axis angle # with the hopes that this LRotationf constructor will figure it out self.angularForce.setQuat(\ LRotationf(LVector3f(torque_subspace[1], \ torque_subspace[0], \ -torque_subspace[2]), 1)) # Update shared variables for controller outputs.heading = self.getHeading() outputs.pitch = self.myPath.getP() outputs.roll = self.myPath.getR() # This velocity is in world space # We need to put it into THRUST CONVENTION SPACE # which we assume kalman outputs in... velocity = self.getPhysicsObject().getVelocity() # Bring the velocity into THRUST CONVENTION SPACE # Don't forget to account for panda's coordinate system velocity = self.tm.heading_quat.conjugate() * \ np.array((velocity.getY(), velocity.getX(), -velocity.getZ())) outputs.velx = velocity[0] outputs.vely = velocity[1] outputs.depth_rate = velocity[2] outputs.depth = self.getDepth() outputs.north = self.myPath.getY() outputs.east = self.myPath.getX() dX = self.myPath.getX() - self.previousXY[0] dY = self.myPath.getY() - self.previousXY[1] # Forward and sway are in THRUST CONVENTION SPACE # don't forget to account for panda's coordinate system dF, dS, dD = self.tm.heading_quat.conjugate() * np.array((dY, dX, 0.0)) outputs.forward += dF outputs.sway += dS # Output some quaternions, accounting for Panda's coordinate system outputs.q0, outputs.q2, outputs.q1, outputs.q3 = self.myPath.getQuat() outputs.q3 *= -1.0 shm.kalman.set(outputs) svHeadingInt.set(self.getHeading()) svDepth.set(self.getDepth()) #XXX: Approximate altitude assuming that the pool is 12 feet deep svAltitude.set(3.6 - self.getDepth()) svDvlDmgNorth.set(self.myPath.getY()) svDvlDmgEast.set(self.myPath.getX()) self.previousXY = (self.myPath.getX(), self.myPath.getY()) #update self.output_hydro_data() return Task.cont def output_hydro_data(self): #Update simulated hydrophone values pingers = [] #Get all pingers from the layout for element in self.layout.elements: if element.getTypeName() == "Pinger": pingers.append(element) HYDRO_TICK_PERIOD = 1 if time() - self.last_hydro_update > HYDRO_TICK_PERIOD: dt = time() - self.last_hydro_update self.last_hydro_update = time() if shm.hydrophones_settings.dsp_mode.get() == 1: #Search mode #Incr search count shm.hydrophones_results.search_count.set( shm.hydrophones_results.search_count.get() + 1) #Generate proper "hydrophone bins" marks sb = 0 for p in pingers: f = p.pinger_frequency dc = (f - (shm.hydrophones_settings.searchCenter.get() - shm.hydrophones_settings.searchDelta.get()) ) / shm.hydrophones_settings.searchStep.get() + 0.5 sb |= 1 << int(dc) shm.hydrophones_results.search_bins.set(sb) else: #Track Mode #Incr ping count shm.hydrophones_results.ping_count.set( shm.hydrophones_results.ping_count.get() + 1) #Determine which pinger we are actively tracking (within 0.7khz of target) targetp = None for p in pingers: if abs(shm.hydrophones_settings.trackFrequency.get() - p.pinger_frequency) < 700: targetp = p if targetp is not None: shm.hydrophones_results.intensity.set( int(shm.hydrophones_settings.trackMagThresh.get() + 1e4 * random())) shm.hydrophones_results.ping_time.set(int(dt * 1000)) pp = targetp.path.getPos() vv = vector.Vector(self.myPath.getY(), self.myPath.getX()) pv = vector.Vector(pp.getY(), pp.getX()) #heading dv = pv - vv ang = vector.GetAuvAngle(dv) hdiff = helpers.heading_diff(self.getHeading(), ang) shm.hydrophones_results.heading.set(hdiff) #elevation dh = self.myPath.getZ() - pp.getZ() dist = vector.Length(dv) elev = math.degrees(math.atan2(dist, dh)) elev = min(elev, 90) shm.hydrophones_results.elevation.set(elev) #phase calculations dy = self.myPath.getY() - pp.getY() dx = self.myPath.getX() - pp.getX() yang = math.degrees(math.atan2(dist, dy)) xang = math.degrees(math.atan2(dist, dx)) shm.hydrophones_results.phaseX.set((90.0 - xang) / 90.0) shm.hydrophones_results.phaseY.set((90.0 - yang) / 90.0) shm.hydrophones_results.phaseZ.set((90.0 - elev) / 90.0) else: shm.hydrophones_results.heading.set(0) shm.hydrophones_results.elevation.set(0) shm.hydrophones_results.intensity.set(0) shm.hydrophones_results.ping_time.set(0) shm.hydrophones_results.uptime.set(int(time() - self.start_time)) def __del__(self): ''' Remove update tasks from the panda task manager. ''' taskMgr.remove(self.updatePhysicsTask) ActorNode.__del__(self)
class CameraControl(DirectObject, HasKeybinds): """ adds controls to a given camera, usually base.camera""" def __init__(self, camera=None): #camera setup self.camera = camera if self.camera == None: self.camera = base.camera #XXX note, when moving cam target we need to make sure the camera doesnt move too... cameraBase = GeomNode('cameraBase') #utility node for pan targetGeom = makeCameraTarget() cameraBase.addGeom(targetGeom) self.cameraBase = render.attachNewNode(cameraBase) #self.cameraBase.setTwoSided(True) #backface culling issue with my tristrip fail self.cameraTarget = NodePath( 'cameraTarget') #utility node for rot, zoom, reattach self.cameraTarget.reparentTo(self.cameraBase) #self.cameraTarget.reparentTo(render) self.camera.reparentTo(self.cameraTarget) self.track = self.camera.attachNewNode( 'track') #hack for pointing vector self.track.setPos(LVecBase3f(0, 50, 0)) #nn = GeomNode('helper') #ng = makeCameraTarget() #nn.addGeom(targetGeom) #self.track.attachNewNode(nn) #keybind setup self.__ends__ = defaultdict(list) #self.accept("escape", sys.exit) #no, exit_cleanup will handle this... for function_name, key in keybinds['view'].items(): #self.accept(key,taskMgr.add,(getattr(self,function),function+'Task')) self.accept( key, self.makeTask, [function_name ]) # TODO split out functions that don't require tasks keytest = key.split('-')[-1] #print(keytest) if keytest in {'mouse1', 'mouse2', 'mouse3'}: self.addEndTask(keytest, function_name) self.accept(keytest + '-up', self.endTask, [keytest, function_name]) #gains #TODO tweak me! self.XGAIN = .01 self.YGAIN = .01 #window setup self.getWindowSize() self.accept('window-event', self.getWindowSize) #self.accept('mouse1') #mouse 1 by itself does selection? #self.accpet('mouse3') #pan #self.accpet('mouse2') #--camera moves relatvie to arbitrary origin-- #pan in plane #zoom #this needs to be on a log scale, linear is balls #rotate #--camera in place-- #roll camera in place #yaw #pitch #look at selection/origin/center of mass of #--camera lense changes-- #fov (for perspective) #perspective/orthographic #--worldcraft-- #z mode wasd + mouse to orient for zooming #--selection functions we need to leave space for-- #drop origin if we don't have something selected #click select #drag select, logial intersec #right click for menu self.__ch__ = None self.__cp__ = None self.__cr__ = None self.__cth__ = None self.__ctp__ = None pass def getWindowSize(self, wat=None): self.__winx__ = base.win.getXSize() self.__winy__ = base.win.getYSize() #print(self.__winx__,self.__winy__) def makeTask(self, function_name): """ ye old task spawner """ if hasattr(self, function_name): if base.mouseWatcherNode.hasMouse(): x, y = base.mouseWatcherNode.getMouse() setattr(self, '__%sTask_s__' % function_name, (x, y)) #this should be faster taskMgr.add(getattr(self, function_name), function_name + 'Task') else: raise KeyError( 'Check your keybinds, there is no function by that name here!') def addEndTask(self, key, function_name): self.__ends__[key].append(function_name) def endTask(self, key, function): for func in self.__ends__[key]: taskMgr.remove(func + 'Task') setattr(self, '__%sTask_s__' % func, None) #this should be faster self.__ch__ = None #FIXME this seems hackish self.__cp__ = None self.__cr__ = None self.__cth__ = None self.__ctp__ = None def getMouseDdDt(self, name): #XXX deprecated """ use gain to adjust pixels per degree this should probably be normalized to screen size actually? or no... but to what? """ if base.mouseWatcherNode.hasMouse(): x, z = base.mouseWatcherNode.getMouse() sx, sz = getattr(self, '__%s_start__' % name) print(x, sx) print(z, sz) if z != sz or x != sx: #watch out for aliasing here... norm = (((x - sx) * self.XGAIN)**2 + ((z - sz) * self.YGAIN)**2)**.5 #norm = ((x - sx) * self.X_GAIN), ((z - sz) * self.Y_GAIN) setattr(self, '__%s_start__' % name, (x, z)) return norm else: #mouse has not moved return 0 def getMouseDdDf(self, name): if base.mouseWatcherNode.hasMouse(): x, y = base.mouseWatcherNode.getMouse() sx, sy = getattr(self, '__%s_s__' % (name)) dx = (x - sx) * self.XGAIN * self.__winx__ dy = (y - sy) * self.YGAIN * self.__winy__ return dx, dy def getMouseCross( self, name ): #FIXME may need to do this incrementally as we started with... if base.mouseWatcherNode.hasMouse(): x, y = base.mouseWatcherNode.getMouse() sx, sy = getattr(self, '__%s_s__' % (name)) dx = (x - sx) * self.XGAIN * self.__winx__ dy = (y - sy) * self.YGAIN * self.__winy__ norm = (dx**2 + dy**2)**.5 cross = x * sy - y * sx return cross * norm @event_callback def home(self, task): self.camera.lookAt(self.cameraBase) taskMgr.remove(task.getName()) return task.cont @event_callback def pan(self, task): """ I don't like it, it's weird! """ invert = -1 magic_number = 15 magic_number = 20 if base.mouseWatcherNode.hasMouse(): x, y = base.mouseWatcherNode.getMouse() sx, sy = getattr(self, '__%s_s__' % (task.getName())) dx = (x - sx) * self.XGAIN * self.__winx__ * magic_number * invert dy = (y - sy) * self.YGAIN * self.__winy__ * magic_number * invert #cx,cy,cz = self.camera.getPos() self.camera.setPos(self.camera, dx, 0, dy) setattr( self, '__%s_s__' % task.getName(), (x, y) ) #reset each frame to compensate for moving from own position #nx,ny,nz = self.camera.getPos() #dx2, dy2, dz2 = nx-cx, ny-cy, nz-cz #self.camera.setPos(cx,cz,cy) #self.cameraBase.setPos(self.cameraBase,dx2,dy2,dz2) #a hack to move cameraBase as if it were the camera #self.cameraTarget.setPos(self.cameraBase,dx2,dy2,dz2) #a hack to move cameraBase as if it were the camera return task.cont @event_callback def zoom_in_slow(self, task, speed=10): return self.zoom_in( task, speed) #hehe this will work because it just passes the task :) @event_callback def zoom_out_slow(self, task, speed=10): return self.zoom_out(task, speed) @event_callback def zoom_in_fast(self, task, speed=1000): return self.zoom_in( task, speed) #hehe this will work because it just passes the task :) @event_callback def zoom_out_fast(self, task, speed=1000): return self.zoom_out(task, speed) @event_callback def zoom_in( self, task, speed=100 ): #FIXME zoom_in and zoom_out still get custom xys even thought they don't use them! self.camera.setPos(self.camera, 0, speed, 0) taskMgr.remove(task.getName()) return task.cont @event_callback def zoom_out(self, task, speed=100): self.camera.setPos(self.camera, 0, -speed, 0) taskMgr.remove( task.getName() ) #we do it this way instead of addOnce because we want to add all the tasks in one go return task.cont @event_callback def rotate(self, task ): #FIXME disregard orientation acqurie proper mouse movements! dx, dy = self.getMouseDdDf(task.getName()) if self.__cth__ == None: self.__cth__ = self.cameraTarget.getH() if self.__ctp__ == None: self.__ctp__ = self.cameraTarget.getP() self.cameraTarget.setH(self.__cth__ - dx * 10) self.cameraTarget.setP(self.__ctp__ + dy * 10) return task.cont #if we are in camera mode @event_callback def pitch(self, task): dx, dy = self.getMouseDdDf(task.getName()) print('got pitch', dy) return task.cont @event_callback def look(self, task): #AKA heading in hpr dx, dy = self.getMouseDdDf(task.getName()) if self.__ch__ == None: self.__ch__ = self.camera.getH() if self.__cp__ == None: self.__cp__ = self.camera.getP() self.camera.setH(self.__ch__ - dx) self.camera.setP( self.__cp__ + dy) #FIXME when we're clicking this might should be inverted? return task.cont @event_callback def roll(self, task): """ ALWAYS roll with respect to axis of rotation""" if self.__cr__ == None: self.__cr__ = self.cameraTarget.getR() #cross product idiot cross = self.getMouseCross(task.getName()) self.cameraTarget.setR(self.__cr__ - cross * 10) return task.cont