From bfef4fa423cff1df37eb5ed4c536519b06fc1e17 Mon Sep 17 00:00:00 2001 From: Dave Schuyler Date: Tue, 9 Dec 2003 02:22:04 +0000 Subject: [PATCH] added GravityWalker --- direct/src/showbase/ControlManager.py | 8 +- direct/src/showbase/GravityWalker.py | 391 ++++++++++++++++++++++++++ 2 files changed, 397 insertions(+), 2 deletions(-) create mode 100755 direct/src/showbase/GravityWalker.py diff --git a/direct/src/showbase/ControlManager.py b/direct/src/showbase/ControlManager.py index 34af64fd0d..650d805895 100755 --- a/direct/src/showbase/ControlManager.py +++ b/direct/src/showbase/ControlManager.py @@ -6,6 +6,7 @@ from ShowBaseGlobal import * import Avatar import DirectNotifyGlobal +import GravityWalker import NonPhysicsWalker import PhysicsWalker import Task @@ -22,8 +23,11 @@ class ControlManager: assert(self.debugPrint("ControlManager()")) self.swimControls=NonPhysicsWalker.NonPhysicsWalker() if self.wantAvatarPhysics: - self.walkControls=PhysicsWalker.PhysicsWalker( + self.walkControls=GravityWalker.GravityWalker( gravity = -32.1740 * 2.0) # * 2.0 is a hack; + #self.walkControls=NonPhysicsWalker.NonPhysicsWalker() + #self.walkControls=PhysicsWalker.PhysicsWalker( + # gravity = -32.1740 * 2.0) # * 2.0 is a hack; else: self.walkControls=NonPhysicsWalker.NonPhysicsWalker() self.currentControls = self.walkControls @@ -124,7 +128,7 @@ class ControlManager: if self.wantAvatarPhysicsIndicator: indicator=loader.loadModelCopy('phase_5/models/props/dagger') - self.walkControls.setAvatarPhysicsIndicator(indicator) + #self.walkControls.setAvatarPhysicsIndicator(indicator) def deleteCollisions(self): assert(self.debugPrint("deleteCollisions()")) diff --git a/direct/src/showbase/GravityWalker.py b/direct/src/showbase/GravityWalker.py new file mode 100755 index 0000000000..762e95b805 --- /dev/null +++ b/direct/src/showbase/GravityWalker.py @@ -0,0 +1,391 @@ +""" +GravityWalker.py is for avatars. + +A walker control such as this one provides: + - creation of the collision nodes + - handling the keyboard and mouse input for avatar movement + - moving the avatar + +it does not: + - play sounds + - play animations + +although it does send messeges that allow a listener to play sounds or +animations based on walker events. +""" + +from ShowBaseGlobal import * + +import DirectNotifyGlobal +import DirectObject +import PhysicsManager +import math + + +class GravityWalker(DirectObject.DirectObject): + + notify = DirectNotifyGlobal.directNotify.newCategory("GravityWalker") + wantAvatarPhysicsIndicator = base.config.GetBool('want-avatar-physics-indicator', 0) + + useLifter = 1 + useHeightRay = 0 + + # special methods + def __init__(self, gravity = -32.1740, standableGround=0.707, + hardLandingForce=16.0): + assert(self.debugPrint("GravityWalker(gravity=%s, standableGround=%s)"%( + gravity, standableGround))) + DirectObject.DirectObject.__init__(self) + self.__gravity=gravity + self.__standableGround=standableGround + self.__hardLandingForce=hardLandingForce + + self.jumping = 0 + self.needToDeltaPos = 0 + self.physVelocityIndicator=None + self.avatarControlForwardSpeed=0 + self.avatarControlJumpForce=0 + self.avatarControlReverseSpeed=0 + self.avatarControlRotateSpeed=0 + self.getAirborneHeight=None + self.__oldPosDelta=Vec3(0) + self.__oldDt=0 + self.speed=0.0 + self.rotationSpeed=0.0 + self.slideSpeed=0.0 + self.vel=Vec3(0.0) + self.collisionsActive = 0 + + self.isAirborne = 0 + self.highMark = 0 + + def spawnTest(self): + assert(self.debugPrint("\n\nspawnTest()\n")) + if not self.wantAvatarPhysicsIndicator: + return + from PandaModules import * + from IntervalGlobal import * + import MovingPlatform + + if hasattr(self, "platform"): + # Remove the prior instantiation: + self.moveIval.pause() + del self.moveIval + self.platform.destroy() + del self.platform + + model = loader.loadModelCopy('phase_9/models/cogHQ/platform1') + fakeId = id(self) + self.platform = MovingPlatform.MovingPlatform() + self.platform.setupCopyModel(fakeId, model, 'platformcollision') + self.platformRoot = render.attachNewNode("GravityWalker-spawnTest-%s"%fakeId) + self.platformRoot.setPos(toonbase.localToon, Vec3(0.0, 3.0, 1.0)) + self.platformRoot.setHpr(toonbase.localToon, Vec3.zero()) + self.platform.reparentTo(self.platformRoot) + + startPos = Vec3(0.0, -15.0, 0.0) + endPos = Vec3(0.0, 15.0, 0.0) + distance = Vec3(startPos-endPos).length() + duration = distance/4 + self.moveIval = Sequence( + WaitInterval(0.3), + LerpPosInterval(self.platform, duration, + endPos, startPos=startPos, + name='platformOut%s' % fakeId, + fluid = 1), + WaitInterval(0.3), + LerpPosInterval(self.platform, duration, + startPos, startPos=endPos, + name='platformBack%s' % fakeId, + fluid = 1), + name='platformIval%s' % fakeId, + ) + self.moveIval.loop() + + def setWalkSpeed(self, forward, jump, reverse, rotate): + assert(self.debugPrint("setWalkSpeed()")) + self.avatarControlForwardSpeed=forward + self.avatarControlJumpForce=jump + self.avatarControlReverseSpeed=reverse + self.avatarControlRotateSpeed=rotate + + def getSpeeds(self): + #assert(self.debugPrint("getSpeeds()")) + return (self.speed, self.rotationSpeed) + + def setupRay(self, floorBitmask, floorOffset): + # This is a ray cast from your head down to detect floor polygons + # A toon is about 4.0 feet high, so start it there + cRay = CollisionRay(0.0, 0.0, 4.0, 0.0, 0.0, -1.0) + cRayNode = CollisionNode('GW.cRayNode') + cRayNode.addSolid(cRay) + self.cRayNodePath = self.avatarNodePath.attachNewNode(cRayNode) + cRayNode.setFromCollideMask(floorBitmask) + cRayNode.setIntoCollideMask(BitMask32.allOff()) + + # set up floor collision mechanism + self.lifter = CollisionHandlerGravity() + self.lifter.setGravity(32.174 * 2.0) + self.lifter.setInPattern("enterRay-%in") + self.lifter.setOutPattern("exitRay-%in") + self.lifter.setOffset(floorOffset) + + # 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) + + def setupSphere(self, bitmask, avatarRadius): + """ + Set up the collision sphere + """ + # This is a sphere on the ground to detect barrier collisions + self.avatarRadius = avatarRadius + centerHeight = avatarRadius + if self.useHeightRay: + centerHeight *= 2.0 + self.cSphere = CollisionSphere(0.0, 0.0, centerHeight-0.01, avatarRadius) + cSphereNode = CollisionNode('GW.cSphereNode') + cSphereNode.addSolid(self.cSphere) + self.cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode) + self.cSphereBitMask = bitmask + + cSphereNode.setFromCollideMask(bitmask) + cSphereNode.setIntoCollideMask(BitMask32.allOff()) + + # set up collision mechanism + self.pusher = CollisionHandlerPusher() + self.pusher.setInPattern("enter%in") + self.pusher.setOutPattern("exit%in") + + self.pusher.addCollider(self.cSphereNodePath, self.avatarNodePath) + + def initializeCollisions(self, collisionTraverser, avatarNodePath, + wallBitmask, floorBitmask, + avatarRadius = 1.4, floorOffset = 1.0): + """ + Set up the avatar collisions + """ + assert(self.debugPrint("initializeCollisions()")) + + assert not avatarNodePath.isEmpty() + self.avatarNodePath = avatarNodePath + + self.cTrav = collisionTraverser + self.floorOffset = 0.0 + + self.setupRay(floorBitmask, self.floorOffset) + self.setupSphere(wallBitmask|floorBitmask, avatarRadius) + + self.setCollisionsActive(1) + + def setAirborneHeightFunc(self, getAirborneHeight): + self.getAirborneHeight = getAirborneHeight + + def setAvatarPhysicsIndicator(self, indicator): + """ + indicator is a NodePath + """ + assert(self.debugPrint("setAvatarPhysicsIndicator()")) + self.cSphereNodePath.show() + + def deleteCollisions(self): + assert(self.debugPrint("deleteCollisions()")) + del self.cTrav + + if self.useHeightRay: + del self.cRayQueue + self.cRayNodePath.removeNode() + del self.cRayNodePath + + del self.cSphere + self.cSphereNodePath.removeNode() + del self.cSphereNodePath + + del self.pusher + del self.lifter + + def setCollisionsActive(self, active = 1): + assert(self.debugPrint("collisionsActive(active=%s)"%(active,))) + if self.collisionsActive != active: + self.collisionsActive = active + if active: + self.cTrav.addCollider(self.cSphereNodePath, self.pusher) + self.cTrav.addCollider(self.cRayNodePath, self.lifter) + else: + self.cTrav.removeCollider(self.cSphereNodePath) + self.cTrav.removeCollider(self.cRayNodePath) + # Now that we have disabled collisions, make one more pass + # right now to ensure we aren't standing in a wall. + self.oneTimeCollide() + + def getCollisionsActive(self): + assert(self.debugPrint("getCollisionsActive() returning=%s"%( + self.collisionsActive,))) + return self.collisionsActive + + 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.debugPrint("oneTimeCollide()")) + tempCTrav = CollisionTraverser() + tempCTrav.addCollider(self.cSphereNodePath, self.pusher) + tempCTrav.addCollider(self.cRayNodePath, self.lifter) + tempCTrav.traverse(render) + + def handleAvatarControls(self, task): + """ + Check on the arrow keys and update the avatar. + """ + # get the button states: + forward = inputState.isSet("forward") + reverse = inputState.isSet("reverse") + turnLeft = inputState.isSet("turnLeft") + turnRight = inputState.isSet("turnRight") + slide = inputState.isSet("slide") + jump = inputState.isSet("jump") + pie = inputState.isSet("pie") + # Determine what the speeds are based on the buttons: + self.speed=(forward and self.avatarControlForwardSpeed or + reverse and -self.avatarControlReverseSpeed) + # Should fSlide be renamed slideButton? + self.slideSpeed=slide and ( + (turnLeft and -self.avatarControlForwardSpeed) or + (turnRight and self.avatarControlForwardSpeed)) + self.rotationSpeed=not slide and ( + (turnLeft and self.avatarControlRotateSpeed) or + (turnRight and -self.avatarControlRotateSpeed)) + + # No moving allowed while throwing a pie. + if pie: + self.speed = 0 + self.slideSpeed = 0 + self.rotationSpeed = 0 + jump = 0 + + if 0: + onScreenDebug.add("airborneHeight", self.lifter.getAirborneHeight()) #*# + onScreenDebug.add("jumping", self.jumping) #*# + onScreenDebug.add("isOnGround", self.lifter.isOnGround()) #*# + onScreenDebug.add("velocity", self.lifter.getVelocity()) #*# + onScreenDebug.add("jump", jump) #*# + if self.lifter.isOnGround(): + if self.jumping: + self.jumping = 0 + #messenger.send("jumpHardLand") + messenger.send("jumpLand") + if jump: + # ...the jump button is down and we're close + # enough to the ground to jump. + self.lifter.addVelocity(self.avatarControlJumpForce) + messenger.send("jumpStart") + self.jumping = 1 + #else: + # if self.lifter.getAirborneHeight() > 10000.0: + # assert(0) + + # Check to see if we're moving at all: + if self.speed or self.slideSpeed or self.rotationSpeed: + # How far did we move based on the amount of time elapsed? + dt=min(ClockObject.getGlobalClock().getDt(), 0.1) + distance = dt * self.speed + slideDistance = dt * self.slideSpeed + rotation = dt * self.rotationSpeed + + # Take a step in the direction of our previous heading. + self.vel=Vec3(Vec3.forward() * distance + + Vec3.right() * slideDistance) + if self.vel != Vec3.zero(): + # rotMat is the rotation matrix corresponding to + # our previous heading. + rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up()) + step=rotMat.xform(self.vel) + self.avatarNodePath.setFluidPos(Point3( + self.avatarNodePath.getPos()+step)) + self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation) + messenger.send("avatarMoving") + else: + self.vel.set(0.0, 0.0, 0.0) + return Task.cont + + def doDeltaPos(self): + assert(self.debugPrint("doDeltaPos()")) + self.needToDeltaPos = 1 + + def setPriorParentVector(self): + assert(self.debugPrint("doDeltaPos()")) + + print "self.__oldDt", self.__oldDt, "self.__oldPosDelta", self.__oldPosDelta + if __debug__: + onScreenDebug.add("__oldDt", "% 10.4f"%self.__oldDt) + onScreenDebug.add("self.__oldPosDelta", + self.__oldPosDelta.pPrintValues()) + + velocity = self.__oldPosDelta*(1/self.__oldDt)*4.0 # *4.0 is a hack + assert(self.debugPrint(" __oldPosDelta=%s"%(self.__oldPosDelta,))) + assert(self.debugPrint(" velocity=%s"%(velocity,))) + self.priorParent.setVector(Vec3(velocity)) + if __debug__: + if self.wantAvatarPhysicsIndicator: + onScreenDebug.add("velocity", velocity.pPrintValues()) + + def enableAvatarControls(self): + """ + Activate the arrow keys, etc. + """ + assert(self.debugPrint("enableAvatarControls()")) + print id(self), "GW.enableAvatarControls()" + self.setCollisionsActive(1) + + if __debug__: + self.accept("control-f3", self.spawnTest) #*# + + taskName = "AvatarControls%s"%(id(self),) + # remove any old + taskMgr.remove(taskName) + # spawn the new task + taskMgr.add(self.handleAvatarControls, taskName, 25) + if self.physVelocityIndicator: + taskMgr.add(self.avatarPhysicsIndicator, "AvatarControlsIndicator%s"%(id(self),), 35) + + def disableAvatarControls(self): + """ + Ignore the arrow keys, etc. + """ + assert(self.debugPrint("disableAvatarControls()")) + print id(self), "GW.disableAvatarControls()" + taskName = "AvatarControls%s"%(id(self),) + taskMgr.remove(taskName) + + taskName = "AvatarControlsIndicator%s"%(id(self),) + taskMgr.remove(taskName) + + self.setCollisionsActive(0) + + if __debug__: + self.ignore("control-f3") #*# + + def enableAvatarJump(self): + """ + Stop forcing the jump key to return 0's + """ + inputState.unforce("jump") + + def disableAvatarJump(self): + """ + Force the jump key to return 0's + """ + inputState.force("jump", 0) + + + if __debug__: + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + str(id(self))+' '+message)