Roaming.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. /*
  2. * @Description: 漫游
  3. * @Version: 1.0
  4. * @Author: joy
  5. * @Date: 2023-04-12 21:42:26
  6. * @LastEditors: joy
  7. * @LastEditTime: 2023-04-13 16:32:25
  8. */
  9. export default class Roaming {
  10. /**
  11. * 创建一个漫游实例
  12. * @param {Object} viewer 三维场景viewer对象
  13. * @param {Array<Cesium.Cartesian3>/Array<[lng,lat,height]>} positions 坐标串,点集合
  14. * @param {Object} options 具有以下属性:
  15. * @param {Number} options.time 漫游时间
  16. * @param {Number} options.speed 速度
  17. * @param {Number} options.followedX 距离运动点的距离(后方)
  18. * @param {Number} options.followedZ 距离运动点的高度(上方)
  19. * @param {Number} options.height 高度差
  20. * @param {Number} options.role 0自由模式 1跟随模式 2第一视角 3上帝视角
  21. * 第一人称摄像头放置在主角的前面
  22. * 第三人称摄像头放置在主角的背后
  23. *
  24. * @param {Object} [options.model] model的样式,具有以下属性:
  25. * @param {Number} [options.model.url] 模型的url
  26. *
  27. * @param {Object} [options.billboard] 广告牌的样式,具有以下属性:
  28. * @param {Number} [options.billboard.imgUrl] 广告牌图片
  29. * @param {Number} [options.billboard.scale=1] 尺寸
  30. * @param {Object} [options.billboard.scaleByDistance] 距离相机的距离缩放点。
  31. * @param {Number} [options.billboard.scaleByDistance.near=0] 相机范围的下界。
  32. * @param {String} [options.billboard.scaleByDistance.nearValue=0] 相机范围下界的值。
  33. * @param {String} [options.billboard.scaleByDistance.far=1] 相机范围的上限。
  34. * @param {Number} [options.billboard.scaleByDistance.farValue=0] 该值位于摄像机范围的上界。
  35. *
  36. * @param {Object} [options.point] point的样式,具有以下属性:
  37. * @param {Number} [options.point.pixelSize=10] 指定点的大小,以像素为单位
  38. * @param {Array} [options.point.color=[255,255,255,0]] 点位颜色,颜色数组,[0~255,0~255,0~255,0~1],[red 红色,green 绿色,blue 蓝色,alpha 透明度]
  39. * @param {String} [options.point.outlineColor=[255,255,255,0]] 指定点轮廓的颜色,,颜色数组,[0~255,0~255,0~255,0~1],[red 红色,green 绿色,blue 蓝色,alpha 透明,
  40. * @param {Number} [options.point.outlineWidth=0] 指定点轮廓的宽度
  41. *
  42. * @param {Object} [options.label] label的样式,具有以下属性:
  43. * @param {Number} [options.label.text=""] 文字
  44. * @param {String} [options.label.font="24px Helvetica"] 字体样式
  45. * @param {String} [options.label.fillColor=[255,255,255,0]] 字体颜色
  46. * @param {String} [options.label.outlineColor=[255,255,255,0]] 字体边框颜色
  47. * @param {Number} [options.label.outlineWidth=1] 边框宽度
  48. * @param {Number} [options.label.showBackground=false] 是否显示背景颜色
  49. * @param {Number} [options.label.backgroundColor=[255,255,255,0]] 背景颜色
  50. * @param {Number} [options.label.pixelOffset] 偏移像素
  51. * @param {Number} [options.label.pixelOffset.x=0] 横向偏移像素
  52. * @param {Number} [options.label.pixelOffset.y=0] 纵向偏移像素
  53. * @param {Number} [options.label.scale=1] 尺寸
  54. * @param {Number} [options.label.scaleByDistance] 相机范围
  55. * @param {Number} [options.label.scaleByDistance.near=1.5e2] 相机范围的下界。
  56. * @param {String} [options.label.scaleByDistance.nearValue=1] 相机范围下界的值。
  57. * @param {String} [options.label.scaleByDistance.far=2400] 相机范围的上限。
  58. * @param {Number} [options.label.scaleByDistance.farValue=0] 该值位于摄像机范围的上界。
  59. * @memberof Roaming
  60. */
  61. constructor(viewer, positions, options) {
  62. if (!viewer) throw new Cesium.DeveloperError('no viewer object!');
  63. if (!positions) throw new Cesium.DeveloperError('no positions Array!');
  64. this.viewer = viewer;
  65. this.entity = undefined;
  66. options = options || {};
  67. options.time = Cesium.defaultValue(options.time, 360);
  68. options.speed = Cesium.defaultValue(options.speed, 10);
  69. options.isPathShow = Cesium.defaultValue(options.isPathShow, true);
  70. options.height = Cesium.defaultValue(options.height, 5);
  71. options.role = Cesium.defaultValue(options.role, 1);
  72. options.followedX = Cesium.defaultValue(options.followedX, 50);
  73. options.followedZ = Cesium.defaultValue(options.followedZ, 10);
  74. this.time = options.time;
  75. this.speed = options.speed;
  76. this.isPathShow = options.isPathShow;
  77. this.height = options.height;
  78. this.role = options.role;
  79. this.followedX = options.followedX;
  80. this.followedZ = options.followedZ;
  81. this.model = options.model;
  82. this.billboard = options.billboard;
  83. this.point = options.point;
  84. this.label = options.label;
  85. this.property = this.ComputeRoamingLineProperty(positions, this.time);
  86. }
  87. /**
  88. * @ignore
  89. * @param {*} Lines 点集合
  90. * @param {*} time 漫游时间
  91. * @returns
  92. * @memberof Roaming
  93. */
  94. ComputeRoamingLineProperty(Lines, time) {
  95. let _self = this;
  96. let positions = [];
  97. if (Lines[0] instanceof Cesium.Cartesian3) {
  98. positions = Lines;
  99. } else {
  100. positions = Lines.map(point => {
  101. return Cesium.Cartesian3.fromDegrees(point[0], point[1], point[2] || 0);
  102. });
  103. }
  104. //总距离
  105. let distance = [];
  106. for (let i = 0; i < positions.length - 1; i++) {
  107. let dis = Cesium.Cartesian3.distance(positions[i], positions[i + 1])
  108. distance.push(dis)
  109. }
  110. //点与点之间间隔时间
  111. let times = [Cesium.JulianDate.fromDate(new Date())]
  112. times.push(Cesium.JulianDate.addSeconds(times[0], time, new Cesium.JulianDate()))
  113. for (let i = 1; i < positions.length - 1; i++) {
  114. let s = Cesium.JulianDate.addSeconds(times[i], time * (distance[i] / distance[0]), new Cesium.JulianDate())
  115. times.push(s)
  116. }
  117. //一种属性,其值在给定时间内从提供的样本集和指定的插值算法和插值度中插值。
  118. let oriSamples = new Cesium.SampledProperty(Cesium.Cartesian3);
  119. oriSamples.addSamples(times, positions);
  120. let startTime = times[0]; // 起始时间
  121. let stopTime = times[times.length - 1]; // 结束时间
  122. this.viewer.clock.startTime = startTime.clone(); // 设置始时钟始时间
  123. this.viewer.clock.stopTime = stopTime.clone(); // 设置始终停止时间
  124. this.viewer.clock.currentTime = startTime.clone(); // 设置时钟当前时间
  125. this.viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; //循环执行
  126. this.viewer.clock.multiplier = this.speed; // 时间速率,数字越大时间过的越快
  127. // 计算提供的实例之间的天数差。
  128. let timeOfResolution = 6;
  129. let samplesNum = Math.floor(
  130. Cesium.JulianDate.secondsDifference(stopTime, startTime) / timeOfResolution
  131. );
  132. let sampledPositions = [];
  133. let sampledTimes = [];
  134. for (let i = 0; i < samplesNum + 1; i++) {
  135. let sampleTime = Cesium.JulianDate.addSeconds(
  136. startTime,
  137. i * timeOfResolution,
  138. new Cesium.JulianDate()
  139. );
  140. let tmpPos = oriSamples.getValue(sampleTime);
  141. sampledPositions.push(Cesium.Cartographic.fromCartesian(tmpPos));
  142. sampledTimes.push(sampleTime);
  143. }
  144. let promise = Cesium.sampleTerrainMostDetailed(
  145. this.viewer.terrainProvider,
  146. sampledPositions
  147. ).then(updatedPositions => {
  148. let carPositionProperty = new Cesium.SampledPositionProperty();
  149. //高度
  150. for (let i = 0; i < sampledPositions.length; i++) {
  151. sampledPositions[i].height = sampledPositions[i].height + this.height
  152. }
  153. for (let i = 0; i < samplesNum + 1; i++) {
  154. carPositionProperty.addSample(
  155. sampledTimes[i],
  156. Cesium.Ellipsoid.WGS84.cartographicToCartesian(sampledPositions[i])
  157. );
  158. }
  159. var position = carPositionProperty;
  160. this.InitRoaming(position, startTime, stopTime, this.isPathShow)
  161. });
  162. }
  163. /**
  164. * @ignore
  165. * @param {*} position computeRoamingLineProperty计算的属性
  166. * @param {*} start 开始时间节点
  167. * @param {*} stop 结束时间节点
  168. * @param {*} isPathShow path路径是否显示
  169. * @memberof Roaming
  170. */
  171. InitRoaming(position, start, stop, isPathShow) {
  172. let _self = this;
  173. this.entity = this.viewer.entities.add({
  174. // 和时间轴关联
  175. availability: new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({
  176. start: start,
  177. stop: stop
  178. })]),
  179. // 位置
  180. position: position,
  181. //基于位置移动自动计算方向.
  182. orientation: new Cesium.VelocityOrientationProperty(position),
  183. //路径
  184. path: {
  185. resolution: 1,
  186. //设置航线样式,线条颜色,内发光粗细,航线宽度等
  187. material: new Cesium.PolylineGlowMaterialProperty({
  188. glowPower: 0.1,
  189. color: Cesium.Color.YELLOW
  190. }),
  191. width: 10,
  192. show: isPathShow
  193. }
  194. })
  195. if (this.model) {
  196. let model = this.model;
  197. // 加载模型, 模型数据,跨域,模型文件必须放本地
  198. this.entity.model = {
  199. // 模型路径
  200. uri: model.url,
  201. // 模型最小刻度
  202. minimumPixelSize: 64,
  203. maximumSize: 128,
  204. // 设置模型最大放大大小
  205. maximumScale: 200,
  206. // 模型是否可见
  207. show: true,
  208. // 模型轮廓颜色
  209. silhouetteColor: Cesium.Color.WHITE,
  210. // 模型颜色 ,这里可以设置颜色的变化
  211. // color: color,
  212. // 仅用于调试,显示魔仙绘制时的线框
  213. debugWireframe: false,
  214. // 仅用于调试。显示模型绘制时的边界球。
  215. debugShowBoundingVolume: false,
  216. scale: 20,
  217. runAnimations: true // 是否运行模型中的动画效果
  218. };
  219. } else if (this.billboard) {
  220. let billboard = this.billboard;
  221. billboard.imgUrl = Cesium.defaultValue(billboard.imgUrl, 'jt3dSDK/imgs/point/point3.png');
  222. this.entity.billboard = {
  223. image: billboard.imgUrl, // default: undefined
  224. show: true, // default
  225. width: 30, // default: undefined
  226. scale: 1,
  227. height: 30, // default: undefined
  228. pixelOffset: new Cesium.Cartesian2(0, -14), // default: (0, 0)
  229. // disableDepthTestDistance: 0,
  230. }
  231. } else {
  232. let point = {};
  233. if (this.point) {
  234. point = this.point;
  235. }
  236. //点的大小
  237. point.pixelSize = Cesium.defaultValue(point.pixelSize, 10);
  238. //点位颜色
  239. if (point.color) {
  240. if (point.color instanceof Array) {
  241. point.color = new Cesium.Color(point.color[0] / 255, point.color[1] / 255, point.color[2] / 255, point.color[3]);
  242. } else if (typeof(point.color) === 'string') {
  243. point.color = new Cesium.Color.fromCssColorString(point.color);
  244. } else {
  245. point.color = new Cesium.Color.fromCssColorString("#FFFF00");
  246. }
  247. }
  248. //点位轮廓颜色
  249. if (point.outlineColor) {
  250. if (point.outlineColor instanceof Array) {
  251. point.outlineColor = new Cesium.Color(point.outlineColor[0] / 255, point.outlineColor[1] / 255, point.outlineColor[2] / 255, point.outlineColor[3]);
  252. } else if (typeof(point.outlineColor) === 'string') {
  253. point.outlineColor = new Cesium.Color.fromCssColorString(point.outlineColor);
  254. } else {
  255. point.outlineColor = new Cesium.Color.fromCssColorString("#FFFF00");
  256. }
  257. }
  258. //点位轮廓宽度
  259. point.outlineWidth = Cesium.defaultValue(point.outlineWidth, 2);
  260. this.entity.point = point
  261. }
  262. /* 判断是否需要绘制文字 */
  263. if (this.label) {
  264. let label = this.label;
  265. label.text = Cesium.defaultValue(label.text, "");
  266. label.font = Cesium.defaultValue(label.font, "24px Helvetica"); //字体样式
  267. //字体颜色
  268. if (label.fillColor) {
  269. if (label.fillColor instanceof Array) {
  270. label.fillColor = new Cesium.Color(label.fillColor[0] / 255, label.fillColor[1] / 255, label.fillColor[2] / 255, label.fillColor[3]);
  271. } else if (typeof(label.fillColor) === 'string') {
  272. label.fillColor = new Cesium.Color.fromCssColorString(label.fillColor);
  273. } else {
  274. label.fillColor = new Cesium.Color.fromCssColorString("#FFFF00");
  275. }
  276. }
  277. //字体边框颜色
  278. if (label.outlineColor) {
  279. if (label.outlineColor instanceof Array) {
  280. label.outlineColor = new Cesium.Color(label.outlineColor[0] / 255, label.outlineColor[1] / 255, label.outlineColor[2] / 255, label.outlineColor[3]);
  281. } else if (typeof(label.outlineColor) === 'string') {
  282. label.outlineColor = new Cesium.Color.fromCssColorString(label.outlineColor);
  283. } else {
  284. label.outlineColor = new Cesium.Color.fromCssColorString("#FFFF00");
  285. }
  286. }
  287. //字体边框宽度
  288. label.outlineWidth = Cesium.defaultValue(label.outlineWidth, 1);
  289. //是否显示背景颜色
  290. label.showBackground = Cesium.defaultValue(label.showBackground, false);
  291. //背景颜色
  292. if (label.backgroundColor) {
  293. if (label.backgroundColor instanceof Array) {
  294. label.backgroundColor = new Cesium.Color(label.backgroundColor[0] / 255, label.backgroundColor[1] / 255, label.backgroundColor[2] / 255, label.backgroundColor[3]);
  295. } else if (typeof(label.backgroundColor) === 'string') {
  296. label.backgroundColor = new Cesium.Color.fromCssColorString(label.backgroundColor);
  297. } else {
  298. label.backgroundColor = new Cesium.Color.fromCssColorString("#FFFF00");
  299. }
  300. }
  301. label.pixelOffset = Cesium.defaultValue(label.pixelOffset, 0);
  302. label.scale = Cesium.defaultValue(label.scale, 1);
  303. this.entity.label = {
  304. text: label.text,
  305. font: label.font, //字体样式
  306. fillColor: label.fillColor, //字体颜色
  307. outlineColor: label.outlineColor, //字体边框颜色
  308. outlineWidth: label.outlineWidth, //边框宽度
  309. style: Cesium.LabelStyle.FILL_AND_OUTLINE, //FILL不要轮廓 , OUTLINE只要轮廓,FILL_AND_OUTLINE轮廓加填充
  310. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  311. showBackground: label.showBackground, //是否显示背景颜色
  312. backgroundColor: label.backgroundColor, // 背景颜色
  313. backgroundPadding: new Cesium.Cartesian2(6, 6), //指定以像素为单位的水平和垂直背景填充padding
  314. disableDepthTestDistance: Number.POSITIVE_INFINITY,
  315. scale: label.scale, //尺寸
  316. }
  317. //偏移量
  318. if (label.pixelOffset) {
  319. label.pixelOffset.x = Cesium.defaultValue(label.pixelOffset.x, 0);
  320. label.pixelOffset.y = Cesium.defaultValue(label.pixelOffset.y, 0);
  321. this.entity.label.pixelOffset = new Cesium.Cartesian2(label.pixelOffset.x, label.pixelOffset.y);
  322. }
  323. if (label.scaleByDistance) {
  324. label.scaleByDistance.near = Cesium.defaultValue(label.scaleByDistance.near, 0);
  325. label.scaleByDistance.nearValue = Cesium.defaultValue(label.scaleByDistance.nearValue, 0);
  326. label.scaleByDistance.far = Cesium.defaultValue(label.scaleByDistance.far, 1);
  327. label.scaleByDistance.farValue = Cesium.defaultValue(label.scaleByDistance.farValue, 0);
  328. this.entity.label.scaleByDistance = new Cesium.NearFarScalar(label.scaleByDistance.near, label.scaleByDistance.nearValue, label.scaleByDistance.far, label.scaleByDistance.farValue) //按距离缩放,即距离大于180米时,图标不显示 Cesium.NearFarScalar(near, nearValue, far, farValue)相机范围的下界。相机范围下界的值。相机范围的上限。该值位于摄像机范围的上界。
  329. }
  330. }
  331. this.entity.position.setInterpolationOptions({ // 点插值
  332. interpolationDegree: 5,
  333. interpolationAlgorithm: Cesium.LagrangePolynomialApproximation
  334. })
  335. this.initRole(this.role);
  336. }
  337. /**
  338. * @param {Object} role 0自由模式 1跟随模式 2第一视角 3 第三视角 后方、前方、俯视(上帝视角)、左侧、右侧
  339. */
  340. initRole(role) {
  341. let _self = this;
  342. if (role == 0) { //自由模式
  343. this.viewer.trackedEntity = undefined;
  344. //获取被clock监听的全部事件数量
  345. let len = _self.viewer.clock.onTick.numberOfListeners;
  346. for (let i = 0; i < len; i++) {
  347. //将被监听的方法移除来停止方法
  348. _self.viewer.clock.onTick.removeEventListener(_self.viewer.clock.onTick._listeners[i]);
  349. }
  350. } else if (role == 1) { //跟随模式
  351. this.viewer.trackedEntity = this.entity;
  352. //获取被clock监听的全部事件数量
  353. let len = _self.viewer.clock.onTick.numberOfListeners;
  354. for (let i = 0; i < len; i++) {
  355. //将被监听的方法移除来停止方法
  356. _self.viewer.clock.onTick.removeEventListener(_self.viewer.clock.onTick._listeners[i]);
  357. }
  358. } else if (role == 2) { //第一视角
  359. this.viewer.trackedEntity = this.entity;
  360. let exection = function TimeExecution() {
  361. if (_self.viewer.clock.shouldAnimate === true) {
  362. //获取位置
  363. let center = _self.entity.position.getValue(
  364. _self.viewer.clock.currentTime
  365. );
  366. //获取偏向角
  367. let orientation = _self.entity.orientation.getValue(
  368. _self.viewer.clock.currentTime
  369. )
  370. let transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
  371. transform = Cesium.Matrix4.fromRotationTranslation(Cesium.Matrix3.fromQuaternion(orientation), center);
  372. var transformX = _self.followedX || -100; //距离运动点的距离(后方)
  373. var transformZ = _self.followedZ || 50; //距离运动点的高度(上方)
  374. _self.viewer.camera.lookAtTransform(transform, new Cesium.Cartesian3(transformX, 0, transformZ))
  375. }
  376. };
  377. //监听时间变化 让cesium的时钟方法来监听该方法
  378. this.viewer.clock.onTick.addEventListener(exection);
  379. } else if (role == 3) { //第三视角 上帝视角
  380. this.viewer.trackedEntity = this.entity;
  381. let exection = function TimeExecution() {
  382. if (_self.viewer.clock.shouldAnimate === true) {
  383. //获取位置
  384. let center = _self.entity.position.getValue(
  385. _self.viewer.clock.currentTime
  386. );
  387. let orientation = _self.entity.orientation.getValue(
  388. _self.viewer.clock.currentTime
  389. )
  390. let transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
  391. transform = Cesium.Matrix4.fromRotationTranslation(Cesium.Matrix3.fromQuaternion(orientation), center);
  392. // var transformX = -50; //距离运动点的距离(后方)
  393. // var transformZ = 250; //距离运动点的高度(上方)
  394. var transformX = _self.followedX || -50; //距离运动点的距离(后方)
  395. var transformZ = _self.followedZ || 250; //距离运动点的高度(上方)
  396. _self.viewer.camera.lookAtTransform(transform, new Cesium.Cartesian3(transformX, 0, transformZ))
  397. }
  398. };
  399. //监听时间变化 让cesium的时钟方法来监听该方法
  400. this.viewer.clock.onTick.addEventListener(exection);
  401. }
  402. }
  403. /**
  404. * 漫游的暂停和继续
  405. * @param {Boolean} state bool类型 false为暂停,ture为继续
  406. * @memberof Roaming
  407. */
  408. PauseOrContinue(state) {
  409. this.viewer.clock.shouldAnimate = state;
  410. }
  411. /**
  412. * 向前飞行
  413. */
  414. forwardFly() {
  415. // var clockViewModel = this.viewer.clockViewModel;
  416. // var multiplier = clockViewModel.multiplier;
  417. // if (multiplier < 0) {
  418. // clockViewModel.multiplier = -multiplier;
  419. // }
  420. // clockViewModel.shouldAnimate = true;
  421. var multiplier = this.viewer.clock.multiplier;
  422. if (multiplier < 0) {
  423. this.viewer.clock.multiplier = -multiplier;
  424. }
  425. this.viewer.clock.shouldAnimate = true;
  426. }
  427. /**
  428. * 向后飞行
  429. */
  430. backwardsFly() {
  431. // var clockViewModel = this.viewer.clockViewModel;
  432. // var multiplier = clockViewModel.multiplier;
  433. // if (multiplier > 0) {
  434. // clockViewModel.multiplier = -multiplier;
  435. // }
  436. // clockViewModel.shouldAnimate = true;
  437. var multiplier = this.viewer.clock.multiplier;
  438. if (multiplier > 0) {
  439. this.viewer.clock.multiplier = -multiplier;
  440. }
  441. this.viewer.clock.shouldAnimate = true;
  442. }
  443. /**
  444. * 改变飞行的速度
  445. * @param {Number} value 整数类型
  446. * @memberof Roaming
  447. */
  448. ChangeRoamingSpeed(value) {
  449. this.viewer.clock.multiplier = value;
  450. }
  451. /**
  452. * 取消漫游
  453. * @memberof Roaming
  454. */
  455. EndRoaming() {
  456. if (this.entity !== undefined) {
  457. this.viewer.entities.remove(this.entity);
  458. }
  459. this.viewer.trackedEntity = undefined;
  460. this.viewer.clock.shouldAnimate = false;
  461. //获取被clock监听的全部事件数量
  462. let len = this.viewer.clock.onTick.numberOfListeners;
  463. for (let i = 0; i < len; i++) {
  464. //将被监听的方法移除来停止方法
  465. this.viewer.clock.onTick.removeEventListener(this.viewer.clock.onTick._listeners[i]);
  466. }
  467. }
  468. }