Source: ArrowProvider.js

/**
 * Manages Arrow calculation
 *
 * @alias ArrowProvider
 * @constructor
 *
 * @param {WindDataProvider} windDataProvider The wind data file.
 * @param {Object} [options] Object with the following properties:
 * @param {int} [options.precalcTimesteps] number of timesteps for precalculation
 * @param {float} [options.dseed] d_seed distance
 * @param {float} [options.dsep] d_sep distance
 * @param {Cesium.Rectangle} [options.rect] rect to calculate arrows for
 * @param {float} [options.arrowWidth] width of the arrows
 * @param {int} [options.seedSamplePoints] Number of tested Sampling points per timestep
 * @param {number} [options.scale] Scale for these arrows
 * @param {boolean} [options.random] Distribute arrows randomly, default=false
 */
function ArrowProvider(windDataProvider, options) {
    console.log("Creating ArrowProvider...");

    options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT);
    this._ready = false;
    this.windDataProvider = windDataProvider;

    this._scale = Cesium.defaultValue(options.scale,1);
    this.trajectoryHelper = new TrajectoryHelper(windDataProvider, 20.0/this._scale);

    this._arrows = []; //List of timesteps with list of arrows for each entry
    this.rect = Cesium.defaultValue(options.rect,Cesium.Rectangle.MAX_VALUE);
    this.dseed = Cesium.defaultValue(options.dseed,0);
    this.dsep = Cesium.defaultValue(options.dsep,0);
    this.precalcTimesteps = Cesium.defaultValue(options.precalcTimesteps,10);
    this.arrowWidth = Cesium.defaultValue(options.arrowWidth,1);
    this._currentFirstTime = 0;
    this._seedSamplePoints = Cesium.defaultValue(options.seedSamplePoints,200);
    this._randomDistribution = Cesium.defaultValue(options.random,false);


    this._readyPromise = Cesium.when.defer();
    this.initArrows();
}

/**
 * Returns a list of arrows on the given timestep
 * @param {int} time timestep to return arrows for
 * @return {list} list of {@link Arrow} instances
 */
ArrowProvider.prototype.getArrows = function(time){
    return this._arrows[time];
}

/**
 * Appends a timestep to this arrowprovider
 */
ArrowProvider.prototype.addTimestep = function(){
    this._currentFirstTime++;
    var newTimestep = this.precalcTimesteps+this._currentFirstTime-1;
    console.log("Timestep: " + newTimestep);
    // The forward step
    this.PropagateArrowsOneStep(1,newTimestep);
    this.completeTimeStepWithArrows(newTimestep);
    // Propagate the new frame the number of precalcTimesteps back...
    for (t=newTimestep; t>=this._currentFirstTime; t--){
        dt = 1; //Backward step
        this.PropagateArrowsOneStep(dt,t);
    }
}

/**
 * Initizialize arrows for the first n timesteps
 * @private
 */
ArrowProvider.prototype.initArrows = function(){
    console.log("...init arrows");
    this.completeTimeStepWithArrows(0);
    console.log("...forward pass");
    for (t=1; t<this.precalcTimesteps; t++){
        dt = 1; //Forward step
        this.PropagateArrowsOneStep(dt,t);
        this.completeTimeStepWithArrows(t);
    }
    console.log("...backward pass");
    for (t=this.precalcTimesteps-2; t>=0; t--){
        dt = -1; //Backward step
        this.PropagateArrowsOneStep(dt,t);
    }
    console.log("...done");

    this._ready = true;
    this._readyPromise.resolve(true);
}

/**
 * Returns a list of points, that are uniformly distributed for timestepseeding
 * @private
 * @return {list} list of {@link Point} instances
 */
ArrowProvider.prototype.getSamplePoints = function(){
    //Uniform random distribution of sample points (maybe do something smart here :))
    var points = []
    if (this._randomDistribution){
        for(i = 0; i < this._seedSamplePoints; i++){
            points[i] = new Point(
                Math.random()*this.rect.width+this.rect.west,
                (Math.random()*this.rect.height+this.rect.south)*-1
            );
            // Now we have to convert it to degree:
            points[i].scale(180.0/Math.PI);
        }
    } else {
        sqrtNum = Math.sqrt(this._seedSamplePoints);
        dx = this.rect.height/sqrtNum;
        dy = this.rect.height/sqrtNum;
        i = 0;
        for (x = this.rect.west; x < this.rect.east; x+=dx){
            for (y = this.rect.south; y < this.rect.north; y+=dy){
                points[i] = new Point(
                    x,
                    y*-1
                );
                // Now we have to convert it to degree:
                points[i].scale(180.0/Math.PI);
                i++;
            }
        }
    }
    return points;
}


/**
 * Adds all possible arrows for this timestep
 * @private
 * @param {int} time timestep for calculation
 */
ArrowProvider.prototype.completeTimeStepWithArrows = function(time){
    var samplePoints = this.getSamplePoints();
    if (this._arrows[time] === undefined) this._arrows[time] = [];
    var that = this; // Workaround for function in forEach, that does not know this
    samplePoints.forEach(function (point){
        var newArrow = new Arrow(that.trajectoryHelper);
        newArrow.position[time] = point;
        newArrow.width = that.arrowWidth;
        newArrow.timeBorn = time;
        newArrow.timeDeath = time;
        newArrow.calcDataValues(time);
        if (newArrow.distanceToMultiple(that._arrows[time], time, that.dseed)){
            that._arrows[time].push(newArrow);
        }
    });
}

/**
 * takes all arrows from time-dt and propagates them to time
 * @private
 * @param {int} dt direction of propagation
 * @param {int} time timestep for calculation
 */
ArrowProvider.prototype.PropagateArrowsOneStep = function(dt, time){
    var prevTime = time - dt;
    var candidates = []; //Possible arrows to be used here...
    this._arrows[prevTime].forEach(function(arrow){
        if (arrow.isAlive(prevTime) && !arrow.isAlive(time)){
            // We want you for Arrow Army!
            candidates.push(arrow);
        }
    });
    candidates.sort(function(a, b){
        return b.timeData[prevTime].length-a.timeData[prevTime].length; // sort biggest arrows first
    });
    var that = this; //Workaround
    if (this._arrows[time] === undefined) this._arrows[time] = [];
    candidates.forEach(function(arrow){
        arrow.propagateToTime(prevTime, time);
        if (arrow.distanceToMultiple(that._arrows[time], time, that.dsep)){
            that._arrows[time].push(arrow);
            arrow.timeBorn = Math.min(arrow.timeBorn, time);
            arrow.timeDeath = Math.max(arrow.timeDeath, time);
        }
    });
}