import AssociativeArray from '../Core/AssociativeArray.js';
|
import Cartesian3 from '../Core/Cartesian3.js';
|
import defined from '../Core/defined.js';
|
import destroyObject from '../Core/destroyObject.js';
|
import DeveloperError from '../Core/DeveloperError.js';
|
import JulianDate from '../Core/JulianDate.js';
|
import Matrix3 from '../Core/Matrix3.js';
|
import Matrix4 from '../Core/Matrix4.js';
|
import ReferenceFrame from '../Core/ReferenceFrame.js';
|
import TimeInterval from '../Core/TimeInterval.js';
|
import Transforms from '../Core/Transforms.js';
|
import PolylineCollection from '../Scene/PolylineCollection.js';
|
import SceneMode from '../Scene/SceneMode.js';
|
import CompositePositionProperty from './CompositePositionProperty.js';
|
import ConstantPositionProperty from './ConstantPositionProperty.js';
|
import MaterialProperty from './MaterialProperty.js';
|
import Property from './Property.js';
|
import ReferenceProperty from './ReferenceProperty.js';
|
import SampledPositionProperty from './SampledPositionProperty.js';
|
import ScaledPositionProperty from './ScaledPositionProperty.js';
|
import TimeIntervalCollectionPositionProperty from './TimeIntervalCollectionPositionProperty.js';
|
|
var defaultResolution = 60.0;
|
var defaultWidth = 1.0;
|
|
var scratchTimeInterval = new TimeInterval();
|
var subSampleCompositePropertyScratch = new TimeInterval();
|
var subSampleIntervalPropertyScratch = new TimeInterval();
|
|
function EntityData(entity) {
|
this.entity = entity;
|
this.polyline = undefined;
|
this.index = undefined;
|
this.updater = undefined;
|
}
|
|
function subSampleSampledProperty(property, start, stop, times, updateTime, referenceFrame, maximumStep, startingIndex, result) {
|
var r = startingIndex;
|
//Always step exactly on start (but only use it if it exists.)
|
var tmp;
|
tmp = property.getValueInReferenceFrame(start, referenceFrame, result[r]);
|
if (defined(tmp)) {
|
result[r++] = tmp;
|
}
|
|
var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop);
|
|
//Iterate over all interval times and add the ones that fall in our
|
//time range. Note that times can contain data outside of
|
//the intervals range. This is by design for use with interpolation.
|
var t = 0;
|
var len = times.length;
|
var current = times[t];
|
var loopStop = stop;
|
var sampling = false;
|
var sampleStepsToTake;
|
var sampleStepsTaken;
|
var sampleStepSize;
|
|
while (t < len) {
|
if (!steppedOnNow && JulianDate.greaterThanOrEquals(current, updateTime)) {
|
tmp = property.getValueInReferenceFrame(updateTime, referenceFrame, result[r]);
|
if (defined(tmp)) {
|
result[r++] = tmp;
|
}
|
steppedOnNow = true;
|
}
|
if (JulianDate.greaterThan(current, start) && JulianDate.lessThan(current, loopStop) && !current.equals(updateTime)) {
|
tmp = property.getValueInReferenceFrame(current, referenceFrame, result[r]);
|
if (defined(tmp)) {
|
result[r++] = tmp;
|
}
|
}
|
|
if (t < (len - 1)) {
|
if (maximumStep > 0 && !sampling) {
|
var next = times[t + 1];
|
var secondsUntilNext = JulianDate.secondsDifference(next, current);
|
sampling = secondsUntilNext > maximumStep;
|
|
if (sampling) {
|
sampleStepsToTake = Math.ceil(secondsUntilNext / maximumStep);
|
sampleStepsTaken = 0;
|
sampleStepSize = secondsUntilNext / Math.max(sampleStepsToTake, 2);
|
sampleStepsToTake = Math.max(sampleStepsToTake - 1, 1);
|
}
|
}
|
|
if (sampling && sampleStepsTaken < sampleStepsToTake) {
|
current = JulianDate.addSeconds(current, sampleStepSize, new JulianDate());
|
sampleStepsTaken++;
|
continue;
|
}
|
}
|
sampling = false;
|
t++;
|
current = times[t];
|
}
|
|
//Always step exactly on stop (but only use it if it exists.)
|
tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[r]);
|
if (defined(tmp)) {
|
result[r++] = tmp;
|
}
|
|
return r;
|
}
|
|
function subSampleGenericProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
|
var tmp;
|
var i = 0;
|
var index = startingIndex;
|
var time = start;
|
var stepSize = Math.max(maximumStep, 60);
|
var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop);
|
while (JulianDate.lessThan(time, stop)) {
|
if (!steppedOnNow && JulianDate.greaterThanOrEquals(time, updateTime)) {
|
steppedOnNow = true;
|
tmp = property.getValueInReferenceFrame(updateTime, referenceFrame, result[index]);
|
if (defined(tmp)) {
|
result[index] = tmp;
|
index++;
|
}
|
}
|
tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]);
|
if (defined(tmp)) {
|
result[index] = tmp;
|
index++;
|
}
|
i++;
|
time = JulianDate.addSeconds(start, stepSize * i, new JulianDate());
|
}
|
//Always sample stop.
|
tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[index]);
|
if (defined(tmp)) {
|
result[index] = tmp;
|
index++;
|
}
|
return index;
|
}
|
|
function subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
|
subSampleIntervalPropertyScratch.start = start;
|
subSampleIntervalPropertyScratch.stop = stop;
|
|
var index = startingIndex;
|
var intervals = property.intervals;
|
for (var i = 0; i < intervals.length; i++) {
|
var interval = intervals.get(i);
|
if (!TimeInterval.intersect(interval, subSampleIntervalPropertyScratch, scratchTimeInterval).isEmpty) {
|
var time = interval.start;
|
if (!interval.isStartIncluded) {
|
if (interval.isStopIncluded) {
|
time = interval.stop;
|
} else {
|
time = JulianDate.addSeconds(interval.start, JulianDate.secondsDifference(interval.stop, interval.start) / 2, new JulianDate());
|
}
|
}
|
var tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]);
|
if (defined(tmp)) {
|
result[index] = tmp;
|
index++;
|
}
|
}
|
}
|
return index;
|
}
|
|
function subSampleConstantProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
|
var tmp = property.getValueInReferenceFrame(start, referenceFrame, result[startingIndex]);
|
if (defined(tmp)) {
|
result[startingIndex++] = tmp;
|
}
|
return startingIndex;
|
}
|
|
function subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
|
subSampleCompositePropertyScratch.start = start;
|
subSampleCompositePropertyScratch.stop = stop;
|
|
var index = startingIndex;
|
var intervals = property.intervals;
|
for (var i = 0; i < intervals.length; i++) {
|
var interval = intervals.get(i);
|
if (!TimeInterval.intersect(interval, subSampleCompositePropertyScratch, scratchTimeInterval).isEmpty) {
|
var intervalStart = interval.start;
|
var intervalStop = interval.stop;
|
|
var sampleStart = start;
|
if (JulianDate.greaterThan(intervalStart, sampleStart)) {
|
sampleStart = intervalStart;
|
}
|
|
var sampleStop = stop;
|
if (JulianDate.lessThan(intervalStop, sampleStop)) {
|
sampleStop = intervalStop;
|
}
|
|
index = reallySubSample(interval.data, sampleStart, sampleStop, updateTime, referenceFrame, maximumStep, index, result);
|
}
|
}
|
return index;
|
}
|
|
function reallySubSample(property, start, stop, updateTime, referenceFrame, maximumStep, index, result) {
|
//Unwrap any references until we have the actual property.
|
while (property instanceof ReferenceProperty) {
|
property = property.resolvedProperty;
|
}
|
|
if (property instanceof SampledPositionProperty) {
|
var times = property._property._times;
|
index = subSampleSampledProperty(property, start, stop, times, updateTime, referenceFrame, maximumStep, index, result);
|
} else if (property instanceof CompositePositionProperty) {
|
index = subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
|
} else if (property instanceof TimeIntervalCollectionPositionProperty) {
|
index = subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
|
} else if (property instanceof ConstantPositionProperty ||
|
(property instanceof ScaledPositionProperty && Property.isConstant(property))) {
|
index = subSampleConstantProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
|
} else {
|
//Fallback to generic sampling.
|
index = subSampleGenericProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
|
}
|
return index;
|
}
|
|
function subSample(property, start, stop, updateTime, referenceFrame, maximumStep, result) {
|
if (!defined(result)) {
|
result = [];
|
}
|
|
var length = reallySubSample(property, start, stop, updateTime, referenceFrame, maximumStep, 0, result);
|
result.length = length;
|
return result;
|
}
|
|
var toFixedScratch = new Matrix3();
|
function PolylineUpdater(scene, referenceFrame) {
|
this._unusedIndexes = [];
|
this._polylineCollection = new PolylineCollection();
|
this._scene = scene;
|
this._referenceFrame = referenceFrame;
|
scene.primitives.add(this._polylineCollection);
|
}
|
|
PolylineUpdater.prototype.update = function(time) {
|
if (this._referenceFrame === ReferenceFrame.INERTIAL) {
|
var toFixed = Transforms.computeIcrfToFixedMatrix(time, toFixedScratch);
|
if (!defined(toFixed)) {
|
toFixed = Transforms.computeTemeToPseudoFixedMatrix(time, toFixedScratch);
|
}
|
Matrix4.fromRotationTranslation(toFixed, Cartesian3.ZERO, this._polylineCollection.modelMatrix);
|
}
|
};
|
|
PolylineUpdater.prototype.updateObject = function(time, item) {
|
var entity = item.entity;
|
var pathGraphics = entity._path;
|
var positionProperty = entity._position;
|
|
var sampleStart;
|
var sampleStop;
|
var showProperty = pathGraphics._show;
|
var polyline = item.polyline;
|
var show = entity.isShowing && (!defined(showProperty) || showProperty.getValue(time));
|
|
//While we want to show the path, there may not actually be anything to show
|
//depending on lead/trail settings. Compute the interval of the path to
|
//show and check against actual availability.
|
if (show) {
|
var leadTime = Property.getValueOrUndefined(pathGraphics._leadTime, time);
|
var trailTime = Property.getValueOrUndefined(pathGraphics._trailTime, time);
|
var availability = entity._availability;
|
var hasAvailability = defined(availability);
|
var hasLeadTime = defined(leadTime);
|
var hasTrailTime = defined(trailTime);
|
|
//Objects need to have either defined availability or both a lead and trail time in order to
|
//draw a path (since we can't draw "infinite" paths.
|
show = hasAvailability || (hasLeadTime && hasTrailTime);
|
|
//The final step is to compute the actual start/stop times of the path to show.
|
//If current time is outside of the availability interval, there's a chance that
|
//we won't have to draw anything anyway.
|
if (show) {
|
if (hasTrailTime) {
|
sampleStart = JulianDate.addSeconds(time, -trailTime, new JulianDate());
|
}
|
if (hasLeadTime) {
|
sampleStop = JulianDate.addSeconds(time, leadTime, new JulianDate());
|
}
|
|
if (hasAvailability) {
|
var start = availability.start;
|
var stop = availability.stop;
|
|
if (!hasTrailTime || JulianDate.greaterThan(start, sampleStart)) {
|
sampleStart = start;
|
}
|
|
if (!hasLeadTime || JulianDate.lessThan(stop, sampleStop)) {
|
sampleStop = stop;
|
}
|
}
|
show = JulianDate.lessThan(sampleStart, sampleStop);
|
}
|
}
|
|
if (!show) {
|
//don't bother creating or updating anything else
|
if (defined(polyline)) {
|
this._unusedIndexes.push(item.index);
|
item.polyline = undefined;
|
polyline.show = false;
|
item.index = undefined;
|
}
|
return;
|
}
|
|
if (!defined(polyline)) {
|
var unusedIndexes = this._unusedIndexes;
|
var length = unusedIndexes.length;
|
if (length > 0) {
|
var index = unusedIndexes.pop();
|
polyline = this._polylineCollection.get(index);
|
item.index = index;
|
} else {
|
item.index = this._polylineCollection.length;
|
polyline = this._polylineCollection.add();
|
}
|
polyline.id = entity;
|
item.polyline = polyline;
|
}
|
|
var resolution = Property.getValueOrDefault(pathGraphics._resolution, time, defaultResolution);
|
|
polyline.show = true;
|
polyline.positions = subSample(positionProperty, sampleStart, sampleStop, time, this._referenceFrame, resolution, polyline.positions.slice());
|
polyline.material = MaterialProperty.getValue(time, pathGraphics._material, polyline.material);
|
polyline.width = Property.getValueOrDefault(pathGraphics._width, time, defaultWidth);
|
polyline.distanceDisplayCondition = Property.getValueOrUndefined(pathGraphics._distanceDisplayCondition, time, polyline.distanceDisplayCondition);
|
};
|
|
PolylineUpdater.prototype.removeObject = function(item) {
|
var polyline = item.polyline;
|
if (defined(polyline)) {
|
this._unusedIndexes.push(item.index);
|
item.polyline = undefined;
|
polyline.show = false;
|
polyline.id = undefined;
|
item.index = undefined;
|
}
|
};
|
|
PolylineUpdater.prototype.destroy = function() {
|
this._scene.primitives.remove(this._polylineCollection);
|
return destroyObject(this);
|
};
|
|
/**
|
* A {@link Visualizer} which maps {@link Entity#path} to a {@link Polyline}.
|
* @alias PathVisualizer
|
* @constructor
|
*
|
* @param {Scene} scene The scene the primitives will be rendered in.
|
* @param {EntityCollection} entityCollection The entityCollection to visualize.
|
*/
|
function PathVisualizer(scene, entityCollection) {
|
//>>includeStart('debug', pragmas.debug);
|
if (!defined(scene)) {
|
throw new DeveloperError('scene is required.');
|
}
|
if (!defined(entityCollection)) {
|
throw new DeveloperError('entityCollection is required.');
|
}
|
//>>includeEnd('debug');
|
|
entityCollection.collectionChanged.addEventListener(PathVisualizer.prototype._onCollectionChanged, this);
|
|
this._scene = scene;
|
this._updaters = {};
|
this._entityCollection = entityCollection;
|
this._items = new AssociativeArray();
|
|
this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
|
}
|
|
/**
|
* Updates all of the primitives created by this visualizer to match their
|
* Entity counterpart at the given time.
|
*
|
* @param {JulianDate} time The time to update to.
|
* @returns {Boolean} This function always returns true.
|
*/
|
PathVisualizer.prototype.update = function(time) {
|
//>>includeStart('debug', pragmas.debug);
|
if (!defined(time)) {
|
throw new DeveloperError('time is required.');
|
}
|
//>>includeEnd('debug');
|
|
var updaters = this._updaters;
|
for (var key in updaters) {
|
if (updaters.hasOwnProperty(key)) {
|
updaters[key].update(time);
|
}
|
}
|
|
var items = this._items.values;
|
if (items.length === 0 && defined(this._updaters) && Object.keys(this._updaters).length > 0) {
|
for (var u in updaters) {
|
if (updaters.hasOwnProperty(u)) {
|
updaters[u].destroy();
|
}
|
}
|
this._updaters = {};
|
}
|
|
for (var i = 0, len = items.length; i < len; i++) {
|
var item = items[i];
|
var entity = item.entity;
|
var positionProperty = entity._position;
|
|
var lastUpdater = item.updater;
|
|
var frameToVisualize = ReferenceFrame.FIXED;
|
if (this._scene.mode === SceneMode.SCENE3D) {
|
frameToVisualize = positionProperty.referenceFrame;
|
}
|
|
var currentUpdater = this._updaters[frameToVisualize];
|
|
if ((lastUpdater === currentUpdater) && (defined(currentUpdater))) {
|
currentUpdater.updateObject(time, item);
|
continue;
|
}
|
|
if (defined(lastUpdater)) {
|
lastUpdater.removeObject(item);
|
}
|
|
if (!defined(currentUpdater)) {
|
currentUpdater = new PolylineUpdater(this._scene, frameToVisualize);
|
currentUpdater.update(time);
|
this._updaters[frameToVisualize] = currentUpdater;
|
}
|
|
item.updater = currentUpdater;
|
if (defined(currentUpdater)) {
|
currentUpdater.updateObject(time, item);
|
}
|
}
|
return true;
|
};
|
|
/**
|
* Returns true if this object was destroyed; otherwise, false.
|
*
|
* @returns {Boolean} True if this object was destroyed; otherwise, false.
|
*/
|
PathVisualizer.prototype.isDestroyed = function() {
|
return false;
|
};
|
|
/**
|
* Removes and destroys all primitives created by this instance.
|
*/
|
PathVisualizer.prototype.destroy = function() {
|
this._entityCollection.collectionChanged.removeEventListener(PathVisualizer.prototype._onCollectionChanged, this);
|
|
var updaters = this._updaters;
|
for ( var key in updaters) {
|
if (updaters.hasOwnProperty(key)) {
|
updaters[key].destroy();
|
}
|
}
|
|
return destroyObject(this);
|
};
|
|
PathVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
|
var i;
|
var entity;
|
var item;
|
var items = this._items;
|
|
for (i = added.length - 1; i > -1; i--) {
|
entity = added[i];
|
if (defined(entity._path) && defined(entity._position)) {
|
items.set(entity.id, new EntityData(entity));
|
}
|
}
|
|
for (i = changed.length - 1; i > -1; i--) {
|
entity = changed[i];
|
if (defined(entity._path) && defined(entity._position)) {
|
if (!items.contains(entity.id)) {
|
items.set(entity.id, new EntityData(entity));
|
}
|
} else {
|
item = items.get(entity.id);
|
if (defined(item)) {
|
if (defined(item.updater)) {
|
item.updater.removeObject(item);
|
}
|
items.remove(entity.id);
|
}
|
}
|
}
|
|
for (i = removed.length - 1; i > -1; i--) {
|
entity = removed[i];
|
item = items.get(entity.id);
|
if (defined(item)) {
|
if (defined(item.updater)) {
|
item.updater.removeObject(item);
|
}
|
items.remove(entity.id);
|
}
|
}
|
};
|
|
//for testing
|
PathVisualizer._subSample = subSample;
|
export default PathVisualizer;
|