def __start_view_transition(self, dest_view, quat): self._dest_view = dest_view lerp_interval = LerpQuatInterval(self.cam.target, .5, quat, blendType="easeInOut") self._lerp_interval = lerp_interval lerp_interval.start() self._transition_done = False Mgr.add_task(self.__transition_view, "transition_view", sort=30, uponDeath=self.__adjust_transition_hpr) Mgr.do("start_view_gizmo_transition")
def __reset_view(self, to_default=True, transition=False): """ Make the Home view the current view """ cam = self.cam lerp_view_gizmo = False if to_default: if transition and self.is_front_custom: lerp_view_gizmo = True gizmo_root = Mgr.get("view_gizmo_root") hpr = gizmo_root.get_hpr() self.__reset_front_view(transition=False, reset_roll=False) self.__reset_home_view() Mgr.update_app("active_grid_plane", self.default_grid_plane) GlobalData["render_mode"] = self.default_render_mode Mgr.update_app("render_mode") if lerp_view_gizmo: gizmo_root.set_hpr(hpr) current_quat = cam.target.get_quat() pos = self.home_pos quat = self.home_quat zoom = self.home_zoom almost_same_zoom = Vec3(0., zoom, 0.) == Vec3(0., cam.zoom, 0.) def_front_quat = self.default_front_quat if current_quat.almost_same_direction(quat, .000001) \ and cam.pivot.get_pos() == pos and almost_same_zoom: if lerp_view_gizmo: gizmo_interval = LerpQuatInterval(gizmo_root, .5, def_front_quat, blendType="easeInOut") self._lerp_interval = gizmo_interval gizmo_interval.start() Mgr.add_task(self.__transition_view, "transition_view", sort=30, uponDeath=self.__adjust_transition_hpr) Mgr.do("start_view_gizmo_transition") self._dest_view = "home" self._transition_done = False return if transition: lerp_interval = self.reset_interval if lerp_view_gizmo: gizmo_interval = LerpQuatInterval(gizmo_root, .5, def_front_quat, blendType="easeInOut") lerp_interval.append(gizmo_interval) self._lerp_interval = lerp_interval lerp_interval.start() Mgr.add_task(self.__transition_view, "transition_view", sort=30, uponDeath=self.__adjust_transition_hpr) Mgr.do("start_view_gizmo_transition") self._dest_view = "home" self._transition_done = False else: cam.pivot.set_pos(pos) cam.target.set_quat(quat) cam.zoom = zoom Mgr.do("update_transf_gizmo") Mgr.do("update_coord_sys") Mgr.do("update_zoom_indicator") Mgr.do("update_view_gizmo") if GlobalData["coord_sys_type"] == "screen": Mgr.get("selection").update_transform_values()
class CrashWalker(DirectObject.DirectObject): notify = directNotify.newCategory("CrashWalker") wantDebugIndicator = ConfigVariableBool('want-avatar-physics-indicator', False) wantFloorSphere = ConfigVariableBool('want-floor-sphere', False) earlyEventSphere = ConfigVariableBool('early-event-sphere', False) DiagonalFactor = math.sqrt(2.) / 2. # special methods def __init__(self, gravity = 64.348, standableGround=0.707, hardLandingForce=16.0, legacyLifter=False): assert self.notify.debugStateCall(self) DirectObject.DirectObject.__init__(self) self.__gravity=gravity self.__standableGround=standableGround self.__hardLandingForce=hardLandingForce self._legacyLifter = legacyLifter self.mayJump = 1 self.jumpDelayTask = None self.controlsTask = None self.indicatorTask = None self.falling = 0 self.needToDeltaPos = 0 self.physVelocityIndicator=None self.avatarControlForwardSpeed=0 self.avatarControlJumpForce=0 self.getAirborneHeight=None self.upRay = None self.upRayNodePath = None self.upEveHandl = None self.priorParent=Vec3(0) self.__oldPosDelta=Vec3(0) self.__oldDt=0 self.moving=0 self.lastSpeed = 0.0 self.lastCamNodeH = 0.0 self.speed=0.0 self.rotationSpeed=0.0 self.slideSpeed=0.0 self.vel=Vec3(0.0) self.collisionsActive = 0 self.switchDirLerp = None self.currentDirection = Direction.Null self.currOtherDir = Direction.Null self.isAirborne = 0 self.highMark = 0 def setWalkSpeed(self, forward, jump, foo = 0.0, bar = 0.0): assert self.notify.debugStateCall(self) self.avatarControlForwardSpeed=forward self.avatarControlJumpForce=jump def getSpeeds(self): #assert self.debugPrint("getSpeeds()") return (self.speed, 0.0, 0.0) def getIsAirborne(self): return self.isAirborne def setAvatar(self, avatar): self.avatar = avatar if avatar is not None: pass # setup the avatar def setupRay(self, bitmask, floorOffset, reach): assert self.notify.debugStateCall(self) # This is a ray cast from your head down to detect floor polygons. # This ray start is arbitrarily high in the air. Feel free to use # a higher or lower value depending on whether you want an avatar # that is outside of the world to step up to the floor when they # get under valid floor: self.cRay = CollisionRay(0.0, 0.0, CollisionHandlerRayStart, 0.0, 0.0, -1.0) cRayNode = CollisionNode('GW.cRayNode') cRayNode.addSolid(self.cRay) self.cRayNodePath = self.avatarNodePath.attachNewNode(cRayNode) cRayNode.setFromCollideMask(bitmask) cRayNode.setIntoCollideMask(BitMask32.allOff()) self.upRay = CollisionRay(0.0, 0.0, 1.0, 0.0, 0.0, 1.0) upRayNode = CollisionNode('GW.upRayNode') upRayNode.addSolid(self.upRay) self.upRayNodePath = self.avatarNodePath.attachNewNode(upRayNode) upRayNode.setFromCollideMask(CIGlobals.EventBitmask) upRayNode.setIntoCollideMask(BitMask32.allOff()) # set up floor collision mechanism self.lifter = CollisionHandlerGravity() #self.lifter = CollisionHandlerHighestEvent() self.lifter.setLegacyMode(self._legacyLifter) self.lifter.setGravity(self.__gravity) self.lifter.addInPattern("enter%in") self.lifter.addAgainPattern("again%in") self.lifter.addOutPattern("exit%in") self.lifter.setOffset(floorOffset) self.lifter.setReach(reach) self.upEveHandl = CollisionHandlerEvent() self.upEveHandl.setInPattern("enter_overhead_%in") self.upEveHandl.setOutPattern("exit_overhead_%in") # Limit our rate-of-fall with the lifter. # If this is too low, we actually "fall" off steep stairs # and float above them as we go down. I increased this # from 8.0 to 16.0 to prevent this #self.lifter.setMaxVelocity(16.0) self.lifter.addCollider(self.cRayNodePath, self.avatarNodePath) #self.upEveHandl.addCollider(self.upRayNodePath, self.avatarNodePath) def setupWallSphere(self, bitmask, avatarRadius): """ Set up the collision sphere """ assert self.notify.debugStateCall(self) # This is a sphere on the ground to detect collisions with # walls, but not the floor. self.avatarRadius = avatarRadius cSphere = CollisionSphere(0.0, 0.0, avatarRadius, avatarRadius) cSphereNode = CollisionNode('GW.cWallSphereNode') cSphereNode.addSolid(cSphere) cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode) cSphereNode.setFromCollideMask(bitmask) cSphereNode.setIntoCollideMask(BitMask32.allOff()) # set up collision mechanism if config.GetBool('want-fluid-pusher', 0): self.pusher = CollisionHandlerFluidPusher() else: self.pusher = CollisionHandlerPusher() self.pusher.addCollider(cSphereNodePath, self.avatarNodePath) self.cWallSphereNodePath = cSphereNodePath def setupEventSphere(self, bitmask, avatarRadius): """ Set up the collision sphere """ assert self.notify.debugStateCall(self) # This is a sphere a little larger than the wall sphere to # trigger events. self.avatarRadius = avatarRadius cSphere = CollisionSphere(0.0, 0.0, avatarRadius-0.1, avatarRadius*1.04) # Mark it intangible just to emphasize its non-physical purpose. cSphere.setTangible(0) cSphereNode = CollisionNode('GW.cEventSphereNode') cSphereNode.addSolid(cSphere) cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode) cSphereNode.setFromCollideMask(bitmask) cSphereNode.setIntoCollideMask(BitMask32.allOff()) # set up collision mechanism self.event = CollisionHandlerEvent() self.event.addInPattern("enter%in") self.event.addOutPattern("exit%in") self.cEventSphereNodePath = cSphereNodePath def setupFloorSphere(self, bitmask, avatarRadius): """ Set up the collision sphere """ assert self.notify.debugStateCall(self) # This is a tiny sphere concentric with the wallSphere to keep # us from slipping through floors. self.avatarRadius = avatarRadius cSphere = CollisionSphere(0.0, 0.0, avatarRadius, 0.01) cSphereNode = CollisionNode('GW.cFloorSphereNode') cSphereNode.addSolid(cSphere) cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode) cSphereNode.setFromCollideMask(bitmask) cSphereNode.setIntoCollideMask(BitMask32.allOff()) # set up collision mechanism self.pusherFloorhandler = CollisionHandlerPusher() self.pusherFloor.addCollider(cSphereNodePath, self.avatarNodePath) self.cFloorSphereNodePath = cSphereNodePath def setWallBitMask(self, bitMask): self.wallBitmask = bitMask def setFloorBitMask(self, bitMask): self.floorBitmask = bitMask def swapFloorBitMask(self, oldMask, newMask): self.floorBitmask = self.floorBitmask &~ oldMask self.floorBitmask |= newMask if self.cRayNodePath and not self.cRayNodePath.isEmpty(): self.cRayNodePath.node().setFromCollideMask(self.floorBitmask) def setGravity(self, gravity): self.__gravity = gravity self.lifter.setGravity(self.__gravity) def getGravity(self, gravity): return self.__gravity def initializeCollisions(self, collisionTraverser, avatarNodePath, avatarRadius = 1.4, floorOffset = 1.0, reach = 1.0): """ floorOffset is how high the avatar can reach. I.e. if the avatar walks under a ledge that is <= floorOffset above the ground (a double floor situation), the avatar will step up on to the ledge (instantly). Set up the avatar collisions """ assert self.notify.debugStateCall(self) assert not avatarNodePath.isEmpty() self.avatarNodePath = avatarNodePath self.cTrav = collisionTraverser self.setupRay(self.floorBitmask, floorOffset, reach) self.setupWallSphere(self.wallBitmask, avatarRadius) self.setupEventSphere(self.wallBitmask, avatarRadius) if self.wantFloorSphere: self.setupFloorSphere(self.floorBitmask, avatarRadius) self.setCollisionsActive(1) def setTag(self, key, value): self.cEventSphereNodePath.setTag(key, value) def setAirborneHeightFunc(self, unused_parameter): assert self.notify.debugStateCall(self) self.getAirborneHeight = self.lifter.getAirborneHeight def getAirborneHeight(self): assert self.notify.debugStateCall(self) self.lifter.getAirborneHeight() def setAvatarPhysicsIndicator(self, indicator): """ indicator is a NodePath """ assert self.notify.debugStateCall(self) self.cWallSphereNodePath.show() def deleteCollisions(self): assert self.notify.debugStateCall(self) del self.cTrav self.cWallSphereNodePath.removeNode() del self.cWallSphereNodePath if self.wantFloorSphere: self.cFloorSphereNodePath.removeNode() del self.cFloorSphereNodePath del self.pusher # del self.pusherFloor del self.event del self.lifter del self.getAirborneHeight def setCollisionsActive(self, active = 1): assert self.notify.debugStateCall(self) if self.collisionsActive != active: self.collisionsActive = active # Each time we change the collision geometry, make one # more pass to ensure we aren't standing in a wall. self.oneTimeCollide() # make sure we have a shadow traverser base.initShadowTrav() if active: if 1: # Please let skyler or drose know if this is causing a problem # This is a bit of a hack fix: self.avatarNodePath.setP(0.0) self.avatarNodePath.setR(0.0) self.cTrav.addCollider(self.cWallSphereNodePath, self.pusher) if self.wantFloorSphere: self.cTrav.addCollider(self.cFloorSphereNodePath, self.pusherFloor) # Add the lifter to the shadow traverser, which runs after # our traverser. This prevents the "fall through wall and # off ledge" bug. The problem was that we couldn't control # which collided first, the wall pusher or the lifter, if # they're in the same collision traverser. If the lifter # collided first, we'd start falling before getting pushed # back behind the wall. base.shadowTrav.addCollider(self.cRayNodePath, self.lifter) base.shadowTrav.addCollider(self.upRayNodePath, self.upEveHandl) if self.earlyEventSphere: # If we want to trigger the events at the same # time as we intersect walls (e.g. Toontown, for # backward compatibility issues), add the event # sphere to the main traverser. This allows us to # hit door triggers that are just slightly behind # the door itself. self.cTrav.addCollider(self.cEventSphereNodePath, self.event) else: # Normally, we'd rather trigger the events after # the pusher has had a chance to fix up our # position, so we never trigger things that are # behind other polygons. base.shadowTrav.addCollider(self.cEventSphereNodePath, self.event) else: if hasattr(self, 'cTrav'): self.cTrav.removeCollider(self.cWallSphereNodePath) if self.wantFloorSphere: self.cTrav.removeCollider(self.cFloorSphereNodePath) self.cTrav.removeCollider(self.cEventSphereNodePath) base.shadowTrav.removeCollider(self.cEventSphereNodePath) base.shadowTrav.removeCollider(self.cRayNodePath) base.shadowTrav.removeCollider(self.upRayNodePath) def getCollisionsActive(self): assert self.debugPrint("getCollisionsActive() returning=%s"%( self.collisionsActive,)) return self.collisionsActive def placeOnFloor(self): """ Make a reasonable effor to place the avatar on the ground. For example, this is useful when switching away from the current walker. """ assert self.notify.debugStateCall(self) self.oneTimeCollide() self.avatarNodePath.setZ(self.avatarNodePath.getZ()-self.lifter.getAirborneHeight()) def oneTimeCollide(self): """ Makes one quick collision pass for the avatar, for instance as a one-time straighten-things-up operation after collisions have been disabled. """ assert self.notify.debugStateCall(self) if not hasattr(self, 'cWallSphereNodePath'): return self.isAirborne = 0 self.mayJump = 1 tempCTrav = CollisionTraverser("oneTimeCollide") tempCTrav.addCollider(self.cWallSphereNodePath, self.pusher) if self.wantFloorSphere: tempCTrav.addCollider(self.cFloorSphereNodePath, self.event) tempCTrav.addCollider(self.cRayNodePath, self.lifter) tempCTrav.traverse(render) def setMayJump(self, task): """ This function's use is internal to this class (maybe I'll add the __ someday). Anyway, if you want to enable or disable jumping in a general way see the ControlManager (don't use this). """ assert self.notify.debugStateCall(self) self.mayJump = 1 return Task.done def startJumpDelay(self, delay): assert self.notify.debugStateCall(self) if self.jumpDelayTask: self.jumpDelayTask.remove() self.mayJump = 0 self.jumpDelayTask=taskMgr.doMethodLater( delay, self.setMayJump, "jumpDelay-%s"%id(self)) def addBlastForce(self, vector): self.lifter.addVelocity(vector.length()) def displayDebugInfo(self): """ For debug use. """ onScreenDebug.add("w controls", "CrashWalker") onScreenDebug.add("w airborneHeight", self.lifter.getAirborneHeight()) onScreenDebug.add("w falling", self.falling) onScreenDebug.add("w isOnGround", self.lifter.isOnGround()) #onScreenDebug.add("w gravity", self.lifter.getGravity()) #onScreenDebug.add("w jumpForce", self.avatarControlJumpForce) onScreenDebug.add("w contact normal", self.lifter.getContactNormal().pPrintValues()) onScreenDebug.add("w mayJump", self.mayJump) onScreenDebug.add("w impact", self.lifter.getImpactVelocity()) onScreenDebug.add("w velocity", self.lifter.getVelocity()) onScreenDebug.add("w isAirborne", self.isAirborne) onScreenDebug.add("w hasContact", self.lifter.hasContact()) def handleAvatarControls(self, task): """ Check on the arrow keys and update the avatar. """ # get the button states: run = inputState.isSet("run") forward = inputState.isSet("forward") reverse = inputState.isSet("reverse") turnLeft = inputState.isSet("turnLeft") turnRight = inputState.isSet("turnRight") jump = inputState.isSet("jump")# or bool(self.requestJump) # Check for Auto-Run #if 'localAvatar' in __builtins__: # if base.localAvatar and base.localAvatar.getAutoRun(): # forward = 1 # reverse = 0 # Determine what the speeds are based on the buttons: self.speed=0.0 if (forward and reverse): # What? self.lastSpeed = 0.0 return task.cont if (forward or reverse or turnLeft or turnRight): self.speed = self.avatarControlForwardSpeed self.slideSpeed = 0.0 self.rotationSpeed = 0.0 twodirs = False direction = Direction.Null if (forward): direction = Direction.Forward elif (reverse): direction = Direction.Reverse elif (turnLeft): direction = Direction.Left elif (turnRight): direction = Direction.Right otherdir = Direction.Null if (forward and turnLeft) or (reverse and turnLeft): otherdir = Direction.Left elif (forward and turnRight) or (reverse and turnRight): otherdir = Direction.Right if self.needToDeltaPos: self.setPriorParentVector() self.needToDeltaPos = 0 if self.wantDebugIndicator: self.displayDebugInfo() if self.lifter.isOnGround(): if self.isAirborne: self.isAirborne = 0 assert self.debugPrint("isAirborne 0 due to isOnGround() true") impact = self.lifter.getImpactVelocity() if impact < -30.0: messenger.send("jumpHardLand") self.startJumpDelay(0.3) else: messenger.send("jumpLand") if impact < -5.0: self.startJumpDelay(0.2) # else, ignore the little potholes. assert self.isAirborne == 0 self.priorParent = Vec3.zero() if jump and self.mayJump: # The jump button is down and we're close # enough to the ground to jump. self.lifter.addVelocity(self.avatarControlJumpForce) messenger.send("jumpStart") self.isAirborne = 1 assert self.debugPrint("isAirborne 1 due to jump") else: if self.isAirborne == 0: assert self.debugPrint("isAirborne 1 due to isOnGround() false") self.isAirborne = 1 self.__oldPosDelta = self.avatarNodePath.getPosDelta(render) # How far did we move based on the amount of time elapsed? self.__oldDt = ClockObject.getGlobalClock().getDt() dt=self.__oldDt close_enough = 0.1 if ((direction != self.currentDirection and direction != Direction.Null) or (otherdir != self.currOtherDir) or (abs(self.lastCamNodeH - base.minigame.camNode.getH(render)) > close_enough)): self.currentDirection = direction self.currOtherDir = otherdir self.lastCamNodeH = base.minigame.camNode.getH(render) if (self.currentDirection != Direction.Null): hdg = Direction.getHeading(direction, otherdir) currHdg = self.avatarNodePath.getH(render) % 360 dur = 0.1 if (self.switchDirLerp): self.switchDirLerp.pause() self.switchDirLerp = None self.switchDirLerp = LerpQuatInterval(self.avatarNodePath, dur, Point3(hdg, 0, 0), startHpr = Point3(currHdg, 0, 0)) self.switchDirLerp.start() if (self.switchDirLerp != None): if (self.switchDirLerp.isPlaying()): if (otherdir == Direction.Null and not self.lastSpeed): # Don't move while we're switching directions return task.cont # Check to see if we're moving at all: self.moving = self.speed or self.slideSpeed or self.rotationSpeed or (self.priorParent!=Vec3.zero()) if self.moving: distance = dt * self.speed slideDistance = dt * self.slideSpeed rotation = dt * self.rotationSpeed # Take a step in the direction of our previous heading. if distance or slideDistance or self.priorParent != Vec3.zero(): # rotMat is the rotation matrix corresponding to # our previous heading. rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up()) if self.isAirborne: forward = Vec3.forward() else: contact = self.lifter.getContactNormal() forward = contact.cross(Vec3.right()) # Consider commenting out this normalize. If you do so # then going up and down slops is a touch slower and # steeper terrain can cut the movement in half. Without # the normalize the movement is slowed by the cosine of # the slope (i.e. it is multiplied by the sign as a # side effect of the cross product above). forward.normalize() self.vel=Vec3(forward * distance) if slideDistance: if self.isAirborne: right = Vec3.right() else: right = forward.cross(contact) # See note above for forward.normalize() right.normalize() self.vel=Vec3(self.vel + (right * slideDistance)) self.vel=Vec3(rotMat.xform(self.vel)) step=self.vel + (self.priorParent * dt) self.avatarNodePath.setFluidPos(Point3( self.avatarNodePath.getPos()+step)) self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation) else: self.vel.set(0.0, 0.0, 0.0) if self.moving or jump: messenger.send("avatarMoving") self.lastSpeed = float(self.speed) return Task.cont def doDeltaPos(self): assert self.notify.debugStateCall(self) self.needToDeltaPos = 1 def setPriorParentVector(self): assert self.notify.debugStateCall(self) if __debug__: onScreenDebug.add("__oldDt", "% 10.4f"%self.__oldDt) onScreenDebug.add("self.__oldPosDelta", self.__oldPosDelta.pPrintValues()) # avoid divide by zero crash - grw if self.__oldDt == 0: velocity = 0 else: velocity = self.__oldPosDelta*(1.0/self.__oldDt) self.priorParent = Vec3(velocity) if __debug__: if self.wantDebugIndicator: onScreenDebug.add("priorParent", self.priorParent.pPrintValues()) def reset(self): assert self.notify.debugStateCall(self) self.lifter.setVelocity(0.0) self.priorParent=Vec3.zero() def getVelocity(self): return self.vel def enableAvatarControls(self): """ Activate the arrow keys, etc. """ assert self.notify.debugStateCall(self) assert self.collisionsActive #*#if __debug__: #*# self.accept("control-f3", self.spawnTest) #*# # remove any old if self.controlsTask: self.controlsTask.remove() # spawn the new task taskName = "AvatarControls-%s"%(id(self),) self.controlsTask = taskMgr.add(self.handleAvatarControls, taskName, 25) self.isAirborne = 0 self.mayJump = 1 if self.physVelocityIndicator: if self.indicatorTask: self.indicatorTask.remove() self.indicatorTask = taskMgr.add( self.avatarPhysicsIndicator, "AvatarControlsIndicator-%s"%(id(self),), 35) def disableAvatarControls(self): """ Ignore the arrow keys, etc. """ assert self.notify.debugStateCall(self) if self.controlsTask: self.controlsTask.remove() self.controlsTask = None if self.indicatorTask: self.indicatorTask.remove() self.indicatorTask = None if self.jumpDelayTask: self.jumpDelayTask.remove() self.jumpDelayTask = None if __debug__: self.ignore("control-f3") #*# def flushEventHandlers(self): if hasattr(self, 'cTrav'): self.pusher.flush() if self.wantFloorSphere: self.floorPusher.flush() self.event.flush() self.lifter.flush() # not currently defined or needed if __debug__: def debugPrint(self, message): """for debugging""" return self.notify.debug( str(id(self))+' '+message) # There are sometimes issues if the collision ray height is # so tall that it collides with multiple levels of floors. def setCollisionRayHeight(self, height): self.cRay.setOrigin(0.0, 0.0, height)