PathVisualizer.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /*global define*/
  2. define([
  3. '../Core/AssociativeArray',
  4. '../Core/Cartesian3',
  5. '../Core/defined',
  6. '../Core/destroyObject',
  7. '../Core/DeveloperError',
  8. '../Core/JulianDate',
  9. '../Core/Matrix3',
  10. '../Core/Matrix4',
  11. '../Core/ReferenceFrame',
  12. '../Core/TimeInterval',
  13. '../Core/Transforms',
  14. '../Scene/PolylineCollection',
  15. '../Scene/SceneMode',
  16. './CompositePositionProperty',
  17. './ConstantPositionProperty',
  18. './MaterialProperty',
  19. './Property',
  20. './ReferenceProperty',
  21. './SampledPositionProperty',
  22. './TimeIntervalCollectionPositionProperty'
  23. ], function(
  24. AssociativeArray,
  25. Cartesian3,
  26. defined,
  27. destroyObject,
  28. DeveloperError,
  29. JulianDate,
  30. Matrix3,
  31. Matrix4,
  32. ReferenceFrame,
  33. TimeInterval,
  34. Transforms,
  35. PolylineCollection,
  36. SceneMode,
  37. CompositePositionProperty,
  38. ConstantPositionProperty,
  39. MaterialProperty,
  40. Property,
  41. ReferenceProperty,
  42. SampledPositionProperty,
  43. TimeIntervalCollectionPositionProperty) {
  44. "use strict";
  45. var defaultResolution = 60.0;
  46. var defaultWidth = 1.0;
  47. var scratchTimeInterval = new TimeInterval();
  48. var subSampleCompositePropertyScratch = new TimeInterval();
  49. var subSampleIntervalPropertyScratch = new TimeInterval();
  50. var EntityData = function(entity) {
  51. this.entity = entity;
  52. this.polyline = undefined;
  53. this.index = undefined;
  54. this.updater = undefined;
  55. };
  56. function subSampleSampledProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
  57. var times = property._property._times;
  58. var r = startingIndex;
  59. //Always step exactly on start (but only use it if it exists.)
  60. var tmp;
  61. tmp = property.getValueInReferenceFrame(start, referenceFrame, result[r]);
  62. if (defined(tmp)) {
  63. result[r++] = tmp;
  64. }
  65. var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop);
  66. //Iterate over all interval times and add the ones that fall in our
  67. //time range. Note that times can contain data outside of
  68. //the intervals range. This is by design for use with interpolation.
  69. var t = 0;
  70. var len = times.length;
  71. var current = times[t];
  72. var loopStop = stop;
  73. var sampling = false;
  74. var sampleStepsToTake;
  75. var sampleStepsTaken;
  76. var sampleStepSize;
  77. while (t < len) {
  78. if (!steppedOnNow && JulianDate.greaterThanOrEquals(current, updateTime)) {
  79. tmp = property.getValueInReferenceFrame(updateTime, referenceFrame, result[r]);
  80. if (defined(tmp)) {
  81. result[r++] = tmp;
  82. }
  83. steppedOnNow = true;
  84. }
  85. if (JulianDate.greaterThan(current, start) && JulianDate.lessThan(current, loopStop) && !current.equals(updateTime)) {
  86. tmp = property.getValueInReferenceFrame(current, referenceFrame, result[r]);
  87. if (defined(tmp)) {
  88. result[r++] = tmp;
  89. }
  90. }
  91. if (t < (len - 1)) {
  92. if (maximumStep > 0 && !sampling) {
  93. var next = times[t + 1];
  94. var secondsUntilNext = JulianDate.secondsDifference(next, current);
  95. sampling = secondsUntilNext > maximumStep;
  96. if (sampling) {
  97. sampleStepsToTake = Math.ceil(secondsUntilNext / maximumStep);
  98. sampleStepsTaken = 0;
  99. sampleStepSize = secondsUntilNext / Math.max(sampleStepsToTake, 2);
  100. sampleStepsToTake = Math.max(sampleStepsToTake - 1, 1);
  101. }
  102. }
  103. if (sampling && sampleStepsTaken < sampleStepsToTake) {
  104. current = JulianDate.addSeconds(current, sampleStepSize, new JulianDate());
  105. sampleStepsTaken++;
  106. continue;
  107. }
  108. }
  109. sampling = false;
  110. t++;
  111. current = times[t];
  112. }
  113. //Always step exactly on stop (but only use it if it exists.)
  114. tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[r]);
  115. if (defined(tmp)) {
  116. result[r++] = tmp;
  117. }
  118. return r;
  119. }
  120. function subSampleGenericProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
  121. var tmp;
  122. var i = 0;
  123. var index = startingIndex;
  124. var time = start;
  125. var stepSize = Math.max(maximumStep, 60);
  126. var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop);
  127. while (JulianDate.lessThan(time, stop)) {
  128. if (!steppedOnNow && JulianDate.greaterThanOrEquals(time, updateTime)) {
  129. steppedOnNow = true;
  130. tmp = property.getValueInReferenceFrame(updateTime, referenceFrame, result[index]);
  131. if (defined(tmp)) {
  132. result[index] = tmp;
  133. index++;
  134. }
  135. }
  136. tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]);
  137. if (defined(tmp)) {
  138. result[index] = tmp;
  139. index++;
  140. }
  141. i++;
  142. time = JulianDate.addSeconds(start, stepSize * i, new JulianDate());
  143. }
  144. //Always sample stop.
  145. tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[index]);
  146. if (defined(tmp)) {
  147. result[index] = tmp;
  148. index++;
  149. }
  150. return index;
  151. }
  152. function subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
  153. subSampleIntervalPropertyScratch.start = start;
  154. subSampleIntervalPropertyScratch.stop = stop;
  155. var index = startingIndex;
  156. var intervals = property.intervals;
  157. for (var i = 0; i < intervals.length; i++) {
  158. var interval = intervals.get(i);
  159. if (!TimeInterval.intersect(interval, subSampleIntervalPropertyScratch, scratchTimeInterval).isEmpty) {
  160. var time = interval.start;
  161. if (!interval.isStartIncluded) {
  162. if (interval.isStopIncluded) {
  163. time = interval.stop;
  164. } else {
  165. time = JulianDate.addSeconds(interval.start, JulianDate.secondsDifference(interval.stop, interval.start) / 2, new JulianDate());
  166. }
  167. }
  168. var tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]);
  169. if (defined(tmp)) {
  170. result[index] = tmp;
  171. index++;
  172. }
  173. }
  174. }
  175. return index;
  176. }
  177. function subSampleConstantProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
  178. var tmp = property.getValueInReferenceFrame(start, referenceFrame, result[startingIndex]);
  179. if (defined(tmp)) {
  180. result[startingIndex++] = tmp;
  181. }
  182. return startingIndex;
  183. }
  184. function subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
  185. subSampleCompositePropertyScratch.start = start;
  186. subSampleCompositePropertyScratch.stop = stop;
  187. var index = startingIndex;
  188. var intervals = property.intervals;
  189. for (var i = 0; i < intervals.length; i++) {
  190. var interval = intervals.get(i);
  191. if (!TimeInterval.intersect(interval, subSampleCompositePropertyScratch, scratchTimeInterval).isEmpty) {
  192. var intervalStart = interval.start;
  193. var intervalStop = interval.stop;
  194. var sampleStart = start;
  195. if (JulianDate.greaterThan(intervalStart, sampleStart)) {
  196. sampleStart = intervalStart;
  197. }
  198. var sampleStop = stop;
  199. if (JulianDate.lessThan(intervalStop, sampleStop)) {
  200. sampleStop = intervalStop;
  201. }
  202. var intervalProperty = interval.data;
  203. if (intervalProperty instanceof ReferenceProperty) {
  204. intervalProperty = intervalProperty.resolvedProperty;
  205. }
  206. if (intervalProperty instanceof SampledPositionProperty) {
  207. index = subSampleSampledProperty(intervalProperty, sampleStart, sampleStop, updateTime, referenceFrame, maximumStep, index, result);
  208. } else if (intervalProperty instanceof CompositePositionProperty) {
  209. index = subSampleCompositeProperty(intervalProperty, sampleStart, sampleStop, updateTime, referenceFrame, maximumStep, index, result);
  210. } else if (intervalProperty instanceof TimeIntervalCollectionPositionProperty) {
  211. index = subSampleIntervalProperty(intervalProperty, sampleStart, sampleStop, updateTime, referenceFrame, maximumStep, index, result);
  212. } else if (intervalProperty instanceof ConstantPositionProperty) {
  213. index = subSampleConstantProperty(intervalProperty, sampleStart, sampleStop, updateTime, referenceFrame, maximumStep, index, result);
  214. } else {
  215. //Fallback to generic sampling.
  216. index = subSampleGenericProperty(intervalProperty, sampleStart, sampleStop, updateTime, referenceFrame, maximumStep, index, result);
  217. }
  218. }
  219. }
  220. return index;
  221. }
  222. function subSample(property, start, stop, updateTime, referenceFrame, maximumStep, result) {
  223. if (!defined(result)) {
  224. result = [];
  225. }
  226. if (property instanceof ReferenceProperty) {
  227. property = property.resolvedProperty;
  228. }
  229. var length = 0;
  230. if (property instanceof SampledPositionProperty) {
  231. length = subSampleSampledProperty(property, start, stop, updateTime, referenceFrame, maximumStep, 0, result);
  232. } else if (property instanceof CompositePositionProperty) {
  233. length = subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, 0, result);
  234. } else if (property instanceof TimeIntervalCollectionPositionProperty) {
  235. length = subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, 0, result);
  236. } else if (property instanceof ConstantPositionProperty) {
  237. length = subSampleConstantProperty(property, start, stop, updateTime, referenceFrame, maximumStep, 0, result);
  238. } else {
  239. //Fallback to generic sampling.
  240. length = subSampleGenericProperty(property, start, stop, updateTime, referenceFrame, maximumStep, 0, result);
  241. }
  242. result.length = length;
  243. return result;
  244. }
  245. var toFixedScratch = new Matrix3();
  246. var PolylineUpdater = function(scene, referenceFrame) {
  247. this._unusedIndexes = [];
  248. this._polylineCollection = new PolylineCollection();
  249. this._scene = scene;
  250. this._referenceFrame = referenceFrame;
  251. scene.primitives.add(this._polylineCollection);
  252. };
  253. PolylineUpdater.prototype.update = function(time) {
  254. if (this._referenceFrame === ReferenceFrame.INERTIAL) {
  255. var toFixed = Transforms.computeIcrfToFixedMatrix(time, toFixedScratch);
  256. if (!defined(toFixed)) {
  257. toFixed = Transforms.computeTemeToPseudoFixedMatrix(time, toFixedScratch);
  258. }
  259. Matrix4.fromRotationTranslation(toFixed, Cartesian3.ZERO, this._polylineCollection.modelMatrix);
  260. }
  261. };
  262. PolylineUpdater.prototype.updateObject = function(time, item) {
  263. var entity = item.entity;
  264. var pathGraphics = entity._path;
  265. var positionProperty = entity._position;
  266. var sampleStart;
  267. var sampleStop;
  268. var showProperty = pathGraphics._show;
  269. var polyline = item.polyline;
  270. var show = !defined(showProperty) || showProperty.getValue(time);
  271. //While we want to show the path, there may not actually be anything to show
  272. //depending on lead/trail settings. Compute the interval of the path to
  273. //show and check against actual availability.
  274. if (show) {
  275. var leadTime = Property.getValueOrUndefined(pathGraphics._leadTime, time);
  276. var trailTime = Property.getValueOrUndefined(pathGraphics._trailTime, time);
  277. var availability = entity._availability;
  278. var hasAvailability = defined(availability);
  279. var hasLeadTime = defined(leadTime);
  280. var hasTrailTime = defined(trailTime);
  281. //Objects need to have either defined availability or both a lead and trail time in order to
  282. //draw a path (since we can't draw "infinite" paths.
  283. show = hasAvailability || (hasLeadTime && hasTrailTime);
  284. //The final step is to compute the actual start/stop times of the path to show.
  285. //If current time is outside of the availability interval, there's a chance that
  286. //we won't have to draw anything anyway.
  287. if (show) {
  288. if (hasTrailTime) {
  289. sampleStart = JulianDate.addSeconds(time, -trailTime, new JulianDate());
  290. }
  291. if (hasLeadTime) {
  292. sampleStop = JulianDate.addSeconds(time, leadTime, new JulianDate());
  293. }
  294. if (hasAvailability) {
  295. var start = availability.start;
  296. var stop = availability.stop;
  297. if (!hasTrailTime || JulianDate.greaterThan(start, sampleStart)) {
  298. sampleStart = start;
  299. }
  300. if (!hasLeadTime || JulianDate.lessThan(stop, sampleStop)) {
  301. sampleStop = stop;
  302. }
  303. }
  304. show = JulianDate.lessThan(sampleStart, sampleStop);
  305. }
  306. }
  307. if (!show) {
  308. //don't bother creating or updating anything else
  309. if (defined(polyline)) {
  310. this._unusedIndexes.push(item.index);
  311. item.polyline = undefined;
  312. polyline.show = false;
  313. item.index = undefined;
  314. }
  315. return;
  316. }
  317. if (!defined(polyline)) {
  318. var unusedIndexes = this._unusedIndexes;
  319. var length = unusedIndexes.length;
  320. if (length > 0) {
  321. var index = unusedIndexes.pop();
  322. polyline = this._polylineCollection.get(index);
  323. item.index = index;
  324. } else {
  325. item.index = this._polylineCollection.length;
  326. polyline = this._polylineCollection.add();
  327. }
  328. polyline.id = entity;
  329. item.polyline = polyline;
  330. }
  331. var resolution = Property.getValueOrDefault(pathGraphics._resolution, time, defaultResolution);
  332. polyline.show = true;
  333. polyline.positions = subSample(positionProperty, sampleStart, sampleStop, time, this._referenceFrame, resolution, polyline.positions);
  334. polyline.material = MaterialProperty.getValue(time, pathGraphics._material, polyline.material);
  335. polyline.width = Property.getValueOrDefault(pathGraphics._width, time, defaultWidth);
  336. };
  337. PolylineUpdater.prototype.removeObject = function(item) {
  338. var polyline = item.polyline;
  339. if (defined(polyline)) {
  340. this._unusedIndexes.push(item.index);
  341. item.polyline = undefined;
  342. polyline.show = false;
  343. item.index = undefined;
  344. }
  345. };
  346. PolylineUpdater.prototype.destroy = function() {
  347. this._scene.primitives.remove(this._polylineCollection);
  348. return destroyObject(this);
  349. };
  350. /**
  351. * A {@link Visualizer} which maps {@link Entity#path} to a {@link Polyline}.
  352. * @alias PathVisualizer
  353. * @constructor
  354. *
  355. * @param {Scene} scene The scene the primitives will be rendered in.
  356. * @param {EntityCollection} entityCollection The entityCollection to visualize.
  357. */
  358. var PathVisualizer = function(scene, entityCollection) {
  359. //>>includeStart('debug', pragmas.debug);
  360. if (!defined(scene)) {
  361. throw new DeveloperError('scene is required.');
  362. }
  363. if (!defined(entityCollection)) {
  364. throw new DeveloperError('entityCollection is required.');
  365. }
  366. //>>includeEnd('debug');
  367. entityCollection.collectionChanged.addEventListener(PathVisualizer.prototype._onCollectionChanged, this);
  368. this._scene = scene;
  369. this._updaters = {};
  370. this._entityCollection = entityCollection;
  371. this._items = new AssociativeArray();
  372. this._onCollectionChanged(entityCollection, entityCollection.entities, [], []);
  373. };
  374. /**
  375. * Updates all of the primitives created by this visualizer to match their
  376. * Entity counterpart at the given time.
  377. *
  378. * @param {JulianDate} time The time to update to.
  379. * @returns {Boolean} This function always returns true.
  380. */
  381. PathVisualizer.prototype.update = function(time) {
  382. //>>includeStart('debug', pragmas.debug);
  383. if (!defined(time)) {
  384. throw new DeveloperError('time is required.');
  385. }
  386. //>>includeEnd('debug');
  387. var updaters = this._updaters;
  388. for ( var key in updaters) {
  389. if (updaters.hasOwnProperty(key)) {
  390. updaters[key].update(time);
  391. }
  392. }
  393. var items = this._items.values;
  394. for (var i = 0, len = items.length; i < len; i++) {
  395. var item = items[i];
  396. var entity = item.entity;
  397. var positionProperty = entity._position;
  398. var lastUpdater = entity._pathUpdater;
  399. var frameToVisualize = ReferenceFrame.FIXED;
  400. if (this._scene.mode === SceneMode.SCENE3D) {
  401. frameToVisualize = positionProperty.referenceFrame;
  402. }
  403. var currentUpdater = this._updaters[frameToVisualize];
  404. if ((lastUpdater === currentUpdater) && (defined(currentUpdater))) {
  405. currentUpdater.updateObject(time, item);
  406. continue;
  407. }
  408. if (defined(lastUpdater)) {
  409. lastUpdater.removeObject(item);
  410. }
  411. if (!defined(currentUpdater)) {
  412. currentUpdater = new PolylineUpdater(this._scene, frameToVisualize);
  413. currentUpdater.update(time);
  414. this._updaters[frameToVisualize] = currentUpdater;
  415. }
  416. item.updater = currentUpdater;
  417. if (defined(currentUpdater)) {
  418. currentUpdater.updateObject(time, item);
  419. }
  420. }
  421. return true;
  422. };
  423. /**
  424. * Returns true if this object was destroyed; otherwise, false.
  425. *
  426. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  427. */
  428. PathVisualizer.prototype.isDestroyed = function() {
  429. return false;
  430. };
  431. /**
  432. * Removes and destroys all primitives created by this instance.
  433. */
  434. PathVisualizer.prototype.destroy = function() {
  435. this._entityCollection.collectionChanged.removeEventListener(PathVisualizer.prototype._onCollectionChanged, this);
  436. var updaters = this._updaters;
  437. for ( var key in updaters) {
  438. if (updaters.hasOwnProperty(key)) {
  439. updaters[key].destroy();
  440. }
  441. }
  442. return destroyObject(this);
  443. };
  444. PathVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
  445. var i;
  446. var entity;
  447. var item;
  448. var items = this._items;
  449. for (i = added.length - 1; i > -1; i--) {
  450. entity = added[i];
  451. if (defined(entity._path) && defined(entity._position)) {
  452. items.set(entity.id, new EntityData(entity));
  453. }
  454. }
  455. for (i = changed.length - 1; i > -1; i--) {
  456. entity = changed[i];
  457. if (defined(entity._path) && defined(entity._position)) {
  458. if (!items.contains(entity.id)) {
  459. items.set(entity.id, new EntityData(entity));
  460. }
  461. } else {
  462. item = items.get(entity.id);
  463. if (defined(item)) {
  464. item.updater.removeObject(item);
  465. items.remove(entity.id);
  466. }
  467. }
  468. }
  469. for (i = removed.length - 1; i > -1; i--) {
  470. entity = removed[i];
  471. item = items.get(entity.id);
  472. if (defined(item)) {
  473. item.updater.removeObject(item);
  474. items.remove(entity.id);
  475. }
  476. }
  477. };
  478. //for testing
  479. PathVisualizer._subSample = subSample;
  480. return PathVisualizer;
  481. });