I did a very basic experiment. And I cannot add enough disclaimers here: This is literally just written down within a few minutes, just to see whether it could work in general.
It assumes that the tileset (i.e. your tileset that you shared in the first post) is provided via a server on localhost
. The basic idea is: There is an FpsTracker
that provides the FPS (averaged over 10 frames. There is an MsseUpdater
that regularly updates the maximumScreenSpaceError
. It does so by checking the average FPS (every second), comparing it to a targetFps
(that is currently set to 50), and based on the deviation from this target, either increases or decreases the MSSE with a certain rate.
From a very quick test, it seems like this could work: When loading your tileset, it starts with the MSSE of 32. Note that initially, the numbers are “distorted” by the fact that it has to load even the initial set of tiles first. But one can see that after a while, it somewhat converges towards a state where it has somewhat stable 50FPS, with an MSSE of ~300:
FPS 36.45643456060094 target 50 change from 32 to 33.6
FPS 41.356492969268764 target 50 change from 33.6 to 35.28
FPS 39.66679888938825 target 50 change from 35.28 to 37.044000000000004
FPS 45.45454545454545 target 50 change from 37.044000000000004 to 38.89620000000001
FPS 46.16805170829732 target 50 change from 38.89620000000001 to 40.84101000000001
FPS 40.338846309238036 target 50 change from 40.84101000000001 to 42.88306050000001
...
FPS 50.81300812988893 target 50 change from 325.4261684785942 to 309.1548600546645
FPS 43.66812227074236 target 50 change from 309.1548600546645 to 324.61260305739773
FPS 50.377833753148614 target 50 change from 324.61260305739773 to 308.38197290452786
FPS 43.290043290043286 target 50 change from 308.38197290452786 to 323.8010715497543
FPS 49.407114624414994 target 50 change from 323.8010715497543 to 339.991125127242
FPS 58.445353594516504 target 50 change from 339.991125127242 to 322.9915688708799
FPS 49.55401387494093 target 50 change from 322.9915688708799 to 339.1411473144239
FPS 58.377116170207266 target 50 change from 339.1411473144239 to 322.1840899487027
There are many degrees of freedom and steering factors. And there certainly will be corner cases. (For example: There might be a tileset where it achieves 60FPS with an MSSE of 100, but only 10FPS with an MSSE of 101 - so there may be no point that it can “converge” to). But the basic approach could be worth another look…
const viewer = new Cesium.Viewer("cesiumContainer", {
terrain: Cesium.Terrain.fromWorldTerrain(),
});
viewer.extend(Cesium.viewerCesium3DTilesInspectorMixin);
const inspectorViewModel = viewer.cesium3DTilesInspector.viewModel;
const position = Cesium.Cartesian3.fromDegrees(
76.61758113143246,
28.192182356673207,
1
);
const orientation = Cesium.Transforms.headingPitchRollQuaternion(
position,
new Cesium.HeadingPitchRoll.fromDegrees(0, 0, 0)
);
const scaling = new Cesium.Cartesian3(1, 1, 1);
const modelMatrix = Cesium.Matrix4.fromTranslationQuaternionRotationScale(
position,
orientation,
scaling
);
const tileset = await Cesium.Cesium3DTileset.fromUrl(
"http://localhost:8003/tileset.json",
{
maximumScreenSpaceError: 32,
}
);
tileset.style = new Cesium.Cesium3DTileStyle({
pointSize: 2.0,
});
viewer.scene.primitives.add(tileset);
tileset.root.transform = Cesium.Matrix4.IDENTITY;
tileset.modelMatrix = modelMatrix;
viewer.scene.debugShowFramesPerSecond = true;
viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider();
viewer.flyTo(tileset);
//============================================================================
class FpsTracker {
constructor() {
this.previousTimeStampMs = undefined;
this.frameTimesMs = [];
this.windowSize = 10;
this.averageFps = 1;
}
update() {
const currentTimeStampMs = Cesium.getTimestamp();
if (this.previousTimeStampMs === undefined) {
this.previousTimeStampMs = currentTimeStampMs;
return;
}
const previousFrameTimeMs = currentTimeStampMs - this.previousTimeStampMs;
this.previousTimeStampMs = currentTimeStampMs;
this.frameTimesMs.push(previousFrameTimeMs);
while (this.frameTimesMs.length > this.windowSize) {
this.frameTimesMs.shift();
}
const frameTimesMsSum = this.frameTimesMs.reduce(function (a, b) {
return a + b;
}, 0);
const averageFrameTimeMs = frameTimesMsSum / this.frameTimesMs.length;
this.averageFps = 1000.0 / averageFrameTimeMs;
//console.log("Average FPS "+this.averageFps+" from "+this.frameTimesMs);
}
getAverageFps() {
return this.averageFps;
}
}
const fpsTracker = new FpsTracker();
class MsseUpdater {
constructor(fpsTracker, tileset) {
this.fpsTracker = fpsTracker;
this.tileset = tileset;
this.previousTimeStampMs = undefined;
this.updateIntervalMs = 1000;
this.targetFps = 50;
this.adjustmentRate = 0.05;
}
update() {
this.fpsTracker.update();
const currentTimeStampMs = Cesium.getTimestamp();
if (this.previousTimeStampMs === undefined) {
this.previousTimeStampMs = currentTimeStampMs;
return;
}
if (currentTimeStampMs - this.previousTimeStampMs < this.updateIntervalMs) {
return;
}
this.previousTimeStampMs = currentTimeStampMs;
const averageFps = this.fpsTracker.getAverageFps();
const deviation = averageFps - this.targetFps;
let newMaximumScreenSpaceError = this.tileset.maximumScreenSpaceError;
if (deviation > 0) {
newMaximumScreenSpaceError *= 1.0 - this.adjustmentRate;
} else if (deviation < 0) {
newMaximumScreenSpaceError *= 1.0 + this.adjustmentRate;
}
console.log(
"FPS " +
averageFps +
" target " +
this.targetFps +
" change from " +
this.tileset.maximumScreenSpaceError +
" to " +
newMaximumScreenSpaceError
);
this.tileset.maximumScreenSpaceError = newMaximumScreenSpaceError;
}
}
const msseUpdater = new MsseUpdater(fpsTracker, tileset);
viewer.scene.preUpdate.addEventListener(() => msseUpdater.update());