Roaming.js 19 KB

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