# -*- test-case-name: mv3d.test.phys.test_biped -*- # Copyright (C) 2007-2012 Mortal Coil Games # See LICENSE for details. """ Biped related functionality """ from time import time from mv3d.phys.body import BodyWithColliders, IVisualObjectView, StringFlinger from mv3d.util.math3d import Vector, matrixFromEuler, Quaternion from mv3d.net.pb import Cacheable, withClientUpdate from mv3d.net.security import Securable from mv3d.util.classgen import ClassGenerator from mv3d.util.persist import Float, FloatVector, AttributeProperty, \ IDTuple, UntypedReference, List from twisted.internet.defer import Deferred, gatherResults from mv3d.util.profiler import timed from mv3d.phys.iphys import IBody, ICylinderCollider, IBallJoint class BipedBody(BodyWithColliders, Cacheable, Securable): """ A biped body is a combination of two bodies. One has a physical form (the body) and the other doens't (the head) """ _schemaVersion = 3 setCopyableState = Cacheable.setCopyableState # TODO: We probably don't have to save half of this stuff. height = Float(default=7, transmit=True) climbheight = Float(default=1, transmit=True) radius = Float(default=2.5, transmit=True) bodyoffset = Float(default=0)# TODO: don't save me headoffset = Float(default=0)# TODO: don't save me bodylength = Float(default=0)# TODO: don't save me maxwalkspeed = Float(default=25, transmit=True) maxturnspeed = Float(default=2.0, transmit=True) jumpcounter = Float(default=0) # TODO: don't save me gradualOffset = Float(default=0) # TODO: don't save me objectid = IDTuple(autoSave=True, partialSave=True, transmit=True) areaid = IDTuple(autoSave=True, partialSave=True, transmit=True) # more persist attribs at the bottom of the class vobs = List(UntypedReference(), transmit=True) enabled = False onground = False standing = False walking = False turning = False stopturning = False walkdir = (0, 0, 0) turndir = (0, 0, 0) lastPosition = None parent = None gradualUpdate = None lastIter = None lastpos = None body = None head = None headjoint = None world = None bodycollider = None colliderspace = None space = None area = None teleported = False otherspacespaces = None onOtherSpaceGround = False def __init__(self): BodyWithColliders.__init__(self) Cacheable.__init__(self) Securable.__init__(self) self.otherspacecolliders = {} self.otherspacecolliderspaces = {} self.otherspacespaces = {} self.vobs = [] self.aggregators = [] self.ysmooth = [] self.lastTime = time() @classmethod def upgrade(cls, oldData): oldVersion = oldData["_schemaVersion"] if oldVersion > 2: raise ValueError("Old version is %d, which is > 1" % oldVersion) newData = dict() newData.update(oldData) if oldVersion < 2: if newData.has_key("enabled"): del newData["enabled"] if newData.has_key("onground"): del newData["onground"] if newData.has_key("standing"): del newData["standing"] if newData.has_key("walking"): del newData["walking"] if newData.has_key("turning"): del newData["turning"] if newData.has_key("stopturning"): del newData["stopturning"] if newData.has_key("walkdir"): del newData["walkdir"] if newData.has_key("turndir"): del newData["turndir"] if oldVersion < 3: newData["objectid"] = None return newData @withClientUpdate def setHeight(self, h): """ Set the height """ self.height = h self.queueSave(selectAttributes=["height"]) @withClientUpdate def setClimbHeight(self, h): """ Set the climbing height """ self.climbheight = h self.queueSave(selectAttributes=["climbheight"]) @withClientUpdate def setRadius(self, r): """ Set the radius of the body """ self.radius = r self.queueSave(selectAttributes=["radius"]) observe_setHeight = setHeight observe_setClimbHeight = setClimbHeight observe_setRadius = setRadius def onCached(self): """ Called when we are cached. """ self.jumpcounter = 0 self.gradualOffset = 0 def create(self, world): """ Create this BipedBody """ assert self.body is None, "Body is not none %r" % self.body self.bodylength = self.height - self.climbheight - (2 * self.radius) self.bodyoffset = self.height - (self.bodylength / 2) self.headoffset = self.height - self.radius self.body = world.getPhysicsClass(IBody)(world) self.body.mvbody = self self.world = world self.head = world.getPhysicsClass(IBody)(world) self.headjoint = world.getPhysicsClass(IBallJoint)(world.world) self.body.setPosition((0, self.bodyoffset, 0)) self.head.setPosition((0, self.headoffset, 0)) self.headjoint.attach(self.body.body, self.head.body) self.headjoint.setAnchor((0, self.headoffset, 0)) self.bodycollider = world.getPhysicsClass(ICylinderCollider)( length=self.bodylength, radius=self.radius, rotation=matrixFromEuler((3.1415 / 2, 0, 0))) if self.objectid is not None: self.bodycollider.oid = self.objectid or self.oid if self.initial_position is not None: self.body.setPosition(self.initial_position) self.head.setPosition(Vector(0, self.headoffset - self.bodyoffset, 0) + self.initial_position) # self.body.setPosition((0,0,0)) if self.initial_rotation is not None: self.head.setOrientation(self.initial_rotation) if self.initial_linearVel is not None: self.body.setLinearVelocity(self.initial_linearVel) if self.initial_angularVel is not None: self.head.setAngularVelocity(self.initial_angularVel) if not self.enabled: self.body.disable() self.head.disable() def destroy(self): """ Destroy this body """ self.bodycollider.destroy() self.headjoint.attach(None, None) self.headjoint = None self.head = None self.body = None for v in self.vobs: v.destroy() def onLoad(self): """ Called when loaded from a datastore """ self.aggregators = [] self.ysmooth = [] self.lastTime = time() self.jumpcounter = 0 self.gradualOffset = 0 self.otherspacecolliders = {} self.otherspacespaces = {} def checkOnGround(self): """ Returns true if the biped is on a solid surface. """ p = self.body.getPosition() self.onground = False bodo = self.bodyoffset#(self.height / 2.0) + self.radius# +0.2 HERE extracheck = 0.5 def rayCheck(offs=(0, 0, 0), checkspaces=None): """ Shoot a ray for feet """ checkspaces = checkspaces or [] checkspaces.append(self.area.getDisabledSpace()) checkspaces.append(self.area.getEnabledSpace()) d = None mindepth = 9999999 p1 = Vector(p) + Vector(offs) #+ Vector(0,2,0) p2 = (p1[0], p1[1] - (bodo + extracheck), p1[2]) for space in checkspaces: r = space.castRay(p1, p2, [self.bodycollider]) if r.mindepth < mindepth: mindepth = r.mindepth d = bodo - r.mindepth if d is not None and self.jumpcounter == 0: if d - extracheck > 0 or self.bodycollider.tgeom.waterDepth == 0: self.onground = True return d for offs in ((0, 0, 0), (0.3, 0, 0.3), (0.1, 0, 0.1)): d = rayCheck(offs) if d is not None: break else: self.jumpcounter = 0 return self.onground @timed def standUp(self, checkspaces=None, resetJump=True, primarySpace=True): """ Make the body float above the ground to simulate feet """ self.lastpos = self.body.getPosition() p = self.body.getPosition() lv = self.body.getLinearVelocity() if primarySpace: self.onground = False else: self.onOtherSpaceGround = False bodo = self.bodyoffset#(self.height / 2.0) + self.radius# +0.2 HERE checkspaces = checkspaces or [self.area.getDisabledSpace(), self.area.getEnabledSpace()] f = 0 extracheck = 0.5 def rayCheck(offs=(0, 0, 0)): """ Shoot a ray for feet """ d = None geom = None pos = None mindepth = 9999999 p1 = Vector(p) + Vector(offs) #+ Vector(0,2,0) p2 = (p1[0], p1[1] - (bodo + extracheck), p1[2]) for space in checkspaces: r = space.castRay(p1, p2, [self.bodycollider]) if r.mindepth < mindepth: mindepth = r.mindepth d = bodo - r.mindepth geom = r.mingeom pos = r.minpos if d is not None and self.jumpcounter == 0: # if d - extracheck > 0.1 or lv[1] > 500 * self.body.getMass().mass: # f = (((d - extracheck) - lv[1]) * self.body.getMass().mass) / 0.004#* 250 # # self.body.addForce((0, f, 0)) # print "i never get here", f # else: # uncomment these two lines to have no knees if d - extracheck > 0 or self.bodycollider.tgeom.waterDepth == 0: # tpos = p[1] + (d - extracheck) # delta = tpos - self.body.getPosition()[1] # desv = delta * self.body.getMass().mass #* max(2.0, abs(delta)) # dtm = time() - self.lastTime # if dtm > 0.0: # print "desv %.2f lv %.2f py %.2f dtm %.2f delt %.2f" % ( # desv, # self.body.getLinearVelocity()[1], # self.body.getPosition()[1], dtm, delta) # self.body.addForce((0, (desv - # self.body.getLinearVelocity()[1]) / 0.05, 0)) # self.lastTime = time() # self.ysmooth.append(p[1] + (d - extracheck)) # if len(self.ysmooth) == 10: # self.ysmooth = self.ysmooth[1:] # newY = sum(self.ysmooth) / float(len(self.ysmooth)) # print newY self.body.setPosition((p[0], p[1] + (d - extracheck), p[2])) self.body.setLinearVelocity((lv[0], 0, lv[2])) bod = geom.getBody() if bod is None and hasattr(geom, "body"): bod = geom.body if bod is not None: # apply force to it # f = (((d - extracheck) - lv[1]) * self.body.getMass().mass) * 0.5 force = self.body.getMass().mass * (lv[1] + 1) * 0.05 bod.addForceAtPos((0, force, 0), pos) bod.enable() # should also check for rotation if primarySpace: self.onground = True else: self.onOtherSpaceGround = True return d, geom, pos for offs in ((0, 0, 0), (0.3, 0, 0.3), (0.1, 0, 0.1)): d, _geom, _pos = rayCheck(offs) if d is not None: break else: if resetJump: self.jumpcounter = 0 self.body.setOrientation((1, 0, 0, 0)) self.body.setAngularVelocity((0, 0, 0)) # lvel=self.body.getLinearVelocity() # avel=self.head.getAngularVelocity() # slv=abs(lvel[0])+abs(lvel[2])+abs(dp) # sav=abs(avel[0])+abs(avel[1])+abs(avel[2]) # if (slv<0.01 and sav==0 and self.onground # and not self.walking and not self.turning and dp<=0): # if self.enabled: # self.disable() # print "IS THIS" return f def isOnGround(self): """ Returns true if the biped is standing on the groun. """ return self.onground or self.onOtherSpaceGround # @withClientUpdate def standStill(self): """ Attempt to retard the lateral movement of the biped as if it was trying with its feet to stand still. Note: This function starts this behavior and will stop all walking behavior. """ self.standing = True self.walking = False self.walkdir = (0, 0, 0) @timed @withClientUpdate def walk(self, d): """ Start walking in the direction specified by d. This function only starts the behavior. """ # if self.getPriority() != 1: # m = self.getManipulator() # if m.broker.disconnected: # return # m.callRemote("Manipulate", "walk", d) # return self.enable() self.walking = True self.standing = False wd = d wd0, wd1, wd2 = wd if wd0 == 0: wd0 = 1 if wd1 == 0: wd1 = 1 if wd2 == 0: wd2 = 1 self.walkdir = (wd[0] / abs(wd0), wd[1] / abs(wd1), wd[2] / abs(wd2)) if self.walkdir == (0, 0, 0): self.standStill() return @timed @withClientUpdate def turn(self, d): """ Start turning in the direction specified by d. This starts the behavior. """ self.enable() nd = [] for x in range(len(d)): if d[x] != 0: nd.append(d[x] / abs(d[x])) else: nd.append(d[x]) d = tuple(nd) self.turndir = Vector(d) * 50 self.turning = True self.stopturning = False self.lastturn = time() @withClientUpdate def jump(self): """ Apply a force sufficient to make the biped jump into the air. """ # if self.getPriority() != 1: # m = self.getManipulator() # m.callRemote("Manipulate", "jump") # return # if not self.isOnGround(): # return if self.enabled or not self.body.isEnabled(): self.enable() self.body.addForce((0, 200 * self.body.getMass().mass, 0)) self.jumpcounter = 1 @withClientUpdate def stopTurning(self): """ Start slowing down angular momentum. """ self.stopturning = 1 self.turning = False self.turndir = (0, 0, 0) observe_standStill = standStill observe_walk = walk observe_turn = turn observe_jump = jump observe_stopTurning = stopTurning @timed def doWalk(self, dt): """ Called each iteration, this function applies the various forces associated with walking. """ if not self.isOnGround(): return lv = self.body.getLinearVelocity() spd = Vector(lv[0], 0, lv[2]).length() d = Quaternion(self.head.getOrientation()) if spd >= self.maxwalkspeed: spd = self.maxwalkspeed else: rv = d.rotate(self.walkdir) f = Vector(rv) * (self.body.getMass().mass * 1000 * dt) self.body.addForce(f) return nlv = d.rotate(self.walkdir) * spd nlv = (nlv[0], lv[1], nlv[2]) self.body.setLinearVelocity(nlv) @timed def doStand(self, p=0.3): """ Called every iteration, this function attempts to stop the biped. When it it stopped, turn off the standing behavior. """ if not self.isOnGround(): return lv = self.body.getLinearVelocity() vv = lv[0] + lv[2] if vv < 0.1: self.body.setLinearVelocity((0, 0, 0)) if not self.turning and not self.stopturning: self.disable() self.standing = False return v = Vector(lv) * (p * 0.05) v = (v[0], lv[1], v[2]) # can't stop gravity! self.body.setLinearVelocity(v) @timed def doTurn(self, dt): """ Updated every Iteration, this function applies the various forces needed to continue turning. """ td = Vector(self.turndir) * (5.0 * dt) self.head.addTorque(td) lv = self.head.getAngularVelocity() spd = (lv[0] * lv[0]) + (lv[1] * lv[1]) + (lv[2] * lv[2]) if spd > self.maxturnspeed: self.head.setAngularVelocity(Vector(lv) * (self.maxturnspeed / spd)) return @timed def doStopTurning(self): """ Called every iteration, this function will decrease angular momentum until it is nearly none. Then it stops the behavior. """ av = Vector(self.head.getAngularVelocity()) * 0.05#(20.0 * 0.05) self.head.setAngularVelocity(av) if self.stopturning > 2: self.head.setAngularVelocity((0, 0, 0)) self.stopturning -= 1 if self.stopturning == 2: self.stopturning = False return spd = Vector(self.head.getAngularVelocity()).squaredDistance((0, 0, 0)) if spd < 0.01: self.stopturning = 15 self.head.setAngularVelocity((0, 0, 0)) def enterSpace(self, space): """ Create colliders in this space """ assert self.body is not None self.bodycollider.destroy() self.bodycollider.build(body=self.body, space=space) self.bodycollider.geom.waterDepth = 0 self.bodycollider.tgeom.waterDepth = 0 self.space = space def enterSecondarySpace(self, ownerid, space): """ Create secondary colliders in this space """ self.otherspacecolliders[ownerid] = [] self.otherspacespaces[ownerid] = space if self.otherspacecolliderspaces.has_key(ownerid): self.otherspacecolliderspaces.destroy() self.otherspacecolliders[ownerid] = self.bodycollider.copy() self.otherspacecolliders[ownerid].density = 0 self.otherspacecolliders[ownerid].build(self.body, space) def enterArea(self, area): """ Determine the space most appropriate in are and enter it. Area should be an area object """ dfrds = [] if hasattr(area, "sim"): self.parent = area.sim if self.body is None: self.create(area.world) #Hack! if self.isEnabled(): space = area.getEnabledSpace(self.getPosition()) else: space = area.getDisabledSpace(self.getPosition()) self.enterSpace(space) self.areaid = area.getID() self.area = area for vob in self.vobs: pdfrd = vob.create(area, self.getPosition(), self.getRotation()) if isinstance(pdfrd, Deferred): dfrds.append(pdfrd) if len(dfrds): return gatherResults(dfrds) def leaveArea(self, area): """ This body is leaving the area, remove everything in the area """ if area.getID() != self.areaid: return # self.colliderspace.destroy() self.bodycollider.destroy() self.space = None def leaveSecondarySpace(self, ownerid): """ Remove our secondary info from this space """ self.otherspacecolliders[ownerid].destroy() del self.otherspacecolliders[ownerid] del self.otherspacespaces[ownerid] # self.otherspacecolliderspaces[ownerid].destroy() # del self.otherspacecolliderspaces[ownerid] def hasSecondarySpace(self, ownerid): """ Returns true if we have colliders in this secondary space """ return ownerid in self.otherspacespaces.keys() def attach(self, other, joint): """ Attach this body to another one using the joint """ joint.attach(self, other) self.joints.append(joint) other.joints.append(joint) def setPosition(self, pos, permanent=True): """ Set the position of this body """ self.initial_position = pos if self.body is None: return self.body.setPosition(pos) self.head.setPosition(Vector(pos) + (0, self.headoffset, 0)) if permanent: self.teleported = True self.updateVobs() self.queueSave(selectAttributes=["position", "rotation"]) self.updateAllClients("setPosition", pos) def getPosition(self): """ Get the position of this body """ if self.body is not None: return self.body.getPosition() else: return self.initial_position def setRotation(self, rot, permanent=True): """ Set the quaternion rotation of this body """ self.initial_rotation = rot if self.head is None: return self.head.setOrientation(rot) if permanent: self.updateVobs() self.updateAllClients("setRotation", rot) setOrientation = setRotation def getRotation(self): """ Get the quaternion rotation of this body """ if self.body is not None: return self.head.getOrientation() else: return self.initial_rotation getOrientation = getRotation @withClientUpdate def setLinearVelocity(self, lv): """ Set the linear velocity of this body """ self.body.setLinearVelocity(lv) def getLinearVelocity(self): """ Returns the linear velocity of this body """ if self.body is not None: return self.body.getLinearVelocity() else: return (0, 0, 0) @withClientUpdate def setAngularVelocity(self, av): """ Set the angular Velocity of this body """ self.head.setAngularVelocity(av) def getAngularVelocity(self): """ Get the angular velocity of this body """ if self.body is not None: return self.head.getAngularVelocity() else: return (0, 0, 0) def setScale(self, scale): """ Set the scale of the vob """ # TODO: set scale of colliders too! self.vobs[0].setScale(scale) @timed @withClientUpdate def enable(self): """ Set the state of this body to be enabled """ self.body.enable() self.head.enable() # self.setPosition(self.getPosition()) # self.setRotation(self.getRotation()) if not self.enabled: self.setLinearVelocity(self.getLinearVelocity()) self.setAngularVelocity(self.getAngularVelocity()) if self.objectid is not None and self.parent is not None: self.parent.requestIteration(self.parent.items[self.objectid]) if self.area is not None: self.enterSpace(self.area.getEnabledSpace(self.getPosition())) self.enabled = True @timed @withClientUpdate def disable(self): """ Set the state of this body to be disabled """ self.body.disable() self.head.disable() self.setLinearVelocity((0, 0, 0)) self.setAngularVelocity((0, 0, 0)) self.setPosition(self.getPosition()) self.setRotation(self.getRotation()) if self.enabled and self.area is not None: self.setControllerParameter("linearVelocity", (0, 0, 0)) self.setControllerParameter("angularVelocity", (0, 0, 0)) self.enterSpace(self.area.getDisabledSpace(self.getPosition())) if self.objectid is not None and self.parent is not None: self.parent.removeIteration(self.parent.items[self.objectid]) self.enabled = False def isEnabled(self): """ Returns true if this body is enabled """ if self.body is None: return self.enabled return self.body.isEnabled() def setWaterDepth(self, depth): """ Set how far under water this is """ self.waterdepth = depth @timed def updateDynamics(self, pos, rot, lvel, avel): """ Update the position, rotation, linear and angular velocities """ self.teleported = True dpos = Vector(self.getPosition()) - pos dst = dpos.squaredDistance((0, 0, 0)) movement = (Vector(avel).squaredDistance((0, 0, 0)) + Vector(lvel).squaredDistance((0, 0, 0))) if dst > 0.5 and dst < 100 and movement > 0.1: self.gradualUpdate = dpos * -0.05 self.gradualOffset = 20 self.body.setLinearVelocity(lvel) self.head.setAngularVelocity(avel) self.head.setOrientation(rot) if not self.enabled: self.enable() return self.body.setPosition(pos) self.head.setOrientation(rot) self.body.setLinearVelocity(lvel) self.head.setAngularVelocity(avel) self.updateVobs() if movement > 0.1 and not self.enabled: self.enable() elif movement < 0.1 and self.enabled and self.checkOnGround(): self.disable() def getPreferredSpaceType(self): """ Return our preferred space type 0 = Enabled 1 = Disabled """ if self.isEnabled(): return 0 return 1 @withClientUpdate def addVisualObject(self, vob): """ Add a visual object """ self.vobs.append(vob) self.queueSave(selectAttributes=["vobs"]) @withClientUpdate def removeVisualObject(self, vob): """ Remove a visual object """ self.vobs.remove(vob) self.queueSave(selectAttributes=["vobs"]) observe_removeVisualObject = removeVisualObject observe_addVisualObject = addVisualObject def getVisualObjects(self): """ Return our list of visual objects """ return self.vobs def updateVobs(self): """ Update all the visual objects """ p = self.getPosition() r = self.getRotation() for vob in self.vobs: if isinstance(vob, IVisualObjectView): vob.setPosition(p) vob.setRotation(r) def setControllerParameter(self, name, value): """ Send a controller parameter to all vobs. """ for vob in self.vobs: vob.setControllerParameter(name, value) @timed def iterate(self): """ iterate this body """ if self.owner is not None: # nothing to do since we are controlled remotely if self.enabled and not self.body.isEnabled(): self.body.enable() elif not self.enabled and self.body.isEnabled(): self.body.disable() return if self.lastIter is None: self.lastIter = time() - 0.05 dt = time() - self.lastIter self.lastIter = time() self.setControllerParameter("angularVelocity", self.head.getAngularVelocity()) self.setControllerParameter("linearVelocity", self.body.getRelativeLinearVelocity()) if self.gradualOffset > 0 and self.gradualUpdate is not None: self.gradualOffset -= 1 self.body.setPosition(self.gradualUpdate + self.body.getPosition()) if not self.isEnabled() and not self.walking and not self.turning: if self.enabled: self.disable() return if not self.enabled: self.enable() p = self.body.getPosition() r = self.head.getOrientation() lv = self.body.getLinearVelocity() av = self.head.getAngularVelocity() if self.walking: self.doWalk(dt) elif self.standing: self.doStand() elif self.body is not None: lv = self.body.getLinearVelocity() if (lv[0] * lv[0]) + (lv[2] * lv[2]) > 0: self.standStill() if self.turning: self.doTurn(dt) elif self.stopturning: self.doStopTurning() else: av = Vector(self.head.getAngularVelocity()) if av.squaredDistance((0, 0, 0)) > 0: self.stopTurning() self.standUp() if self.lastPosition is not None and not self.teleported: delta = Vector(self.lastPosition) - self.getPosition() dist = delta.length() if dist > self.maxwalkspeed: self.setPosition(Vector(self.lastPosition) - (delta / dist) * self.maxwalkspeed) self.lastPosition = self.getPosition() self.teleported = False self.setControllerParameter("onGround", self.onground) for a in self.aggregators: a.giveUpdate(self, p, r, lv, av) self.updateVobs() self.bodycollider.tgeom.waterDepth = 0 def iterateOtherArea(self, _deltaTime, area): """ Iterate the body for another area. """ if not self.enabled or self.owner is not None: return self.standUp([area.getEnabledSpace(), area.getDisabledSpace()], False, False) def getBoundingBox(self): """ Returns the bounding box for this body (AA, object space) """ bbox = self.colliderspace.geom.getAABB() #minx, maxx, miny, maxy, etc low = Vector(bbox[::2]) - self.getPosition() high = Vector(bbox[1::2]) - self.getPosition() return tuple(list(low) + list(high)) def getServerClassGenerators(self): """ Return class generators """ cgs = [ClassGenerator("mv3d.phys.body.StringCatcher", sourceclass="mv3d.phys.body.StringFlinger")] cgs.append(ClassGenerator().setFrom(self)) for v in self.vobs: cgs.append(v.getServerClassGenerators()) return cgs def getClientClassGenerators(self): """ Return class generators """ cgs = [ClassGenerator("mv3d.phys.body.StringCatcher", sourceclass="mv3d.phys.body.StringFlinger")] cgs.append(ClassGenerator().setFrom(self)) for v in self.vobs: cgs.append(v.getClientClassGenerators()) return cgs def getStateToCacheAndObserveFor(self, perspective, observer): st = Cacheable.getStateToCacheAndObserveFor(self, perspective, observer) assert perspective is not None, "Perspective is none" if not hasattr(perspective, "bodydynamicsaggregator"): perspective.bodydynamicsaggregator = StringFlinger() perspective.bodydynamicsaggregator.grantPermission("read", "all") perspective.bodydynamicsaggregator.grantPermission("reference", "all") perspective.parent.parent.addIterator(perspective.bodydynamicsaggregator.iterate) st["bodydynamicsreceiver"] = perspective.bodydynamicsaggregator self.aggregators.append(perspective.bodydynamicsaggregator) st["initial_position"] = self.getPosition() st["initial_rotation"] = self.getRotation() st["initial_linearVel"] = self.getLinearVelocity() st["initial_angularVel"] = self.getAngularVelocity() return st def stoppedObserving(self, perspective, observer): assert perspective is not None, "perspective is none" perspective.bodydynamicsaggregator.removeBody(self) self.aggregators.remove(perspective.bodydynamicsaggregator) return Cacheable.stoppedObserving(self, perspective, observer) observe_setRotation = setRotation observe_setPosition = setPosition observe_setLinearVelocity = setLinearVelocity observe_setAngularVelocity = setAngularVelocity observe_disable = disable observe_enable = enable def _setInitialPosition(self, value): """ used in persisting """ self.initial_position = value def _setInitialRotation(self, value): """ used in persisting """ self.initial_rotation = value def _setInitialLinearVel(self, value): """ used in persisting """ self.initial_linearVel = value def _setInitialAngularVel(self, value): """ used in persisting """ self.initial_angularVel = value position = AttributeProperty(FloatVector(), getPosition, _setInitialPosition) rotation = AttributeProperty(FloatVector(), getRotation, _setInitialRotation) linearVel = AttributeProperty(FloatVector(), getLinearVelocity, _setInitialLinearVel) angularVel = AttributeProperty(FloatVector(), getAngularVelocity, _setInitialAngularVel) def save(self, store=None, partial=False, selectAttributes=None, transactionID=None): return BodyWithColliders.save(self, store, partial, selectAttributes, transactionID=transactionID)