From 09b4a34d142b928c34ae4cd278e9c948d96c37d2 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sun, 20 Jan 2019 20:13:56 +0900 Subject: [PATCH] trying to get smooth delta (because fuck you GDX) --- src/net/torvald/dataclass/CircularArray.kt | 7 +- src/net/torvald/terrarum/AppLoader.java | 103 +++++++++++++++++- src/net/torvald/terrarum/Terrarum.kt | 3 +- .../terrarum/gameactors/ActorWBMovable.kt | 6 +- .../torvald/terrarum/modulebasegame/Ingame.kt | 16 +-- 5 files changed, 113 insertions(+), 22 deletions(-) diff --git a/src/net/torvald/dataclass/CircularArray.kt b/src/net/torvald/dataclass/CircularArray.kt index ed932bd3d..57a99f75d 100644 --- a/src/net/torvald/dataclass/CircularArray.kt +++ b/src/net/torvald/dataclass/CircularArray.kt @@ -21,10 +21,11 @@ class CircularArray(val size: Int) { val lastIndex = size - 1 - /** elemCount == size means it has the exact elements and no more. - * If elemCount is greater by 1 against the size, it means it started to circle, elemCount won't increment at this point. */ + /** + * Number of elements that forEach() or fold() would iterate. + */ val elemCount: Int - get() = unreliableAddCount + get() = minOf(unreliableAddCount, size) fun add(item: T) { if (unreliableAddCount <= size) unreliableAddCount += 1 diff --git a/src/net/torvald/terrarum/AppLoader.java b/src/net/torvald/terrarum/AppLoader.java index 720f312ab..2e13e6d4b 100644 --- a/src/net/torvald/terrarum/AppLoader.java +++ b/src/net/torvald/terrarum/AppLoader.java @@ -16,6 +16,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import net.torvald.dataclass.ArrayListMap; +import net.torvald.dataclass.CircularArray; import net.torvald.terrarum.modulebasegame.IngameRenderer; import net.torvald.terrarum.utils.JsonFetcher; import net.torvald.terrarum.utils.JsonWriter; @@ -24,6 +25,7 @@ import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Random; @@ -162,8 +164,6 @@ public class AppLoader implements ApplicationListener { //appConfig.useGL30 = true; // used: loads GL 3.2, unused: loads GL 4.6; what the fuck? appConfig.vSyncEnabled = false; appConfig.resizable = false;//true; - //appConfig.width = 1072; // IMAX ratio - //appConfig.height = 742; // IMAX ratio appConfig.width = 1110; // photographic ratio (1.5:1) appConfig.height = 740; // photographic ratio (1.5:1) appConfig.backgroundFPS = 9999; @@ -237,6 +237,99 @@ public class AppLoader implements ApplicationListener { updateFullscreenQuad(appConfig.width, appConfig.height); } + private static double _kalman_xhat_k = 1.0 / 60.0; + private static double _kalman_p_k = 1.0; + private static final double _kalman_R = 0.1; + private static boolean _kalman_discard_requested = true; + private static int _kalman_discard_frame_counter = 0; + private final int _KALMAN_FRAMES_TO_DISCARD = 3; + private final double _KALMAN_UPDATE_THRE = 0.1; + + private static final int _DELTA_ITER_AVR_SAMPLESIZE = 25; + private static CircularArray deltaHistory = new CircularArray<>(_DELTA_ITER_AVR_SAMPLESIZE); + private static double deltaAvr = 0.0; + + /** + * Because fuck you GDX. (No, really; take a look at LwjglGraphics.java, getDeltaTime() and rawDeltaTime() are exactly the same) + * @return Render delta that is smoothed out. + */ + public static double getSmoothDelta() { + // kalman filter is calculated but not actually being used. + //return deltaAvr; + + + // below is the kalman part + if (_kalman_discard_requested) + return Gdx.graphics.getRawDeltaTime(); + + return _kalman_xhat_k; + } + + public static void resetDeltaSmoothingHistory() { + _kalman_xhat_k = 1.0 / 60.0; + _kalman_p_k = 1.0; + _kalman_discard_requested = true; + + + deltaHistory = new CircularArray<>(_DELTA_ITER_AVR_SAMPLESIZE); + } + + /** + * @link http://bilgin.esme.org/BitsAndBytes/KalmanFilterforDummies + */ + private void updateKalmanRenderDelta() { + + // TODO implement nonlinear kalman filter or n-frames average + + // kalman filter is calculated but not actually being used. + // the problem with this kalman filter is that it assumes most simplistic situation: + // 1. the actual delta (measured delta - noise) is constant + // 2. everything is linear + // we may need to implement Extended Kalman Filter but wtf is Jacobian, I suck at maths. + + if (_kalman_discard_requested) { + _kalman_discard_frame_counter += 1; + if (_kalman_discard_frame_counter >= _KALMAN_FRAMES_TO_DISCARD) { + _kalman_discard_requested = false; + } + } + else { + // measurement value + double _kalman_zed_k = Gdx.graphics.getRawDeltaTime(); + + if (_kalman_zed_k <= _KALMAN_UPDATE_THRE) { + // time update + double _kalman_xhatminus_k = _kalman_xhat_k; + double _kalman_pminus_k = _kalman_p_k; + + // measurement update + double _kalman_gain = _kalman_pminus_k / (_kalman_pminus_k + _kalman_R); + double _kalman_xhat_kNew = _kalman_xhatminus_k + _kalman_gain * (_kalman_zed_k - _kalman_xhatminus_k); + double _kalman_p_kNew = (1.0 - _kalman_gain) * _kalman_pminus_k; + + _kalman_xhat_k = _kalman_xhat_kNew; + _kalman_p_k = _kalman_p_kNew; + } + } + + + // below is the averaging part + if (Gdx.graphics.getRawDeltaTime() <= _KALMAN_UPDATE_THRE) { + deltaHistory.add(((double) Gdx.graphics.getRawDeltaTime())); + ArrayList middleVals = new ArrayList<>(); + deltaHistory.forEach((it) -> { + middleVals.add(it); + return null; + }); + for (int k = deltaHistory.getElemCount() - 2; k >= 0; k--) { + for (int l = 0; l <= k; l++) { + middleVals.set(l, (middleVals.get(l) + middleVals.get(l + 1)) / 2.0); + } + } + deltaAvr = middleVals.get(0); + } + } + @Override public void render() { @@ -245,6 +338,8 @@ public class AppLoader implements ApplicationListener { postInit(); } + // update smooth delta AFTER postInit + updateKalmanRenderDelta(); FrameBufferManager.begin(renderFBO); gdxClearAndSetBlend(.094f, .094f, .094f, 0f); @@ -341,6 +436,8 @@ public class AppLoader implements ApplicationListener { updateFullscreenQuad(screenW, screenH); printdbg(this, "Resize event"); + + resetDeltaSmoothingHistory(); } @Override @@ -385,6 +482,8 @@ public class AppLoader implements ApplicationListener { } printdbg(this, "Screen transisiton complete: " + this.screen.getClass().getCanonicalName()); + + resetDeltaSmoothingHistory(); } private void postInit() { diff --git a/src/net/torvald/terrarum/Terrarum.kt b/src/net/torvald/terrarum/Terrarum.kt index e9eaf15b5..47689cfef 100644 --- a/src/net/torvald/terrarum/Terrarum.kt +++ b/src/net/torvald/terrarum/Terrarum.kt @@ -411,7 +411,8 @@ object Terrarum : Screen { } override fun render(delta: Float) { - AppLoader.debugTimers["GDX.delta"] = delta.times(1000_000_000f).toLong() + AppLoader.debugTimers["GDX.rawDelta"] = Gdx.graphics.rawDeltaTime.times(1000_000_000f).toLong() + AppLoader.debugTimers["GDX.smtDelta"] = AppLoader.getSmoothDelta().times(1000_000_000f).toLong() AppLoader.getINSTANCE().screen.render(deltaTime) //GLOBAL_RENDER_TIMER += 1 // moved to AppLoader; global event must be place at the apploader to prevent ACCIDENTAL forgot-to-update type of bug. diff --git a/src/net/torvald/terrarum/gameactors/ActorWBMovable.kt b/src/net/torvald/terrarum/gameactors/ActorWBMovable.kt index ce057a2e4..8b946bacb 100644 --- a/src/net/torvald/terrarum/gameactors/ActorWBMovable.kt +++ b/src/net/torvald/terrarum/gameactors/ActorWBMovable.kt @@ -1,5 +1,6 @@ package net.torvald.terrarum.gameactors +import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.g2d.SpriteBatch @@ -358,7 +359,10 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean = override fun update(delta: Float) { if (isUpdate && !flagDespawn) { - val ddelta = delta.toDouble() + val ddelta = Gdx.graphics.rawDeltaTime.toDouble() + //val ddelta = AppLoader.getSmoothDelta() + //println("${Gdx.graphics.rawDeltaTime.toDouble()}\t${AppLoader.getSmoothDelta()}") + if (!assertPrinted) assertInit() diff --git a/src/net/torvald/terrarum/modulebasegame/Ingame.kt b/src/net/torvald/terrarum/modulebasegame/Ingame.kt index a28b2a45b..cb88f5339 100644 --- a/src/net/torvald/terrarum/modulebasegame/Ingame.kt +++ b/src/net/torvald/terrarum/modulebasegame/Ingame.kt @@ -408,7 +408,6 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) { itemOnGrip?.endSecondaryUse(delta) } - protected var updateDeltaCounter = 0.0 protected val renderRate = Terrarum.renderRate private var firstTimeRun = true @@ -439,6 +438,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) { gameFullyLoaded = true + AppLoader.resetDeltaSmoothingHistory() } @@ -446,7 +446,6 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) { /** UPDATE CODE GOES HERE */ - updateDeltaCounter += delta @@ -460,19 +459,6 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) { // else, NOP; } else { - updateDeltaCounter += delta - /*if (delta < 1f / 10f) { // discard async if measured FPS <= 10 - var updateTries = 0 - while (updateDeltaCounter >= renderRate && updateTries < 6) { - AppLoader.debugTimers["Ingame.update"] = measureNanoTime { updateGame(delta) } - updateDeltaCounter -= renderRate - updateTries++ - } - } - else { - AppLoader.debugTimers["Ingame.update"] = measureNanoTime { updateGame(delta) } - }*/ - AppLoader.debugTimers["Ingame.update"] = measureNanoTime { updateGame(delta) } }