this.animationTime_set = function (value) {

    if (this.animationStartTime == null) {
        this.animationStartTime = 0;
    }
    if (this.animationStopTime == null) {
        this.animationStopTime = this.animationDuration;
    }
    // Save copies to avoid repeated reads.
    var duration = this.animationStopTime - this.animationStartTime,
        rate = this.animationRate;
    // Range limit the incoming value.
    value = Math.min(Math.max(this.animationStartTime, value), this.animationStopTime);
    // Keep paused if updating start/pause from null/null. Use t=0 instead of `this.time` so that
    // setting `node.animationTime` during initialization is consistent across multiple clients.
    if (this.animationStartSIM == null) {
        this.animationPauseSIM = 0;
    }
    // Calculate the start and stop times that makes the new time work.
    this.animationStartSIM =
        (this.animationPauseSIM != null ? this.animationPauseSIM : this.time) -
        (rate >= 0 ? value - this.animationStartTime : value - duration) / rate;
    this.animationStopSIM =
        this.animationStartSIM +
        (rate >= 0 ? duration : -duration) / rate;
    // Update the node and fire the changed event.
    if (value !== this.animationTimeUpdated) {
        this.animationTimeUpdated = value;
        this.animationUpdate(value, this.animationDuration);
        this.animationTimeChanged(value);
    } //@ sourceURL=animation.animationTime.set.vwf

}

this.animationTime_get = function () {

    // Save copies to avoid repeated reads.
    var startTime = this.animationStartTime;
    var stopTime = this.animationStopTime;
    var rate = this.animationRate;
    var animationPauseSIM = this.animationPauseSIM;
    var animationStartSIM = this.animationStartSIM;
    var time = this.time;
    // Calculate the time from the start and current/pause times.
    var value = (
        (animationPauseSIM != null ? animationPauseSIM : time) -
        (animationStartSIM != null ? animationStartSIM : time)
    ) * rate + (rate >= 0 ? startTime : stopTime);
    // Range limit the value.
    value = Math.min(Math.max(startTime, value), stopTime);
    // If changed since last seen, update and fire the changed event.
    if (value !== this.animationTimeUpdated) {
        this.animationTimeUpdated = value;
        this.animationUpdate(value, this.animationDuration);
        this.animationTimeChanged(value);
    }
    return value; //@ sourceURL=animation.animationTime.get.vwf
}

this.animationDuration_set = function (value) {
    var duration = value, rate = this.animationRate;
    this.animationDuration = duration;
    this.animationDurationSIM = (rate >= 0 ? duration : -duration) / rate;
}

this.animationRate_set = function (value) {
    var duration = this.animationDuration, rate = value;
    this.animationRate = rate;
    this.animationDurationSIM = (rate >= 0 ? duration : -duration) / rate;
}

this.animationPlaying_set = function (value) {
    if (this.animationStartTime == null) {
        this.animationStartTime = 0;
    }
    if (this.animationStopTime == null) {
        this.animationStopTime = this.animationDuration;
    }
    if (this.animationStartSIM != null && this.animationPauseSIM == null) {
        if (!value) {
            // Mark as paused at the current time.
            this.animationPauseSIM = this.time;
            // Send the `animationStopped` event if stopping at the end.
            if (this.time == this.animationStopSIM) {
                this.animationStopped();
            }
        }
    } else {
        if (value) {
            // Save copies to avoid repeated reads.
            var duration = this.animationStopTime - this.animationStartTime,
                rate = this.animationRate;
            // Start from the beginning if resuming from the end.
            if (this.animationPauseSIM == this.animationStopSIM) {
                this.animationPauseSIM = this.animationStartSIM;
            }
            // Recalculate the start and stop times to keep paused time unchanged, then resume.
            this.animationStartSIM =
                (this.animationStartSIM != null ? this.animationStartSIM : this.time) -
                (this.animationPauseSIM != null ? this.animationPauseSIM : this.time) +
                this.time;
            this.animationStopSIM =
                this.animationStartSIM +
                (rate >= 0 ? duration : -duration) / rate;
            this.animationPauseSIM = null;
            // Send the `animationStarted` event if starting from the beginning.
            if (this.time == this.animationStartSIM) {
                this.animationStarted();
            }
            // Start the animation worker.
            this.logger.debug("scheduling animationTick");
            this.animationTick();
        }
    } //@ sourceURL=animation.animationPlaying.set.vwf

}

this.animationPlaying_get = function () {
    return this.animationStartSIM != null && this.animationPauseSIM == null;
}

this.animationStartTime_set = function (value) {
    this.animationStartTime = value ? Math.min(Math.max(0, value), this.animationDuration) : value;

}

this.animationStopTime_set = function (value) {
    this.animationStopTime = value ? Math.min(Math.max(0, value), this.animationDuration) : value;
}

