/**
* @module
*/
// Apache v2 license
// https://github.com/TerriaJS/terriajs/blob/
// ebd382a8278a817fce316730d9e459bbb9b829e9/lib/Models/Cesium.js
const exports = function(ol3d, debug) {
this.ol3d = ol3d;
this.scene_ = ol3d.getCesiumScene();
this.canvas_ = this.scene_.canvas;
this.verboseRendering = debug;
this._boundNotifyRepaintRequired = this.notifyRepaintRequired.bind(this);
this.lastCameraViewMatrix_ = new Cesium.Matrix4();
this.lastCameraMoveTime_ = 0;
this.stoppedRendering = false;
this._removePostRenderListener = this.scene_.postRender.addEventListener(this.postRender.bind(this));
this.repaintEventNames_ = [
'mousemove', 'mousedown', 'mouseup',
'touchstart', 'touchend', 'touchmove',
'pointerdown', 'pointerup', 'pointermove',
'wheel'
];
const CameraPrototype = Cesium.Camera.prototype;
this.interceptedAPIs_ = [
[CameraPrototype, 'setView'],
[CameraPrototype, 'move'],
[CameraPrototype, 'rotate'],
[CameraPrototype, 'lookAt'],
[CameraPrototype, 'flyTo'],
[CameraPrototype, 'flyToHome'],
[CameraPrototype, 'flyToBoundingSphere']
];
this.originalAPIs_ = this.interceptedAPIs_.map(tuple => tuple[0][tuple[1]]);
this.originalLoadWithXhr_ = Cesium.loadWithXhr.load;
this.originalScheduleTask_ = Cesium.TaskProcessor.prototype.scheduleTask;
this.enable();
};
/**
* Enable.
*/
exports.prototype.enable = function() {
for (const repaintKey of this.repaintEventNames_) {
this.canvas_.addEventListener(repaintKey, this._boundNotifyRepaintRequired, false);
}
window.addEventListener('resize', this._boundNotifyRepaintRequired, false);
// Hacky way to force a repaint when an async load request completes
const that = this;
Cesium.loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType, preferText, timeout) {
deferred['promise']['always'](that._boundNotifyRepaintRequired);
that.originalLoadWithXhr_(...arguments); // eslint-disable-line prefer-rest-params
};
// Hacky way to force a repaint when a web worker sends something back.
Cesium.TaskProcessor.prototype.scheduleTask = function(parameters, transferableObjects) {
const result = that.originalScheduleTask_.call(this, parameters, transferableObjects);
const taskProcessor = this;
if (!taskProcessor._originalWorkerMessageSinkRepaint) {
const worker = taskProcessor['_worker'];
taskProcessor._originalWorkerMessageSinkRepaint = worker.onmessage;
worker.onmessage = function(event) {
taskProcessor._originalWorkerMessageSinkRepaint(event);
that.notifyRepaintRequired();
};
}
return result;
};
// Intercept API calls to trigger a repaint
for (let i = 0; i < this.interceptedAPIs_.length; ++i) {
const api = this.interceptedAPIs_[i];
const parent = api[0];
const original = this.originalAPIs_[i];
// Not using an arrow function to keep the "this" unbounded.
parent[api[1]] = function(...args) {
original.apply(this, args);
that.notifyRepaintRequired();
};
}
// Listen for changes on the layer group
this.ol3d.getOlMap().getLayerGroup().on('change', this._boundNotifyRepaintRequired);
};
/**
* Disable.
*/
exports.prototype.disable = function() {
if (!!this._removePostRenderListener) {
this._removePostRenderListener();
this._removePostRenderListener = undefined;
}
for (const repaintKey of this.repaintEventNames_) {
this.canvas_.removeEventListener(repaintKey, this._boundNotifyRepaintRequired, false);
}
window.removeEventListener('resize', this._boundNotifyRepaintRequired, false);
Cesium.loadWithXhr.load = this.originalLoadWithXhr_;
Cesium.TaskProcessor.prototype.scheduleTask = this.originalScheduleTask_;
// Restore original APIs
for (let i = 0; i < this.interceptedAPIs_.length; ++i) {
const api = this.interceptedAPIs_[i];
const parent = api[0];
const original = this.originalAPIs_[i];
parent[api[1]] = original;
}
this.ol3d.getOlMap().getLayerGroup().un('change', this._boundNotifyRepaintRequired);
};
/**
* @param {number} date
*/
exports.prototype.postRender = function(date) {
// We can safely stop rendering when:
// - the camera position hasn't changed in over a second,
// - there are no tiles waiting to load, and
// - the clock is not animating
// - there are no tweens in progress
const now = Date.now();
const scene = this.scene_;
const camera = scene.camera;
if (!Cesium.Matrix4.equalsEpsilon(this.lastCameraViewMatrix_, camera.viewMatrix, 1e-5)) {
this.lastCameraMoveTime_ = now;
}
const cameraMovedInLastSecond = now - this.lastCameraMoveTime_ < 1000;
const surface = scene.globe['_surface'];
const tilesWaiting = !surface['tileProvider'].ready ||
surface['_tileLoadQueueHigh'].length > 0 ||
surface['_tileLoadQueueMedium'].length > 0 ||
surface['_tileLoadQueueLow'].length > 0 ||
surface['_debug']['tilesWaitingForChildren'] > 0;
const tweens = scene['tweens'];
if (!cameraMovedInLastSecond && !tilesWaiting && tweens.length == 0) {
if (this.verboseRendering) {
console.log(`stopping rendering @ ${Date.now()}`);
}
this.ol3d.setBlockCesiumRendering(true);
this.stoppedRendering = true;
}
Cesium.Matrix4.clone(camera.viewMatrix, this.lastCameraViewMatrix_);
};
/**
* Restart render loop.
* Force a restart of the render loop.
* @api
*/
exports.prototype.restartRenderLoop = function() {
this.notifyRepaintRequired();
};
/**
* Notifies the viewer that a repaint is required.
*/
exports.prototype.notifyRepaintRequired = function() {
if (this.verboseRendering && this.stoppedRendering) {
console.log(`starting rendering @ ${Date.now()}`);
}
this.lastCameraMoveTime_ = Date.now();
// TODO: do not unblock if not blocked by us
this.ol3d.setBlockCesiumRendering(false);
this.stoppedRendering = false;
};
/**
* @param {boolean} debug
* @api
*/
exports.prototype.setDebug = function(debug) {
this.verboseRendering = debug;
};
export default exports;