From 59606cc4ecbcfbec2531ab3006a722058e03a270 Mon Sep 17 00:00:00 2001 From: Dave Schuyler Date: Sat, 7 Aug 2004 01:07:43 +0000 Subject: [PATCH] moved from ../showbase --- direct/src/controls/ControlManager.py | 196 +++++++ direct/src/controls/DevWalker.py | 161 +++++ direct/src/controls/GhostWalker.py | 27 + direct/src/controls/GravityWalker.py | 614 ++++++++++++++++++++ direct/src/controls/InputState.py | 94 +++ direct/src/controls/NonPhysicsWalker.py | 259 +++++++++ direct/src/controls/PhysicsRoller.py | 2 + direct/src/controls/PhysicsWalker.py | 741 ++++++++++++++++++++++++ direct/src/controls/Sources.pp | 0 direct/src/controls/__init__.py | 0 10 files changed, 2094 insertions(+) create mode 100755 direct/src/controls/ControlManager.py create mode 100755 direct/src/controls/DevWalker.py create mode 100755 direct/src/controls/GhostWalker.py create mode 100755 direct/src/controls/GravityWalker.py create mode 100755 direct/src/controls/InputState.py create mode 100755 direct/src/controls/NonPhysicsWalker.py create mode 100755 direct/src/controls/PhysicsRoller.py create mode 100755 direct/src/controls/PhysicsWalker.py create mode 100644 direct/src/controls/Sources.pp create mode 100644 direct/src/controls/__init__.py diff --git a/direct/src/controls/ControlManager.py b/direct/src/controls/ControlManager.py new file mode 100755 index 0000000000..e2aaf5f29b --- /dev/null +++ b/direct/src/controls/ControlManager.py @@ -0,0 +1,196 @@ + +from direct.showbase.ShowBaseGlobal import * +#from DirectGui import * +#from PythonUtil import * +#from IntervalGlobal import * + +from otp.avatar import Avatar +from direct.directnotify import DirectNotifyGlobal +#import GhostWalker +#import GravityWalker +#import NonPhysicsWalker +#import PhysicsWalker +#if __debug__: +# import DevWalker +from direct.task import Task + + +class ControlManager: + notify = DirectNotifyGlobal.directNotify.newCategory("ControlManager") + wantAvatarPhysicsIndicator = base.config.GetBool('want-avatar-physics-indicator', 0) + wantAvatarPhysicsDebug = base.config.GetBool('want-avatar-physics-debug', 0) + + def __init__(self): + assert self.notify.debugCall(id(self)) + self.enableJumpCounter = 1 + self.controls = {} + self.currentControls = None + self.isEnabled = 1 + #self.monitorTask = taskMgr.add(self.monitor, "ControlManager-%s"%(id(self)), priority=-1) + inputState.watch("forward", "arrow_up", "arrow_up-up") + inputState.watch("forward", "control-arrow_up", "control-arrow_up-up") + inputState.watch("forward", "alt-arrow_up", "alt-arrow_up-up") + inputState.watch("forward", "shift-arrow_up", "shift-arrow_up-up") + + inputState.watch("reverse", "arrow_down", "arrow_down-up") + inputState.watch("reverse", "control-arrow_down", "control-arrow_down-up") + inputState.watch("reverse", "alt-arrow_down", "alt-arrow_down-up") + inputState.watch("reverse", "shift-arrow_down", "shift-arrow_down-up") + + inputState.watch("turnLeft", "arrow_left", "arrow_left-up") + inputState.watch("turnLeft", "control-arrow_left", "control-arrow_left-up") + inputState.watch("turnLeft", "alt-arrow_left", "alt-arrow_left-up") + inputState.watch("turnLeft", "shift-arrow_left", "shift-arrow_left-up") + inputState.watch("turnLeft", "mouse-look_left", "mouse-look_left-done") + + inputState.watch("turnRight", "arrow_right", "arrow_right-up") + inputState.watch("turnRight", "control-arrow_right", "control-arrow_right-up") + inputState.watch("turnRight", "alt-arrow_right", "alt-arrow_right-up") + inputState.watch("turnRight", "shift-arrow_right", "shift-arrow_right-up") + inputState.watch("turnRight", "mouse-look_right", "mouse-look_right-done") + + inputState.watch("jump", "control", "control-up") + inputState.watch("jump", "alt-control", "alt-control-up") + inputState.watch("jump", "shift-control", "shift-control-up") + + inputState.watch("slideLeft", "home", "home-up") + inputState.watch("slideRight", "end", "end-up") + inputState.watch("levitateUp", "page_up", "page_up-up") + inputState.watch("levitateDown", "page_down", "page_down-up") + inputState.watch("run", "shift", "shift-up") + + # FYI, ghost mode uses jump for slide. + #inputState.watch("slide", "slide-is-disabled", "slide-is-disabled") + inputState.watch("slide", "mouse3", "mouse3-up") + + #inputState.watch("slideLeft", "shift-arrow_left", "shift-arrow_left-up") + #inputState.watch("slideLeft", "control-arrow_left", "control-arrow_left-up") + #inputState.watch("slideLeft", "alt-arrow_left", "alt-arrow_left-up") + #inputState.watch("slideLeft", "shift-arrow_left", "shift-arrow_left-up") + #inputState.watch("slideLeft", "slide-is-disabled", "slide-is-disabled") + + #inputState.watch("slideRight", "shift-arrow_right", "shift-arrow_right-up") + #inputState.watch("slideRight", "control-arrow_right", "control-arrow_right-up") + #inputState.watch("slideRight", "alt-arrow_right", "alt-arrow_right-up") + #inputState.watch("slideRight", "shift-arrow_right", "shift-arrow_right-up") + #inputState.watch("slideRight", "slide-is-disabled", "slide-is-disabled") + + def add(self, controls, name="basic"): + """ + controls is an avatar control system. + name is any key that you want to use to refer to the + the controls later (e.g. using the use() call). + + Add a control instance to the list of available control systems. + + See also: use(). + """ + assert self.notify.debugCall(id(self)) + assert controls is not None + oldControls = self.controls.get(name) + if oldControls is not None: + print "Replacing controls:", name + oldControls.disableAvatarControls() + oldControls.setCollisionsActive(0) + oldControls.delete() + controls.disableAvatarControls() + controls.setCollisionsActive(0) + self.controls[name] = controls + + def use(self, name="basic"): + """ + name is a key (string) that was previously passed to add(). + + Use a previously added control system. + + See also: add(). + """ + assert self.notify.debugCall(id(self)) + controls = self.controls.get(name) + if controls is not None: + if controls is not self.currentControls: + if self.currentControls is not None: + self.currentControls.disableAvatarControls() + self.currentControls.setCollisionsActive(0) + self.currentControls = controls + self.currentControls.setCollisionsActive(1) + if self.isEnabled: + self.currentControls.enableAvatarControls() + #else: + # print "Controls are already", name + else: + print "Unkown controls:", name + + def setSpeeds(self, forwardSpeed, jumpForce, + reverseSpeed, rotateSpeed): + assert self.notify.debugCall(id(self)) + for controls in self.controls.values(): + controls.setWalkSpeed( + forwardSpeed, jumpForce, reverseSpeed, rotateSpeed) + + def delete(self): + assert self.notify.debugCall(id(self)) + self.disable() + #self.monitorTask.remove() + + def getSpeeds(self): + return self.currentControls.getSpeeds() + + def deleteCollisions(self): + assert self.notify.debugCall(id(self)) + for controls in self.controls.values(): + controls.deleteCollisions() + + def collisionsOn(self): + assert self.notify.debugCall(id(self)) + self.currentControls.setCollisionsActive(1) + + def collisionsOff(self): + assert self.notify.debugCall(id(self)) + self.currentControls.setCollisionsActive(0) + + def placeOnFloor(self): + assert self.notify.debugCall(id(self)) + self.currentControls.placeOnFloor() + + def enable(self): + assert self.notify.debugCall(id(self)) + self.isEnabled = 1 + self.currentControls.enableAvatarControls() + + def disable(self): + assert self.notify.debugCall(id(self)) + self.isEnabled = 0 + self.currentControls.disableAvatarControls() + + def enableAvatarJump(self): + """ + Stop forcing the ctrl key to return 0's + """ + assert self.notify.debugCall(id(self)) + self.enableJumpCounter+=1 + if self.enableJumpCounter: + assert self.enableJumpCounter == 1 + self.enableJumpCounter = 1 + inputState.unforce("jump") + + def disableAvatarJump(self): + """ + Force the ctrl key to return 0's + """ + assert self.notify.debugCall(id(self)) + self.enableJumpCounter-=1 + if self.enableJumpCounter <= 0: + inputState.force("jump", 0) + + def monitor(self, foo): + #assert(self.debugPrint("monitor()")) + #if 1: + # airborneHeight=self.avatar.getAirborneHeight() + # onScreenDebug.add("airborneHeight", "% 10.4f"%(airborneHeight,)) + if 0: + onScreenDebug.add("InputState forward", "%d"%(inputState.isSet("forward"))) + onScreenDebug.add("InputState reverse", "%d"%(inputState.isSet("reverse"))) + onScreenDebug.add("InputState turnLeft", "%d"%(inputState.isSet("turnLeft"))) + onScreenDebug.add("InputState turnRight", "%d"%(inputState.isSet("turnRight"))) + return Task.cont diff --git a/direct/src/controls/DevWalker.py b/direct/src/controls/DevWalker.py new file mode 100755 index 0000000000..ae3c961974 --- /dev/null +++ b/direct/src/controls/DevWalker.py @@ -0,0 +1,161 @@ +""" +DevWalker.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 direct.showbase.ShowBaseGlobal import * + +from direct.directnotify import DirectNotifyGlobal +from direct.showbase import DirectObject + +class DevWalker(DirectObject.DirectObject): + + notify = DirectNotifyGlobal.directNotify.newCategory("DevWalker") + wantDebugIndicator = base.config.GetBool('want-avatar-physics-indicator', 0) + + # Ghost mode overrides this: + slideName = "slide-is-disabled" + + # special methods + def __init__(self): + DirectObject.DirectObject.__init__(self) + self.speed=0.0 + self.rotationSpeed=0.0 + self.vel=Vec3(0.0, 0.0, 0.0) + + self.task = None + + 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 initializeCollisions(self, collisionTraverser, avatarNodePath, + wallCollideMask, floorCollideMask, + avatarRadius = 1.4, floorOffset = 1.0, reach = 1.0): + assert not avatarNodePath.isEmpty() + + self.cTrav = collisionTraverser + self.avatarNodePath = avatarNodePath + + def setAirborneHeightFunc(self, getAirborneHeight): + pass + + def deleteCollisions(self): + pass + + def setCollisionsActive(self, active = 1): + pass + + def placeOnFloor(self): + pass + + def oneTimeCollide(self): + pass + + def displayDebugInfo(self): + """ + For debug use. + """ + onScreenDebug.add("controls", "DevWalker") + + 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") + slideLeft = inputState.isSet("slideLeft") + slideRight = inputState.isSet("slideRight") + levitateUp = inputState.isSet("levitateUp") + levitateDown = inputState.isSet("levitateDown") + run = inputState.isSet("run") and 4.0 or 1.0 + # Determine what the speeds are based on the buttons: + self.speed=( + (forward and self.avatarControlForwardSpeed or + reverse and -self.avatarControlReverseSpeed)) + self.liftSpeed=( + (levitateUp and self.avatarControlForwardSpeed or + levitateDown and -self.avatarControlReverseSpeed)) + self.slideSpeed=( + (slideLeft and -self.avatarControlForwardSpeed) or + (slideRight and self.avatarControlForwardSpeed)) + self.rotationSpeed=( + (turnLeft and self.avatarControlRotateSpeed) or + (turnRight and -self.avatarControlRotateSpeed)) + + if self.wantDebugIndicator: + self.displayDebugInfo() + + # Check to see if we're moving at all: + if self.speed or self.liftSpeed or self.slideSpeed or self.rotationSpeed: + # How far did we move based on the amount of time elapsed? + dt=ClockObject.getGlobalClock().getDt() + distance = dt * self.speed * run + lift = dt * self.liftSpeed * run + slideDistance = dt * self.slideSpeed * run + rotation = dt * self.rotationSpeed + + # Take a step in the direction of our previous heading. + self.vel=Vec3(Vec3.forward() * distance + + Vec3.up() * lift + + 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 enableAvatarControls(self): + """ + Activate the arrow keys, etc. + """ + assert(self.debugPrint("enableAvatarControls")) + + if self.task: + # remove any old + self.task.remove(self.task) + # spawn the new task + self.task = taskMgr.add( + self.handleAvatarControls, "AvatarControls-dev-%s"%(id(self),)) + + def disableAvatarControls(self): + """ + Ignore the arrow keys, etc. + """ + assert(self.debugPrint("disableAvatarControls")) + if self.task: + self.task.remove() + self.task = None + + if __debug__: + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + str(id(self))+' '+message) diff --git a/direct/src/controls/GhostWalker.py b/direct/src/controls/GhostWalker.py new file mode 100755 index 0000000000..2e6151c910 --- /dev/null +++ b/direct/src/controls/GhostWalker.py @@ -0,0 +1,27 @@ +""" +GhostWalker.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 direct.showbase.ShowBaseGlobal import * + +from direct.directnotify import DirectNotifyGlobal +import NonPhysicsWalker + +class GhostWalker(NonPhysicsWalker.NonPhysicsWalker): + + notify = DirectNotifyGlobal.directNotify.newCategory("GhostWalker") + + # Ghosts slide instead of jump: + slideName = "jump" diff --git a/direct/src/controls/GravityWalker.py b/direct/src/controls/GravityWalker.py new file mode 100755 index 0000000000..f86e79806a --- /dev/null +++ b/direct/src/controls/GravityWalker.py @@ -0,0 +1,614 @@ +""" +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 direct.showbase.ShowBaseGlobal import * + +from direct.directnotify import DirectNotifyGlobal +from direct.showbase import DirectObject +from pandac import PhysicsManager +import math + + +class GravityWalker(DirectObject.DirectObject): + notify = DirectNotifyGlobal.directNotify.newCategory("GravityWalker") + wantDebugIndicator = base.config.GetBool('want-avatar-physics-indicator', 0) + wantFloorSphere = base.config.GetBool('want-floor-sphere', 0) + + # special methods + def __init__(self, gravity = -32.1740, standableGround=0.707, + hardLandingForce=16.0): + assert self.notify.debugStateCall(self) + DirectObject.DirectObject.__init__(self) + self.__gravity=gravity + self.__standableGround=standableGround + self.__hardLandingForce=hardLandingForce + + 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.avatarControlReverseSpeed=0 + self.avatarControlRotateSpeed=0 + self.getAirborneHeight=None + + self.priorParent=Vec3(0) + self.__oldPosDelta=Vec3(0) + self.__oldDt=0 + + self.moving=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 delete(self): + assert self.notify.debugStateCall(self) + if self.doLaterTask is not None: + self.doLaterTask.remove() + del self.doLaterTask + #DirectObject.DirectObject.delete(self) + + """ + def spawnTest(self): + assert self.notify.debugStateCall(self) + if not self.wantDebugIndicator: + return + from pandac.PandaModules import * + from direct.interval.IntervalGlobal import * + from toontown.coghq import MovingPlatform + + if hasattr(self, "platform"): + # Remove the prior instantiation: + self.moveIval.pause() + del self.moveIval + self.platform.destroy() + del self.platform + self.platform2.destroy() + del self.platform2 + + 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(base.localAvatar, Vec3(0.0, 0.0, 1.0)) + self.platformRoot.setHpr(base.localAvatar, Vec3.zero()) + self.platform.reparentTo(self.platformRoot) + + self.platform2 = MovingPlatform.MovingPlatform() + self.platform2.setupCopyModel(1+fakeId, model, 'platformcollision') + self.platform2Root = render.attachNewNode("GravityWalker-spawnTest2-%s"%fakeId) + self.platform2Root.setPos(base.localAvatar, Vec3(-16.0, 30.0, 1.0)) + self.platform2Root.setHpr(base.localAvatar, Vec3.zero()) + self.platform2.reparentTo(self.platform2Root) + + duration = 5 + self.moveIval = Parallel( + Sequence( + WaitInterval(0.3), + LerpPosInterval(self.platform, duration, + Vec3(0.0, 30.0, 0.0), + name='platformOut%s' % fakeId, + fluid = 1), + WaitInterval(0.3), + LerpPosInterval(self.platform, duration, + Vec3(0.0, 0.0, 0.0), + name='platformBack%s' % fakeId, + fluid = 1), + WaitInterval(0.3), + LerpPosInterval(self.platform, duration, + Vec3(0.0, 0.0, 30.0), + name='platformUp%s' % fakeId, + fluid = 1), + WaitInterval(0.3), + LerpPosInterval(self.platform, duration, + Vec3(0.0, 0.0, 0.0), + name='platformDown%s' % fakeId, + fluid = 1), + ), + Sequence( + WaitInterval(0.3), + LerpPosInterval(self.platform2, duration, + Vec3(0.0, -30.0, 0.0), + name='platform2Out%s' % fakeId, + fluid = 1), + WaitInterval(0.3), + LerpPosInterval(self.platform2, duration, + Vec3(0.0, 30.0, 30.0), + name='platform2Back%s' % fakeId, + fluid = 1), + WaitInterval(0.3), + LerpPosInterval(self.platform2, duration, + Vec3(0.0, -30.0, 0.0), + name='platform2Up%s' % fakeId, + fluid = 1), + WaitInterval(0.3), + LerpPosInterval(self.platform2, duration, + Vec3(0.0, 0.0, 0.0), + name='platformDown%s' % fakeId, + fluid = 1), + ), + name='platformIval%s' % fakeId, + ) + self.moveIval.loop() + """ + + def setWalkSpeed(self, forward, jump, reverse, rotate): + assert self.notify.debugStateCall(self) + 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, 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: + cRay = CollisionRay(0.0, 0.0, CollisionHandlerRayStart, 0.0, 0.0, -1.0) + cRayNode = CollisionNode('GW.cRayNode') + cRayNode.addSolid(cRay) + self.cRayNodePath = self.avatarNodePath.attachNewNode(cRayNode) + cRayNode.setFromCollideMask(bitmask) + cRayNode.setIntoCollideMask(BitMask32.allOff()) + + # set up floor collision mechanism + self.lifter = CollisionHandlerGravity() + self.lifter.setGravity(32.174 * 2.0) + self.lifter.addInPattern("enter%in") + self.lifter.addOutPattern("exit%in") + self.lifter.setOffset(floorOffset) + self.lifter.setReach(reach) + + # 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 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 + handler = CollisionHandlerPusher() + #handler.setInPattern("pusher_enter%in") + #handler.setOutPattern("pusher_exit%in") + + handler.addCollider(cSphereNodePath, self.avatarNodePath) + self.pusher = handler + 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 + handler = CollisionHandlerEvent() + handler.addInPattern("enter%in") + handler.addOutPattern("exit%in") + + self.event = handler + 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 + handler = CollisionHandlerPusher() + #handler.setInPattern("pusherFloor_enter%in") + #handler.setOutPattern("pusherFloor_exit%in") + + handler.addCollider(cSphereNodePath, self.avatarNodePath) + self.pusherFloor = handler + self.cFloorSphereNodePath = cSphereNodePath + + def initializeCollisions(self, collisionTraverser, avatarNodePath, + wallBitmask, floorBitmask, + 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(floorBitmask, floorOffset, reach ) + self.setupWallSphere(wallBitmask, avatarRadius) + self.setupEventSphere(wallBitmask, avatarRadius) + if self.wantFloorSphere: + self.setupFloorSphere(floorBitmask, avatarRadius) + + self.setCollisionsActive(1) + + 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() + 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) + self.cTrav.addCollider(self.cEventSphereNodePath, self.event) + self.cTrav.addCollider(self.cRayNodePath, self.lifter) + else: + self.cTrav.removeCollider(self.cWallSphereNodePath) + if self.wantFloorSphere: + self.cTrav.removeCollider(self.cFloorSphereNodePath) + self.cTrav.removeCollider(self.cEventSphereNodePath) + self.cTrav.removeCollider(self.cRayNodePath) + + 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) + 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 displayDebugInfo(self): + """ + For debug use. + """ + onScreenDebug.add("w controls", "GravityWalker") + + 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: + forward = inputState.isSet("forward") + reverse = inputState.isSet("reverse") + turnLeft = inputState.isSet("turnLeft") + turnRight = inputState.isSet("turnRight") + #slide = 0 #hack -- was: inputState.isSet("slide") + slide = inputState.isSet("slide") + jump = inputState.isSet("jump") + # 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)) + + 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 + + # 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. + self.vel=Vec3(Vec3.forward() * distance + + Vec3.right() * slideDistance) + if self.vel != Vec3.zero() or self.priorParent != Vec3.zero(): + if 1: + # rotMat is the rotation matrix corresponding to + # our previous heading. + rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up()) + step=rotMat.xform(self.vel) + (self.priorParent * dt) + self.avatarNodePath.setFluidPos(Point3( + self.avatarNodePath.getPos()+step)) + if 0: + # rotMat is the rotation matrix corresponding to + # our previous heading. + rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), self.lifter.getContactNormal()) + step=rotMat.xform(self.vel) + (self.priorParent * dt) + self.avatarNodePath.setFluidPos(Point3( + self.avatarNodePath.getPos()+step)) + if 0: + # rotMat is the rotation matrix corresponding to + # our previous heading. + rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up()) + forward = Vec3(rotMat.xform(Vec3.forward())) + up = Vec3(rotMat.xform(self.lifter.getContactNormal())) + rotMat2=Mat3() + headsUp(rotMat2, forward, up) + #rotMat2=Mat3.rotateMatNormaxis(0.0, ) + step=rotMat2.xform(self.vel) + (self.priorParent * dt) + if 1: + onScreenDebug.add("a getH()", self.avatarNodePath.getH()) + onScreenDebug.add("a forward", forward.pPrintValues()) + onScreenDebug.add("a up", up.pPrintValues()) + onScreenDebug.add("a Vec3.forward()", Vec3.forward().pPrintValues()) + onScreenDebug.add("a Vec3.up()", Vec3.up().pPrintValues()) + onScreenDebug.add("a Vec3.right()", Vec3.right().pPrintValues()) + onScreenDebug.add("a contactNormal()", self.lifter.getContactNormal().pPrintValues()) + onScreenDebug.add("a rotMat", rotMat.pPrintValues()) + onScreenDebug.add("a rotMat2", rotMat2.pPrintValues()) + 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") + 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()) + 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 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") #*# + + + if __debug__: + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + str(id(self))+' '+message) diff --git a/direct/src/controls/InputState.py b/direct/src/controls/InputState.py new file mode 100755 index 0000000000..3422954d0d --- /dev/null +++ b/direct/src/controls/InputState.py @@ -0,0 +1,94 @@ + + +from direct.directnotify import DirectNotifyGlobal +from direct.showbase import DirectObject + + +class InputState(DirectObject.DirectObject): + """ + InputState is for tracking the on/off state of some events. + The initial usage is to watch some keyboard keys so that another + task can poll the key states. By the way, in general polling is + not a good idea, but it is useful in some situations. Know when + to use it :) If in doubt, don't use this class and listen for + events instead. + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("InputState") + + def __init__(self): + self.state = {} + assert(self.debugPrint("InputState()")) + self.watching = {} + self.forcing = {} + + def delete(self): + self.ignoreAll() + + def watch(self, name, eventOn, eventOff, default = 0): + """ + name is any string (or actually any valid dictionary key). + eventOn is the string name of the Messenger event that will + set the state (set to 1). + eventOff is the string name of the Messenger event that will + clear the state (set to 0). + default is the initial value (this will be returned from + isSet() if a call is made before any eventOn or eventOff + events occur. + See Also: ignore() + """ + assert(self.debugPrint( + "watch(name=%s, eventOn=%s, eventOff=%s, default=%s)"%( + name, eventOn, eventOff, default))) + self.accept(eventOn, self.set, [name, 1]) + self.accept(eventOff, self.set, [name, 0]) + self.state[name] = default + self.watching[name] = (eventOn, eventOff) + + def force(self, name, value): + """ + Force isSet(name) to return value. + See Also: unforce() + """ + self.forcing[name] = value + + def unforce(self, name): + """ + Stop forcing a value. + See Also: force() + """ + del self.forcing[name] + + def ignore(self, name): + """ + The opposite of watch(name, ...) + See Also: watch() + """ + eventOn, eventOff = self.watching[name] + DirectObject.DirectObject.ignore(self, eventOn) + DirectObject.DirectObject.ignore(self, eventOff) + del self.watching[name] + del self.state[name] + + def set(self, name, isSet): + assert(self.debugPrint("set(name=%s, isSet=%s)"%(name, isSet))) + self.state[name] = isSet + # We change the name befor sending it because this may + # be the same name that messenger used to call InputState.set() + # this avoids running in circles: + messenger.send("InputState-%s"%(name,), [isSet]) + + def isSet(self, name): + """ + returns 0, 1, or None (if we're not tracking it at all) + """ + #assert(self.debugPrint("isSet(name=%s)"%(name))) + r = self.forcing.get(name) + if r is not None: + return r + return self.state.get(name) + + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + "%s (%s) %s"%(id(self), len(self.state), message)) diff --git a/direct/src/controls/NonPhysicsWalker.py b/direct/src/controls/NonPhysicsWalker.py new file mode 100755 index 0000000000..e8b637e8bd --- /dev/null +++ b/direct/src/controls/NonPhysicsWalker.py @@ -0,0 +1,259 @@ +""" +NonPhysicsWalker.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 direct.showbase.ShowBaseGlobal import * + +from direct.directnotify import DirectNotifyGlobal +from direct.showbase import DirectObject + +class NonPhysicsWalker(DirectObject.DirectObject): + notify = DirectNotifyGlobal.directNotify.newCategory("NonPhysicsWalker") + wantDebugIndicator = base.config.GetBool('want-avatar-physics-indicator', 0) + + # Ghost mode overrides this: + slideName = "slide-is-disabled" + + # special methods + def __init__(self): + DirectObject.DirectObject.__init__(self) + self.collisionsActive = 0 + self.speed=0.0 + self.rotationSpeed=0.0 + self.vel=Vec3(0.0, 0.0, 0.0) + self.stopThisFrame = 0 + + 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 initializeCollisions(self, collisionTraverser, avatarNodePath, + wallCollideMask, floorCollideMask, + avatarRadius = 1.4, floorOffset = 1.0, reach = 1.0): + """ + Set up the avatar for collisions + """ + assert not avatarNodePath.isEmpty() + + self.cTrav = collisionTraverser + self.avatarNodePath = avatarNodePath + + # Set up the collision sphere + # This is a sphere on the ground to detect barrier collisions + self.cSphere = CollisionSphere(0.0, 0.0, 0.0, avatarRadius) + cSphereNode = CollisionNode('NPW.cSphereNode') + cSphereNode.addSolid(self.cSphere) + self.cSphereNodePath = avatarNodePath.attachNewNode(cSphereNode) + self.cSphereBitMask = wallCollideMask + + cSphereNode.setFromCollideMask(self.cSphereBitMask) + cSphereNode.setIntoCollideMask(BitMask32.allOff()) + + # Set up the collison ray + # 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('NPW.cRayNode') + cRayNode.addSolid(self.cRay) + self.cRayNodePath = avatarNodePath.attachNewNode(cRayNode) + self.cRayBitMask = floorCollideMask + cRayNode.setFromCollideMask(self.cRayBitMask) + cRayNode.setIntoCollideMask(BitMask32.allOff()) + + # set up wall collision mechanism + self.pusher = CollisionHandlerPusher() + self.pusher.setInPattern("enter%in") + self.pusher.setOutPattern("exit%in") + + # set up floor collision mechanism + self.lifter = CollisionHandlerFloor() + self.lifter.setInPattern("on-floor") + self.lifter.setOutPattern("off-floor") + self.lifter.setOffset(floorOffset) + self.lifter.setReach(reach) + + # 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.pusher.addCollider(self.cSphereNodePath, avatarNodePath) + self.lifter.addCollider(self.cRayNodePath, avatarNodePath) + + # activate the collider with the traverser and pusher + self.setCollisionsActive(1) + + def setAirborneHeightFunc(self, getAirborneHeight): + self.getAirborneHeight = getAirborneHeight + + def deleteCollisions(self): + del self.cTrav + + del self.cSphere + self.cSphereNodePath.removeNode() + del self.cSphereNodePath + + del self.cRay + self.cRayNodePath.removeNode() + del self.cRayNodePath + + del self.pusher + del self.lifter + + def setCollisionsActive(self, active = 1): + assert(self.debugPrint("setCollisionsActive(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 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. + """ + # With these on, getAirborneHeight is not returning the correct value so + # when we open our book while swimming we pop down underneath the ground + # self.oneTimeCollide() + # self.avatarNodePath.setZ(self.avatarNodePath.getZ()-self.getAirborneHeight()) + # Since this is the non physics walker - wont they already be on the ground? + return + + 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. + """ + tempCTrav = CollisionTraverser("oneTimeCollide") + tempCTrav.addCollider(self.cSphereNodePath, self.pusher) + tempCTrav.addCollider(self.cRayNodePath, self.lifter) + tempCTrav.traverse(render) + + def displayDebugInfo(self): + """ + For debug use. + """ + onScreenDebug.add("controls", "NonPhysicsWalker") + + def handleAvatarControls(self, task): + """ + Check on the arrow keys and update the avatar. + """ + if not self.lifter.hasContact(): + # hack fix for falling through the floor: + messenger.send("walkerIsOutOfWorld", [self.avatarNodePath]) + + # get the button states: + forward = inputState.isSet("forward") + reverse = inputState.isSet("reverse") + turnLeft = inputState.isSet("turnLeft") + turnRight = inputState.isSet("turnRight") + slide = inputState.isSet(self.slideName) or 0 + #jump = inputState.isSet("jump") + # 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)) + + if self.wantDebugIndicator: + self.displayDebugInfo() + # How far did we move based on the amount of time elapsed? + dt=ClockObject.getGlobalClock().getDt() + # Check to see if we're moving at all: + if self.speed or self.slideSpeed or self.rotationSpeed: + if self.stopThisFrame: + distance = 0.0 + slideDistance = 0.0 + rotation = 0.0 + self.stopThisFrame = 0 + else: + 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()")) + + def reset(self): + assert(self.debugPrint("reset()")) + + def enableAvatarControls(self): + """ + Activate the arrow keys, etc. + """ + assert(self.debugPrint("enableAvatarControls")) + assert self.collisionsActive + + taskName = "AvatarControls-%s"%(id(self),) + # remove any old + taskMgr.remove(taskName) + # spawn the new task + taskMgr.add(self.handleAvatarControls, taskName) + + def disableAvatarControls(self): + """ + Ignore the arrow keys, etc. + """ + assert(self.debugPrint("disableAvatarControls")) + taskName = "AvatarControls-%s"%(id(self),) + taskMgr.remove(taskName) + + if __debug__: + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + str(id(self))+' '+message) diff --git a/direct/src/controls/PhysicsRoller.py b/direct/src/controls/PhysicsRoller.py new file mode 100755 index 0000000000..c0510dfc69 --- /dev/null +++ b/direct/src/controls/PhysicsRoller.py @@ -0,0 +1,2 @@ +"""PhysicsRoller is for wheels, soccer balls, billiard balls, and other things that roll.""" + diff --git a/direct/src/controls/PhysicsWalker.py b/direct/src/controls/PhysicsWalker.py new file mode 100755 index 0000000000..44fd4eca36 --- /dev/null +++ b/direct/src/controls/PhysicsWalker.py @@ -0,0 +1,741 @@ +""" +PhysicsWalker.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 direct.showbase.ShowBaseGlobal import * + +from direct.directnotify import DirectNotifyGlobal +from direct.showbase import DirectObject +from pandac import PhysicsManager +import math + +#import LineStream + +class PhysicsWalker(DirectObject.DirectObject): + + notify = DirectNotifyGlobal.directNotify.newCategory("PhysicsWalker") + wantAvatarPhysicsIndicator = base.config.GetBool('want-avatar-physics-indicator', 0) + + useLifter = 0 + useHeightRay = 0 + + # special methods + def __init__(self, gravity = -32.1740, standableGround=0.707, + hardLandingForce=16.0): + assert(self.debugPrint("PhysicsWalker(gravity=%s, standableGround=%s)"%( + gravity, standableGround))) + DirectObject.DirectObject.__init__(self) + self.__gravity=gravity + self.__standableGround=standableGround + self.__hardLandingForce=hardLandingForce + + self.needToDeltaPos = 0 + self.physVelocityIndicator=None + self.avatarControlForwardSpeed=0 + self.avatarControlJumpForce=0 + self.avatarControlReverseSpeed=0 + self.avatarControlRotateSpeed=0 + self.__oldAirborneHeight=None + self.getAirborneHeight=None + self.__oldContact=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 pandac.PandaModules import * + from direct.interval.IntervalGlobal import * + from toontown.coghq 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("physicsWalker-spawnTest-%s"%fakeId) + self.platformRoot.setPos(base.localAvatar, Vec3(0.0, 3.0, 1.0)) + self.platformRoot.setHpr(base.localAvatar, 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 + self.cRay = CollisionRay(0.0, 0.0, CollisionHandlerRayStart, 0.0, 0.0, -1.0) + cRayNode = CollisionNode('PW.cRayNode') + cRayNode.addSolid(self.cRay) + self.cRayNodePath = self.avatarNodePath.attachNewNode(cRayNode) + self.cRayBitMask = floorBitmask + cRayNode.setFromCollideMask(self.cRayBitMask) + cRayNode.setIntoCollideMask(BitMask32.allOff()) + + if 0 or self.useLifter: + # set up floor collision mechanism + self.lifter = CollisionHandlerFloor() + self.lifter.setInPattern("enter%in") + self.lifter.setOutPattern("exit%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.bobNodePath = self.avatarNodePath.attachNewNode("bob") + #self.lifter.addCollider(self.cRayNodePath, self.cRayNodePath) + self.lifter.addCollider(self.cRayNodePath, self.avatarNodePath) + else: # useCollisionHandlerQueue + self.cRayQueue = CollisionHandlerQueue() + self.cTrav.addCollider(self.cRayNodePath, self.cRayQueue) + + def determineHeight(self): + """ + returns the height of the avatar above the ground. + If there is no floor below the avatar, 0.0 is returned. + aka get airborne height. + """ + if self.useLifter: + height = self.avatarNodePath.getPos(self.cRayNodePath) + # If the shadow where not pointed strait down, we would need to + # get magnitude of the vector. Since it is strait down, we'll + # just get the z: + #spammy --> assert self.debugPrint("getAirborneHeight() returning %s"%(height.getZ(),)) + assert onScreenDebug.add("height", height.getZ()) + return height.getZ() - self.floorOffset + else: # useCollisionHandlerQueue + """ + returns the height of the avatar above the ground. + If there is no floor below the avatar, 0.0 is returned. + aka get airborne height. + """ + height = 0.0 + #*#self.cRayTrav.traverse(render) + if self.cRayQueue.getNumEntries() != 0: + # ...we have a floor. + # Choose the highest of the possibly several floors we're over: + self.cRayQueue.sortEntries() + floorPoint = self.cRayQueue.getEntry(0).getFromIntersectionPoint() + height = -floorPoint.getZ() + self.cRayQueue.clearEntries() + if __debug__: + onScreenDebug.add("height", height) + return height + + 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, avatarRadius) + cSphereNode = CollisionNode('PW.cSphereNode') + cSphereNode.addSolid(self.cSphere) + self.cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode) + self.cSphereBitMask = bitmask + + cSphereNode.setFromCollideMask(self.cSphereBitMask) + cSphereNode.setIntoCollideMask(BitMask32.allOff()) + + # set up collision mechanism + self.pusher = PhysicsCollisionHandler() + self.pusher.setInPattern("enter%in") + self.pusher.setOutPattern("exit%in") + + self.pusher.addCollider(self.cSphereNodePath, self.avatarNodePath) + + def setupPhysics(self, avatarNodePath): + assert(self.debugPrint("setupPhysics()")) + # Connect to Physics Manager: + self.actorNode=ActorNode("physicsActor") + self.actorNode.getPhysicsObject().setOriented(1) + self.actorNode.getPhysical(0).setViscosity(0.1) + physicsActor=NodePath(self.actorNode) + avatarNodePath.reparentTo(physicsActor) + avatarNodePath.assign(physicsActor) + self.phys=PhysicsManager.PhysicsManager() + + fn=ForceNode("gravity") + fnp=NodePath(fn) + #fnp.reparentTo(physicsActor) + fnp.reparentTo(render) + gravity=LinearVectorForce(0.0, 0.0, self.__gravity) + fn.addForce(gravity) + self.phys.addLinearForce(gravity) + self.gravity = gravity + + fn=ForceNode("priorParent") + fnp=NodePath(fn) + fnp.reparentTo(render) + priorParent=LinearVectorForce(0.0, 0.0, 0.0) + fn.addForce(priorParent) + self.phys.addLinearForce(priorParent) + self.priorParentNp = fnp + self.priorParent = priorParent + + fn=ForceNode("viscosity") + fnp=NodePath(fn) + #fnp.reparentTo(physicsActor) + fnp.reparentTo(render) + self.avatarViscosity=LinearFrictionForce(0.0, 1.0, 0) + #self.avatarViscosity.setCoef(0.9) + fn.addForce(self.avatarViscosity) + self.phys.addLinearForce(self.avatarViscosity) + + self.phys.attachLinearIntegrator(LinearEulerIntegrator()) + self.phys.attachPhysicalnode(physicsActor.node()) + + self.acForce=LinearVectorForce(0.0, 0.0, 0.0) + fn=ForceNode("avatarControls") + fnp=NodePath(fn) + fnp.reparentTo(render) + fn.addForce(self.acForce) + self.phys.addLinearForce(self.acForce) + #self.phys.removeLinearForce(self.acForce) + #fnp.remove() + return avatarNodePath + + def initializeCollisions(self, collisionTraverser, avatarNodePath, + wallBitmask, floorBitmask, + avatarRadius = 1.4, floorOffset = 1.0, reach = 1.0): + """ + Set up the avatar collisions + """ + assert(self.debugPrint("initializeCollisions()")) + + assert not avatarNodePath.isEmpty() + + self.cTrav = collisionTraverser + self.floorOffset = floorOffset = 7.0 + + self.avatarNodePath = self.setupPhysics(avatarNodePath) + if 0 or self.useHeightRay: + #self.setupRay(floorBitmask, avatarRadius) + self.setupRay(floorBitmask, 0.0) + 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() + if indicator: + # Indicator Node: + change=render.attachNewNode("change") + #change.setPos(Vec3(1.0, 1.0, 1.0)) + #change.setHpr(0.0, 0.0, 0.0) + change.setScale(0.1) + #change.setColor(Vec4(1.0, 1.0, 1.0, 1.0)) + indicator.reparentTo(change) + + indicatorNode=render.attachNewNode("physVelocityIndicator") + #indicatorNode.setScale(0.1) + #indicatorNode.setP(90.0) + indicatorNode.setPos(self.avatarNodePath, 0.0, 0.0, 6.0) + indicatorNode.setColor(0.0, 0.0, 1.0, 1.0) + change.reparentTo(indicatorNode) + + self.physVelocityIndicator=indicatorNode + # Contact Node: + contactIndicatorNode=render.attachNewNode("physContactIndicator") + contactIndicatorNode.setScale(0.25) + contactIndicatorNode.setP(90.0) + contactIndicatorNode.setPos(self.avatarNodePath, 0.0, 0.0, 5.0) + contactIndicatorNode.setColor(1.0, 0.0, 0.0, 1.0) + indicator.instanceTo(contactIndicatorNode) + self.physContactIndicator=contactIndicatorNode + else: + print "failed load of physics indicator" + + def avatarPhysicsIndicator(self, task): + #assert(self.debugPrint("avatarPhysicsIndicator()")) + # Velocity: + self.physVelocityIndicator.setPos(self.avatarNodePath, 0.0, 0.0, 6.0) + physObject=self.actorNode.getPhysicsObject() + a=physObject.getVelocity() + self.physVelocityIndicator.setScale(math.sqrt(a.length())) + a+=self.physVelocityIndicator.getPos() + self.physVelocityIndicator.lookAt(Point3(a)) + # Contact: + contact=self.actorNode.getContactVector() + if contact==Vec3.zero(): + self.physContactIndicator.hide() + else: + self.physContactIndicator.show() + self.physContactIndicator.setPos(self.avatarNodePath, 0.0, 0.0, 5.0) + #contact=self.actorNode.getContactVector() + point=Point3(contact+self.physContactIndicator.getPos()) + self.physContactIndicator.lookAt(point) + return Task.cont + + 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.getAirborneHeight + + 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) + if self.useHeightRay: + if self.useLifter: + self.cTrav.addCollider(self.cRayNodePath, self.lifter) + else: + self.cTrav.addCollider(self.cRayNodePath, self.cRayQueue) + else: + self.cTrav.removeCollider(self.cSphereNodePath) + if self.useHeightRay: + 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 placeOnFloor(self): + """ + Make a reasonable effort to place the avatar on the ground. + For example, this is useful when switching away from the + current walker. + """ + self.oneTimeCollide() + self.avatarNodePath.setZ(self.avatarNodePath.getZ()-self.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.debugPrint("oneTimeCollide()")) + tempCTrav = CollisionTraverser("oneTimeCollide") + if self.useHeightRay: + if self.useLifter: + tempCTrav.addCollider(self.cRayNodePath, self.lifter) + else: + tempCTrav.addCollider(self.cRayNodePath, self.cRayQueue) + tempCTrav.traverse(render) + + def handleAvatarControls(self, task): + """ + Check on the arrow keys and update the avatar. + """ + if __debug__: + if self.wantAvatarPhysicsIndicator: + onScreenDebug.append("localAvatar pos = %s\n"%(base.localAvatar.getPos().pPrintValues(),)) + onScreenDebug.append("localAvatar h = % 10.4f\n"%(base.localAvatar.getH(),)) + onScreenDebug.append("localAvatar anim = %s\n"%(base.localAvatar.animFSM.getCurrentState().getName(),)) + #assert(self.debugPrint("handleAvatarControls(task=%s)"%(task,))) + physObject=self.actorNode.getPhysicsObject() + #rotAvatarToPhys=Mat3.rotateMatNormaxis(-self.avatarNodePath.getH(), Vec3.up()) + #rotPhysToAvatar=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up()) + contact=self.actorNode.getContactVector() + + # hack fix for falling through the floor: + if contact==Vec3.zero() and self.avatarNodePath.getZ()<-50.0: + # DCR: don't reset X and Y; allow player to move + self.reset() + self.avatarNodePath.setZ(50.0) + messenger.send("walkerIsOutOfWorld", [self.avatarNodePath]) + + # get the button states: + forward = inputState.isSet("forward") + reverse = inputState.isSet("reverse") + turnLeft = inputState.isSet("turnLeft") + turnRight = inputState.isSet("turnRight") + slide = 0#inputState.isSet("slide") + slideLeft = 0#inputState.isSet("slideLeft") + slideRight = 0#inputState.isSet("slideRight") + jump = inputState.isSet("jump") + # Determine what the speeds are based on the buttons: + self.__speed=(forward and self.avatarControlForwardSpeed or + reverse and -self.avatarControlReverseSpeed) + avatarSlideSpeed=self.avatarControlForwardSpeed*0.5 + #self.__slideSpeed=slide and ( + # (turnLeft and -avatarSlideSpeed) or + # (turnRight and avatarSlideSpeed)) + self.__slideSpeed=( + (slideLeft and -avatarSlideSpeed) or + (slideRight and avatarSlideSpeed)) + self.__rotationSpeed=not slide and ( + (turnLeft and self.avatarControlRotateSpeed) or + (turnRight and -self.avatarControlRotateSpeed)) + + # How far did we move based on the amount of time elapsed? + dt=ClockObject.getGlobalClock().getDt() + + if self.needToDeltaPos: + self.setPriorParentVector() + self.needToDeltaPos = 0 + #self.__oldPosDelta = render.getRelativeVector( + # self.avatarNodePath, + # self.avatarNodePath.getPosDelta(render)) + #self.__oldPosDelta = self.avatarNodePath.getRelativeVector( + # render, + # self.avatarNodePath.getPosDelta(render)) + self.__oldPosDelta = self.avatarNodePath.getPosDelta(render) + self.__oldDt = dt + #posDelta = self.avatarNodePath.getPosDelta(render) + #if posDelta==Vec3.zero(): + # self.priorParent.setVector(self.__oldPosDelta) + #else: + # self.priorParent.setVector(Vec3.zero()) + # # We must copy the vector to preserve it: + # self.__oldPosDelta=Vec3(posDelta) + if __debug__: + if self.wantAvatarPhysicsIndicator: + onScreenDebug.add("posDelta1", + self.avatarNodePath.getPosDelta(render).pPrintValues()) + + if 0: + onScreenDebug.add("posDelta3", + render.getRelativeVector( + self.avatarNodePath, + self.avatarNodePath.getPosDelta(render)).pPrintValues()) + + if 0: + onScreenDebug.add("gravity", + self.gravity.getLocalVector().pPrintValues()) + onScreenDebug.add("priorParent", + self.priorParent.getLocalVector().pPrintValues()) + onScreenDebug.add("avatarViscosity", + "% 10.4f"%(self.avatarViscosity.getCoef(),)) + + onScreenDebug.add("physObject pos", + physObject.getPosition().pPrintValues()) + onScreenDebug.add("physObject hpr", + physObject.getOrientation().getHpr().pPrintValues()) + onScreenDebug.add("physObject orien", + physObject.getOrientation().pPrintValues()) + + if 1: + onScreenDebug.add("physObject vel", + physObject.getVelocity().pPrintValues()) + onScreenDebug.add("physObject len", + "% 10.4f"%physObject.getVelocity().length()) + + if 0: + onScreenDebug.add("posDelta4", + self.priorParentNp.getRelativeVector( + render, + self.avatarNodePath.getPosDelta(render)).pPrintValues()) + + if 1: + onScreenDebug.add("priorParent", + self.priorParent.getLocalVector().pPrintValues()) + + if 0: + onScreenDebug.add("priorParent po", + self.priorParent.getVector(physObject).pPrintValues()) + + if 0: + onScreenDebug.add("__posDelta", + self.__oldPosDelta.pPrintValues()) + + if 1: + onScreenDebug.add("contact", + contact.pPrintValues()) + #onScreenDebug.add("airborneHeight", "% 10.4f"%( + # self.getAirborneHeight(),)) + + if 0: + onScreenDebug.add("__oldContact", + contact.pPrintValues()) + onScreenDebug.add("__oldAirborneHeight", "% 10.4f"%( + self.getAirborneHeight(),)) + airborneHeight=self.getAirborneHeight() + if airborneHeight > self.highMark: + self.highMark = airborneHeight + if __debug__: + onScreenDebug.add("highMark", "% 10.4f"%(self.highMark,)) + #if airborneHeight < 0.1: #contact!=Vec3.zero(): + if 1: + if (airborneHeight > self.avatarRadius*0.5 + or physObject.getVelocity().getZ() > 0.0 + ): # Check stair angles before changing this. + # ...the avatar is airborne (maybe a lot or a tiny amount). + self.isAirborne = 1 + else: + # ...the avatar is very close to the ground (close enough to be + # considered on the ground). + if self.isAirborne and physObject.getVelocity().getZ() <= 0.0: + # ...the avatar has landed. + contactLength = contact.length() + if contactLength>self.__hardLandingForce: + #print "jumpHardLand" + messenger.send("jumpHardLand") + else: + #print "jumpLand" + messenger.send("jumpLand") + self.priorParent.setVector(Vec3.zero()) + self.isAirborne = 0 + elif jump: + #print "jump" + #self.__jumpButton=0 + messenger.send("jumpStart") + if 0: + # ...jump away from walls and with with the slope normal. + jumpVec=Vec3(contact+Vec3.up()) + #jumpVec=Vec3(rotAvatarToPhys.xform(jumpVec)) + jumpVec.normalize() + else: + # ...jump straight up, even if next to a wall. + jumpVec=Vec3.up() + jumpVec*=self.avatarControlJumpForce + physObject.addImpulse(Vec3(jumpVec)) + self.isAirborne = 1 # Avoid double impulse before fully airborne. + else: + self.isAirborne = 0 + if __debug__: + onScreenDebug.add("isAirborne", "%d"%(self.isAirborne,)) + else: + if contact!=Vec3.zero(): + # ...the avatar has touched something (but might not be on the ground). + contactLength = contact.length() + contact.normalize() + angle=contact.dot(Vec3.up()) + if angle>self.__standableGround: + # ...avatar is on standable ground. + if self.__oldContact==Vec3.zero(): + #if self.__oldAirborneHeight > 0.1: #self.__oldContact==Vec3.zero(): + # ...avatar was airborne. + self.jumpCount-=1 + if contactLength>self.__hardLandingForce: + messenger.send("jumpHardLand") + else: + messenger.send("jumpLand") + elif jump: + self.jumpCount+=1 + #self.__jumpButton=0 + messenger.send("jumpStart") + jump=Vec3(contact+Vec3.up()) + #jump=Vec3(rotAvatarToPhys.xform(jump)) + jump.normalize() + jump*=self.avatarControlJumpForce + physObject.addImpulse(Vec3(jump)) + + if contact!=self.__oldContact: + # We must copy the vector to preserve it: + self.__oldContact=Vec3(contact) + self.__oldAirborneHeight=airborneHeight + + moveToGround = Vec3.zero() + if not self.useHeightRay or self.isAirborne: + # ...the airborne check is a hack to stop sliding. + self.phys.doPhysics(dt) + if __debug__: + onScreenDebug.add("phys", "on") + else: + physObject.setVelocity(Vec3.zero()) + #if airborneHeight>0.001 and contact==Vec3.zero(): + # moveToGround = Vec3(0.0, 0.0, -airborneHeight) + #moveToGround = Vec3(0.0, 0.0, -airborneHeight) + moveToGround = Vec3(0.0, 0.0, -self.determineHeight()) + if __debug__: + onScreenDebug.add("phys", "off") + # Check to see if we're moving at all: + if self.__speed or self.__slideSpeed or self.__rotationSpeed or moveToGround!=Vec3.zero(): + distance = dt * self.__speed + slideDistance = dt * self.__slideSpeed + rotation = dt * self.__rotationSpeed + + #debugTempH=self.avatarNodePath.getH() + assert self.avatarNodePath.getQuat().isSameDirection(physObject.getOrientation()) + assert self.avatarNodePath.getPos().almostEqual(physObject.getPosition(), 0.0001) + + # update pos: + # Take a step in the direction of our previous heading. + self.__vel=Vec3( + Vec3.forward() * distance + + Vec3.right() * slideDistance) + + # rotMat is the rotation matrix corresponding to + # our previous heading. + rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up()) + step=rotMat.xform(self.__vel) + physObject.setPosition(Point3( + physObject.getPosition()+step+moveToGround)) + + # update hpr: + o=physObject.getOrientation() + r=LRotationf() + r.setHpr(Vec3(rotation, 0.0, 0.0)) + physObject.setOrientation(o*r) + + # sync the change: + self.actorNode.updateTransform() + + assert self.avatarNodePath.getQuat().isSameDirection(physObject.getOrientation()) + assert self.avatarNodePath.getPos().almostEqual(physObject.getPosition(), 0.0001) + #assert self.avatarNodePath.getH()==debugTempH-rotation + messenger.send("avatarMoving") + else: + self.__vel.set(0.0, 0.0, 0.0) + # Clear the contact vector so we can tell if we contact something next frame: + self.actorNode.setContactVector(Vec3.zero()) + 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 reset(self): + assert(self.debugPrint("reset()")) + self.actorNode.getPhysicsObject().resetPosition(self.avatarNodePath.getPos()) + self.priorParent.setVector(Vec3.zero()) + self.highMark = 0 + self.actorNode.setContactVector(Vec3.zero()) + if __debug__: + contact=self.actorNode.getContactVector() + onScreenDebug.add("priorParent po", + self.priorParent.getVector(self.actorNode.getPhysicsObject()).pPrintValues()) + onScreenDebug.add("highMark", "% 10.4f"%(self.highMark,)) + onScreenDebug.add("contact", contact.pPrintValues()) + + def enableAvatarControls(self): + """ + Activate the arrow keys, etc. + """ + assert(self.debugPrint("enableAvatarControls()")) + assert self.collisionsActive + + if __debug__: + #self.accept("control-f3", self.spawnTest) #*# + self.accept("f3", self.reset) # for debugging only. + + 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()")) + taskName = "AvatarControls-%s"%(id(self),) + taskMgr.remove(taskName) + + taskName = "AvatarControlsIndicator%s"%(id(self),) + taskMgr.remove(taskName) + + if __debug__: + self.ignore("control-f3") #*# + self.ignore("f3") + + + if __debug__: + def setupAvatarPhysicsIndicator(self): + if self.wantAvatarPhysicsIndicator: + indicator=loader.loadModelCopy('phase_5/models/props/dagger') + #self.walkControls.setAvatarPhysicsIndicator(indicator) + + def debugPrint(self, message): + """for debugging""" + return self.notify.debug( + str(id(self))+' '+message) diff --git a/direct/src/controls/Sources.pp b/direct/src/controls/Sources.pp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/direct/src/controls/__init__.py b/direct/src/controls/__init__.py new file mode 100644 index 0000000000..e69de29bb2