2
0

DistributedSmoothNode.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. """DistributedSmoothNode module: contains the DistributedSmoothNode class"""
  2. from PandaModules import *
  3. from ClockDelta import *
  4. import DistributedNode
  5. import Task
  6. # This number defines our tolerance for out-of-sync telemetry packets.
  7. # If a packet appears to have originated from more than MaxFuture
  8. # seconds in the future, assume we're out of sync with the other
  9. # avatar and suggest a resync for both.
  10. MaxFuture = base.config.GetFloat("smooth-max-future", 0.2)
  11. # These flags indicate whether global smoothing and/or prediction is
  12. # allowed or disallowed.
  13. EnableSmoothing = base.config.GetBool("smooth-enable-smoothing", 1)
  14. EnablePrediction = base.config.GetBool("smooth-enable-prediction", 1)
  15. # These values represent the amount of time, in seconds, to delay the
  16. # apparent position of other avatars, when non-predictive and
  17. # predictive smoothing is in effect, respectively. This is in
  18. # addition to the automatic delay of the observed average latency from
  19. # each avatar, which is intended to compensate for relative clock
  20. # skew.
  21. Lag = base.config.GetDouble("smooth-lag", 0.2)
  22. PredictionLag = base.config.GetDouble("smooth-prediction-lag", 0.0)
  23. def activateSmoothing(smoothing, prediction):
  24. """
  25. Enables or disables the smoothing of other avatars' motion.
  26. This is a global flag that controls the behavior of all
  27. SmoothMovers in the world. If smoothing is off, no kind of
  28. smoothing will be performed, regardless of the setting of
  29. prediction.
  30. This is not necessarily predictive smoothing; if predictive
  31. smoothing is off, avatars will be lagged by a certain factor
  32. to achieve smooth motion. Otherwise, if predictive smoothing
  33. is on, avatars will be drawn as nearly as possible in their
  34. current position, by extrapolating from old position reports.
  35. This assumes you have a client repository that knows its
  36. localToonDoId -- stored in self.cr.localToonDoId
  37. """
  38. if smoothing and EnableSmoothing:
  39. if prediction and EnablePrediction:
  40. # Prediction and smoothing.
  41. SmoothMover.setSmoothMode(SmoothMover.SMOn)
  42. SmoothMover.setPredictionMode(SmoothMover.PMOn)
  43. SmoothMover.setDelay(PredictionLag)
  44. else:
  45. # Smoothing, but no prediction.
  46. SmoothMover.setSmoothMode(SmoothMover.SMOn)
  47. SmoothMover.setPredictionMode(SmoothMover.PMOff)
  48. SmoothMover.setDelay(Lag)
  49. else:
  50. # No smoothing, no prediction.
  51. SmoothMover.setSmoothMode(SmoothMover.SMOff)
  52. SmoothMover.setPredictionMode(SmoothMover.PMOff)
  53. SmoothMover.setDelay(0.0)
  54. class DistributedSmoothNode(DistributedNode.DistributedNode):
  55. """DistributedSmoothNode class:
  56. This specializes DistributedNode to add functionality to smooth
  57. motion over time, via the SmoothMover C++ object defined in
  58. DIRECT.
  59. """
  60. def __init__(self, cr):
  61. try:
  62. self.DistributedSmoothNode_initialized
  63. except:
  64. self.DistributedSmoothNode_initialized = 1
  65. DistributedNode.DistributedNode.__init__(self, cr)
  66. self.smoother = SmoothMover()
  67. self.smoothStarted = 0
  68. return None
  69. ### Methods to handle computing and updating of the smoothed
  70. ### position.
  71. def smoothPosition(self):
  72. """smoothPosition(self)
  73. This function updates the position of the node to its computed
  74. smoothed position. This may be overridden by a derived class
  75. to specialize the behavior.
  76. """
  77. if self.smoother.computeSmoothPosition():
  78. self.setMat(self.smoother.getSmoothMat())
  79. def doSmoothTask(self, task):
  80. self.smoothPosition()
  81. return Task.cont
  82. def wantsSmoothing(self):
  83. # Override this function to return 0 if this particular kind
  84. # of smooth node doesn't really want to be smoothed.
  85. return 1
  86. def startSmooth(self):
  87. """startSmooth(self)
  88. This function starts the task that ensures the node is
  89. positioned correctly every frame. However, while the task is
  90. running, you won't be able to lerp the node or directly
  91. position it.
  92. """
  93. if not self.wantsSmoothing():
  94. return
  95. if self.isLocal():
  96. # If we've just finished banging on localToon, reload the
  97. # drive interface's concept of our position.
  98. base.drive.node().setPos(self.getPos())
  99. base.drive.node().setHpr(self.getHpr())
  100. elif not self.smoothStarted:
  101. taskName = self.taskName("smooth")
  102. taskMgr.remove(taskName)
  103. self.reloadPosition()
  104. taskMgr.add(self.doSmoothTask, taskName)
  105. self.smoothStarted = 1
  106. return
  107. def stopSmooth(self):
  108. """startSmooth(self)
  109. This function stops the task spawned by startSmooth(), and
  110. allows show code to move the node around directly.
  111. """
  112. if self.smoothStarted:
  113. taskName = self.taskName("smooth")
  114. taskMgr.remove(taskName)
  115. self.forceToTruePosition()
  116. self.smoothStarted = 0
  117. return
  118. def forceToTruePosition(self):
  119. """forceToTruePosition(self)
  120. This forces the node to reposition itself to its latest known
  121. position. This may result in a pop as the node skips the last
  122. of its lerp points.
  123. """
  124. if (not self.isLocal()) and \
  125. self.smoother.getLatestPosition():
  126. self.setMat(self.smoother.getSmoothMat())
  127. self.smoother.clearPositions(1)
  128. def reloadPosition(self):
  129. """reloadPosition(self)
  130. This function re-reads the position from the node itself and
  131. clears any old position reports for the node. This should be
  132. used whenever show code bangs on the node position and expects
  133. it to stick.
  134. """
  135. self.smoother.clearPositions(0)
  136. self.smoother.setMat(self.getMat())
  137. self.smoother.setPhonyTimestamp()
  138. self.smoother.markPosition()
  139. ### distributed set pos and hpr functions ###
  140. ### These functions send the distributed update to set the
  141. ### appropriate values on the remote side. These are
  142. ### composite fields, with all the likely combinations
  143. ### defined; each function maps (via the dc file) to one or
  144. ### more component operations on the remote client.
  145. def d_setSmStop(self):
  146. self.sendUpdate("setSmStop", [globalClockDelta.getFrameNetworkTime()])
  147. def setSmStop(self, timestamp):
  148. self.setComponentTLive(timestamp)
  149. def d_setSmH(self, h):
  150. self.sendUpdate("setSmH", [h, globalClockDelta.getFrameNetworkTime()])
  151. def setSmH(self, h, timestamp):
  152. self.setComponentH(h)
  153. self.setComponentTLive(timestamp)
  154. def d_setSmXY(self, x, y):
  155. self.sendUpdate("setSmXY", [x, y, globalClockDelta.getFrameNetworkTime()])
  156. def setSmXY(self, x, y, timestamp):
  157. self.setComponentX(x)
  158. self.setComponentY(y)
  159. self.setComponentTLive(timestamp)
  160. def d_setSmXZ(self, x, z):
  161. self.sendUpdate("setSmXZ", [x, z, globalClockDelta.getFrameNetworkTime()])
  162. def setSmXZ(self, x, z, timestamp):
  163. self.setComponentX(x)
  164. self.setComponentZ(z)
  165. self.setComponentTLive(timestamp)
  166. def d_setSmPos(self, x, y, z):
  167. self.sendUpdate("setSmPos", [x, y, z, globalClockDelta.getFrameNetworkTime()])
  168. def setSmPos(self, x, y, z, timestamp):
  169. self.setComponentX(x)
  170. self.setComponentY(y)
  171. self.setComponentZ(z)
  172. self.setComponentTLive(timestamp)
  173. def d_setSmHpr(self, h, p, r):
  174. self.sendUpdate("setSmHpr", [h, p, r, globalClockDelta.getFrameNetworkTime()])
  175. def setSmHpr(self, h, p, r, timestamp):
  176. self.setComponentH(h)
  177. self.setComponentP(p)
  178. self.setComponentR(r)
  179. self.setComponentTLive(timestamp)
  180. def d_setSmXYH(self, x, y, h):
  181. self.sendUpdate("setSmXYH", [x, y, h, globalClockDelta.getFrameNetworkTime()])
  182. def setSmXYH(self, x, y, h, timestamp):
  183. self.setComponentX(x)
  184. self.setComponentY(y)
  185. self.setComponentH(h)
  186. self.setComponentTLive(timestamp)
  187. def d_setSmXYZH(self, x, y, z, h):
  188. self.sendUpdate("setSmXYZH", [x, y, z, h, globalClockDelta.getFrameNetworkTime()])
  189. def setSmXYZH(self, x, y, z, h, timestamp):
  190. self.setComponentX(x)
  191. self.setComponentY(y)
  192. self.setComponentZ(z)
  193. self.setComponentH(h)
  194. self.setComponentTLive(timestamp)
  195. def d_setSmPosHpr(self, x, y, z, h, p, r):
  196. self.sendUpdate("setSmPosHpr", [x, y, z, h, p, r, globalClockDelta.getFrameNetworkTime()])
  197. def setSmPosHpr(self, x, y, z, h, p, r, timestamp):
  198. self.setComponentX(x)
  199. self.setComponentY(y)
  200. self.setComponentZ(z)
  201. self.setComponentH(h)
  202. self.setComponentP(p)
  203. self.setComponentR(r)
  204. self.setComponentTLive(timestamp)
  205. return
  206. ### component set pos and hpr functions ###
  207. ### These are the component functions that are invoked
  208. ### remotely by the above composite functions.
  209. def setComponentX(self, x):
  210. self.smoother.setX(x)
  211. def setComponentY(self, y):
  212. self.smoother.setY(y)
  213. def setComponentZ(self, z):
  214. self.smoother.setZ(z)
  215. def setComponentH(self, h):
  216. self.smoother.setH(h)
  217. def setComponentP(self, p):
  218. self.smoother.setP(p)
  219. def setComponentR(self, r):
  220. self.smoother.setR(r)
  221. def setComponentT(self, timestamp):
  222. # This is a little bit hacky. If *this* function is called,
  223. # it must have been called directly by the server, for
  224. # instance to update the values previously set for some avatar
  225. # that was already into the zone as we entered. (A live
  226. # update would have gone through the function called
  227. # setComponentTLive, below.)
  228. # Since we know this update came through the server, it may
  229. # reflect very old data. Thus, we can't accurately decode the
  230. # network timestamp (since the network time encoding can only
  231. # represent a time up to about 5 minutes in the past), but we
  232. # don't really need to know the timestamp anyway. We'll just
  233. # arbitrarily place it at right now.
  234. self.smoother.setPhonyTimestamp()
  235. self.smoother.clearPositions(1)
  236. self.smoother.markPosition()
  237. def setComponentTLive(self, timestamp):
  238. # This is the variant of setComponentT() that will be called
  239. # whenever we receive a live update directly from the other
  240. # client. This is because the component functions, above,
  241. # call this function explicitly instead of setComponentT().
  242. now = globalClock.getFrameTime()
  243. local = globalClockDelta.networkToLocalTime(timestamp, now)
  244. chug = globalClock.getRealTime() - now
  245. # Sanity check the timestamp from the other avatar. It should
  246. # be just slightly in the past, but it might be off by as much
  247. # as this frame's amount of time forward or back.
  248. howFarFuture = local - now
  249. if howFarFuture - chug >= MaxFuture:
  250. # Too far off; resync both of us.
  251. if self.cr.timeManager != None:
  252. self.cr.timeManager.synchronize("Packets from %d off by %.1f s" % (self.doId, howFarFuture))
  253. self.d_suggestResync(self.cr.localToonDoId)
  254. self.smoother.setTimestamp(local)
  255. self.smoother.markPosition()
  256. def b_clearSmoothing(self):
  257. self.d_clearSmoothing()
  258. self.clearSmoothing()
  259. def d_clearSmoothing(self):
  260. self.sendUpdate("clearSmoothing", [0])
  261. def clearSmoothing(self, bogus = None):
  262. # Call this to invalidate all the old position reports
  263. # (e.g. just before popping to a new position).
  264. self.smoother.clearPositions(1)
  265. def wrtReparentTo(self, parent):
  266. # We override this NodePath method to force it to
  267. # automatically reset the smoothing position when we call it.
  268. if self.smoothStarted:
  269. self.forceToTruePosition()
  270. NodePath.wrtReparentTo(self, parent)
  271. self.reloadPosition()
  272. else:
  273. NodePath.wrtReparentTo(self, parent)