Problem with goToFrame and speed ratio

Steps to reproduce the issue in the following Playground:
https://playground.babylonjs.com/#0HJQEB#3

Could it be that “fps * this.speedRatio” should be “fps / this.speedRatio”.
In Animatable.prototype.goToFrame:
var delay = this.speedRatio !== 0 ? adjustTime * 1000 / (fps * this.speedRatio) : 0;

1 Like

Hello and welcome!

Good catch!!
Will fix for next nightly

1 Like

I also have another issue related to speed ratio but for some reason I cannot reproduce it in the Playground.

Given the same Playground (https://playground.babylonjs.com/#0HJQEB#3) but copied to my local example I can reproduce it the following way:

// Press Playback 1x twice (until it says Playback 0.5x)
// Press Play, wait for a while.
// Press Pause
// Press Goto frame 0
// Press Play
// Expected: Sphere moves smoothly from its current position.
// Result Local example: The animation jumps and starts playing from the wrong frame (ie not 0).
// Result Playground: As expected.

I tried using the same BJS version, no idea why the result differ locally vs Playground.

This is my local example:

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html" charset="utf-8"/>
		<title>Babylon - Getting Started</title>
		<script src="https://preview.babylonjs.com/babylon.js"></script>
		<script src="https://preview.babylonjs.com/gui/babylon.gui.js"></script>
		
		<!-- Link to pep.js to ensure pointer events work consistently in all browsers -->
		<script src="../node_modules/pepjs/dist/pep.js"></script>
		<style>html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } html, body { overflow: hidden; margin: 0; padding: 0; height: 100%; width: 100%; font-family: sans-serif; } .container { height: 100%; line-height: 0; } .container #render-canvas { width: 100%; height: calc(100% - 105px); touch-action: none; } @media (max-width: 800px) { .container #render-canvas { height: calc(100% - 210px); } }</style>

	</head>
	<body>
		<div class="container">
			<canvas id="render-canvas"></canvas>
		</div>
		<script>
			window.addEventListener('DOMContentLoaded', async function() {
				var canvas = document.getElementById('render-canvas');
				var engine = new BABYLON.Engine(canvas, true);

				var createScene = function() {
					var scene = new BABYLON.Scene(engine);
					var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
					camera.setTarget(BABYLON.Vector3.Zero());
					camera.attachControl(canvas, true);
					var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
					light.intensity = 0.7;
					var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
					var UiPanel = new BABYLON.GUI.StackPanel();
						UiPanel.width = "220px";
						UiPanel.fontSize = "14px";
						UiPanel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
						UiPanel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER;
					advancedTexture.addControl(UiPanel);

					var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2, segments: 32}, scene);
					sphere.position.y = 1;

					var anim = new BABYLON.Animation('sphereAnim', 'position.y', 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
					anim.setKeys([
						{ frame: 0, value: 1 },
						{ frame: 500, value: 5 }
					]);
					var animGroup =  new BABYLON.AnimationGroup('animGroup', scene);
					animGroup.addTargetedAnimation(anim, sphere);
					var playbackRate = 1;
					var speedPresets = [0.5, 1, 5];
					var selectedSpeedPresetIndex = 1;
					var toggleSpeed = function() {
						selectedSpeedPresetIndex = (selectedSpeedPresetIndex + 1) >= speedPresets.length ? 0 : selectedSpeedPresetIndex + 1;
						playbackRate = speedPresets[selectedSpeedPresetIndex];
					}
					var button = BABYLON.GUI.Button.CreateSimpleButton("but1", "Play");
						button.paddingTop = "10px";
						button.width = "100px";
						button.height = "50px";
						button.color = "white";
						button.background = "green";
						button.onPointerDownObservable.add(()=> {
							animGroup.play();
						});
					UiPanel.addControl(button);
					
					var button = BABYLON.GUI.Button.CreateSimpleButton("but2", "Pause");
						button.paddingTop = "10px";
						button.width = "100px";
						button.height = "50px";
						button.color = "white";
						button.background = "green";
						button.onPointerDownObservable.add(()=> {
							animGroup.pause();
						});
					UiPanel.addControl(button);
					
					var button = BABYLON.GUI.Button.CreateSimpleButton("but3", "Goto frame 0");
						button.paddingTop = "10px";
						button.width = "100px";
						button.height = "50px";
						button.color = "white";
						button.background = "green";
						button.onPointerDownObservable.add(()=> {
							animGroup.pause();
							animGroup.goToFrame(0);
						});
					UiPanel.addControl(button);
					
					var button = BABYLON.GUI.Button.CreateSimpleButton("but4", 'Playback ' + playbackRate + 'x');
						button.paddingTop = "10px";
						button.width = "100px";
						button.height = "50px";
						button.color = "white";
						button.background = "green";
						button.onPointerDownObservable.add(()=> {
							toggleSpeed();
							console.log('button', playbackRate);
							button.textBlock.text = 'Playback ' + playbackRate + 'x';
							animGroup.speedRatio = playbackRate;
						});
					UiPanel.addControl(button);
					
					var button5 = BABYLON.GUI.Button.CreateSimpleButton("but5", 'Goto frame 30');
						button5.paddingTop = "10px";
						button5.width = "100px";
						button5.height = "50px";
						button5.color = "white";
						button5.background = "green";
						button5.onPointerDownObservable.add(()=> {
							animGroup.pause();
							animGroup.goToFrame(30);
						});
					UiPanel.addControl(button5);

					// Our built-in 'ground' shape.
					var ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 6, height: 6}, scene);

					return scene;
				};

				var scene = createScene();

				engine.runRenderLoop(function() {
					scene.render();
				});

				window.addEventListener('resize', function() {
					engine.resize();
				});
			});
		</script>
	</body>
</html>

Can you make sure to init the engine like in the pg:

You mean with the following?

preserveDrawingBuffer: true,
stencil: true

That does not seem to have an effect.

Well this is really weird then. PG is using preview like you. So there should be no difference

Seems a bit weird indeed but maybe I’ve overlooked something trivial.

Did you try to see if you get the same problem locally by downloading my snippet?

Unfortunately I did not have time right now as I’m catching up with 10000 emails to read :slight_smile:

Going back to the original problem. I have to admit I’m not sure I got my head around the delay calculation fully. But could it be that multiplication (ie fps * speedRatio) is correct when speedRatio > 1 and that division correct when speedRatio < 1? ie:

var delay = this.speedRatio !== 0 ? adjustTime * 1000 / (fps * (this.speedRatio > 1 ? this.speedRatio : 1 / this.speedRatio)) : 0;

ho you are right!! I will fix that

I think I got it right eventually, so I made a pull request.

var delay = this.speedRatio === 0 ? 0 : ((frame - currentFrame) / fps * 1000) / this.speedRatio;

1 Like

Thanks!