this.animationStartFrame_set = function (value) {

    this.animationStartTime = value / this.animationFPS
}

this.animationStartFrame_get = function () {
    return Math.floor(this.animationStartTime * this.animationFPS)
}

this.animationStopFrame_set = function (value) {
    this.animationStopTime = value / this.animationFPS
}
this.animationStopFrame_get = function () {
    return Math.floor(this.animationStopTime * this.animationFPS)
}

this.animationFrames_set = function (value) {
    this.animationDuration = value / this.animationFPS
}
this.animationFrames_get = function () {
    return Math.ceil(this.animationFPS * this.animationDuration)
}

this.animationFrame_set = function (value) {
    if (this.animationFPS) {
        this.animationTime = value / this.animationFPS;
    }
}

this.animationFrame_get = function () {
    if (this.animationFPS) {
        return Math.floor(this.animationTime * this.animationFPS);
    }
}

//methods
this.animationPlay = function (startTime, stopTime) {
    if (!isNaN(stopTime)) {
        this.animationStopTime = stopTime;
    }
    if (!isNaN(startTime)) {
        this.animationStartTime = startTime;
    }
    this.animationPlaying = true;
}

this.animationPause = function () {
    this.animationPlaying = false;
}

this.animationResume = function () {
    this.animationPlaying = true;
}

this.animationStop = function () {
    this.animationPlaying = false;
    this.animationTime = 0;
}

this.animationTick = function () {
    if (this.animationPlaying) {
        // Read the time to recognize the current time and update.
        // TODO: move side effects out of the getter!!! (says Kevin)
        this.animationTime;
        // Loop or stop after reaching the end.
        if (this.time === this.animationStopSIM) {
            if (this.animationLoop) {
                this.animationLooped();
                this.animationTime = this.animationRate >= 0 ?
                    this.animationStartTime : this.animationStopTime;
            } else {
                this.animationPlaying = false;
            }
        }
        // Schedule the next tick if still playing.
        if (this.animationPlaying) {
            if (this.animationStopSIM - this.time > 1 / this.animationTPS) {
                this.in(1 / this.animationTPS).animationTick(); // next interval
            } else {

                // TODO: When animationStopSIM is 0 (usually when a model does not actually have an 
                //       animation on it), we schedule a method call for a time in the past (at time 0).
                //       That immediately calls animationTick again, but this.time does not equal 
                //       animationStopSIM as we would expect.  So, it doesn't stop the animation and we get
                //       caught in an infinite loop.
                //       Ideally we should catch the case where animationStopSIM is 0 before this point.
                //       But for now, we catch it here.  
                if (this.animationStopSIM > 0) {
                    this.at(this.animationStopSIM).animationTick(); // exactly at end
                } else {
                    this.animationPlaying = false;
                }
            }
        } else {
            this.logger.debug("canceling animationTick");
        }
    } //@ sourceURL=animation.animationTick.vwf
}

this.animationUpdate = function (time, duration) {
    //console.log("Do on animation update")

}

this.initialize = function () {

    // Locate child nodes that extend or implement "http://vwf.example.com/animation/position.vwf"
    // to identify themselves as animation key positions.

    var positions = this.find("./element(*,'proxy/animation/position.vwf')");

    // Fill in missing `animationTime` properties, distributing evenly between the left and right
    // positions that define `animationTime`.

    // 1: [ - ] => [ 0 ]
    // 1: [ 0, - ] => [ 0, 1 ]
    // 1: [ -, 1 ] => [ 0, 1 ]
    // 1: [ 0, -, - ] => [ 0, 1/2, 1 ]
    // 1: [ -, -, 1 ] => [ 0, 1/2, 1 ]
    // 1: [ 0, - , -, 1 ] => [ 0, 1/3 , 2/3, 1 ]

    var leftTime, leftIndex;
    var rightTime, rightIndex = -Infinity;

    if (positions.length > 0) {

        positions.sort(function (a, b) {
            return a.sequence - b.sequence;
        });

        if (positions[0].animationTime === null) {
            positions[0].animationTime = 0;
        }

        if (positions[positions.length - 1].animationTime === null) {
            positions[positions.length - 1].animationTime = this.animationDuration;
        }

        positions.forEach(function (position, index) {

            if (position.animationTime !== null) {

                leftTime = position.animationTime;
                leftIndex = index;

            } else {

                if (index > rightIndex) {
                    for (rightIndex = index + 1; rightIndex < positions.length; rightIndex++) {
                        if ((rightTime = /* assignment! */ positions[rightIndex].animationTime) !== null) {
                            break;
                        }
                    }
                }

                position.animationTime = leftTime + (rightTime - leftTime) *
                    (index - leftIndex) / (rightIndex - leftIndex);

            }

        }, this);

    }

} //@ sourceURL=http://vwf.example.com/animation.vwf/scripts~initialize