Compare commits

...

6 commits

Author SHA1 Message Date
cyn
6e64b22dee
Merge 8a2e04cd99 into 0d8e4a5330 2024-10-30 22:30:43 -06:00
Cameron Taylor
0d8e4a5330
fix: re-enable precise chart editor scrolling, and also fix smooth scroll playhead/playbar playback (#3806) 2024-10-30 12:41:28 -04:00
cyn0x8
8a2e04cd99
prevent DCE 2024-10-11 11:15:57 -07:00
cyn
ac97fe2aa0
Merge branch 'develop' into smoothlerp 2024-10-11 11:11:25 -07:00
cyn0x8
75225d3895
convert all coolLerp to smoothLerpPrecision 2024-10-07 17:49:31 -07:00
cyn0x8
51af49e848
mathutil improvements 2024-10-07 17:03:40 -07:00
12 changed files with 142 additions and 60 deletions

View file

@ -234,7 +234,7 @@ class GameOverSubState extends MusicBeatSubState
}
// Smoothly lerp the camera
FlxG.camera.zoom = MathUtil.smoothLerp(FlxG.camera.zoom, targetCameraZoom, elapsed, CAMERA_ZOOM_DURATION);
FlxG.camera.zoom = MathUtil.smoothLerpPrecision(FlxG.camera.zoom, targetCameraZoom, elapsed, CAMERA_ZOOM_DURATION);
//
// Handle user inputs.

View file

@ -210,7 +210,7 @@ class HealthIcon extends FunkinSprite
lerpIconSize();
// Lerp the health icon back to its normal angle.
this.angle = MathUtil.coolLerp(this.angle, 0, 0.15);
this.angle = MathUtil.smoothLerpPrecision(this.angle, 0, elapsed, 0.54);
}
this.updatePosition();
@ -228,7 +228,7 @@ class HealthIcon extends FunkinSprite
if (this.width > this.height)
{
// Apply linear interpolation while accounting for frame rate.
var targetSize:Int = Std.int(MathUtil.coolLerp(this.width, HEALTH_ICON_SIZE * this.size.x, 0.15));
var targetSize:Int = Std.int(MathUtil.smoothLerpPrecision(this.width, HEALTH_ICON_SIZE * this.size.x, FlxG.elapsed, 0.54));
if (force) targetSize = Std.int(HEALTH_ICON_SIZE * this.size.x);
@ -236,7 +236,7 @@ class HealthIcon extends FunkinSprite
}
else
{
var targetSize:Int = Std.int(MathUtil.coolLerp(this.height, HEALTH_ICON_SIZE * this.size.y, 0.15));
var targetSize:Int = Std.int(MathUtil.smoothLerpPrecision(this.height, HEALTH_ICON_SIZE * this.size.y, FlxG.elapsed, 0.54));
if (force) targetSize = Std.int(HEALTH_ICON_SIZE * this.size.y);

View file

@ -221,8 +221,8 @@ class Alphabet extends FlxSpriteGroup
{
var scaledY = FlxMath.remapToRange(targetY, 0, 1, 0, 1.3);
y = MathUtil.coolLerp(y, (scaledY * 120) + (FlxG.height * 0.48), 0.16);
x = MathUtil.coolLerp(x, (targetY * 20) + 90, 0.16);
y = MathUtil.smoothLerpPrecision(y, (scaledY * 120) + (FlxG.height * 0.48), elapsed, 0.5);
x = MathUtil.smoothLerpPrecision(x, (targetY * 20) + 90, elapsed, 0.5);
}
super.update(elapsed);

View file

@ -40,7 +40,7 @@ class MenuItem extends FlxSpriteGroup
override function update(elapsed:Float)
{
super.update(elapsed);
y = MathUtil.coolLerp(y, (targetY * 120) + 480, 0.17);
y = MathUtil.smoothLerpPrecision(y, (targetY * 120) + 480, elapsed, 0.475);
if (isFlashing)
{

View file

@ -52,7 +52,6 @@ class CharSelectSubState extends MusicBeatSubState
var cursorOffsetX:Float = -16;
var cursorOffsetY:Float = -48;
var cursorLocIntended:FlxPoint = new FlxPoint(0, 0);
var lerpAmnt:Float = 0.95;
var tmrFrames:Int = 60;
var currentStage:Stage;
var playerChill:CharSelectPlayer;
@ -885,14 +884,14 @@ class CharSelectSubState extends MusicBeatSubState
cursorLocIntended.x += cursorOffsetX;
cursorLocIntended.y += cursorOffsetY;
cursor.x = MathUtil.smoothLerp(cursor.x, cursorLocIntended.x, elapsed, 0.1);
cursor.y = MathUtil.smoothLerp(cursor.y, cursorLocIntended.y, elapsed, 0.1);
cursor.x = MathUtil.snap(MathUtil.smoothLerpPrecision(cursor.x, cursorLocIntended.x, elapsed, 0.1), cursorLocIntended.x, 1);
cursor.y = MathUtil.snap(MathUtil.smoothLerpPrecision(cursor.y, cursorLocIntended.y, elapsed, 0.1), cursorLocIntended.y, 1);
cursorBlue.x = MathUtil.coolLerp(cursorBlue.x, cursor.x, lerpAmnt * 0.4);
cursorBlue.y = MathUtil.coolLerp(cursorBlue.y, cursor.y, lerpAmnt * 0.4);
cursorBlue.x = MathUtil.smoothLerpPrecision(cursorBlue.x, cursor.x, elapsed, 0.2);
cursorBlue.y = MathUtil.smoothLerpPrecision(cursorBlue.y, cursor.y, elapsed, 0.2);
cursorDarkBlue.x = MathUtil.coolLerp(cursorDarkBlue.x, cursorLocIntended.x, lerpAmnt * 0.2);
cursorDarkBlue.y = MathUtil.coolLerp(cursorDarkBlue.y, cursorLocIntended.y, lerpAmnt * 0.2);
cursorDarkBlue.x = MathUtil.smoothLerpPrecision(cursorDarkBlue.x, cursorLocIntended.x, elapsed, 0.4);
cursorDarkBlue.y = MathUtil.smoothLerpPrecision(cursorDarkBlue.y, cursorLocIntended.y, elapsed, 0.4);
cursorConfirmed.x = cursor.x - 2;
cursorConfirmed.y = cursor.y - 4;

View file

@ -2729,6 +2729,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
// Update the conductor and audio tracks to match.
currentScrollEase = d.value;
easeSongToScrollPosition(currentScrollEase);
}
}
@ -2741,7 +2742,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
playbarHeadDraggingWasPlaying = false;
// Disabled code to resume song playback on drag.
// startAudioPlayback();
startAudioPlayback();
}
}
@ -3873,7 +3874,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
// Mouse Wheel = Scroll
if (FlxG.mouse.wheel != 0 && !FlxG.keys.pressed.CONTROL)
if (FlxG.mouse.wheel != 0)
{
scrollAmount = -50 * FlxG.mouse.wheel;
shouldPause = true;
@ -4469,6 +4470,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
0, songLengthInPixels);
currentScrollEase = clickedPosInPixels;
easeSongToScrollPosition(currentScrollEase);
}
else if (scrollAnchorScreenPos != null)
{

View file

@ -1400,8 +1400,8 @@ class FreeplayState extends MusicBeatSubState
}
}
lerpScore = MathUtil.smoothLerp(lerpScore, intendedScore, elapsed, 0.5);
lerpCompletion = MathUtil.smoothLerp(lerpCompletion, intendedCompletion, elapsed, 0.5);
lerpScore = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpScore, intendedScore, elapsed, 0.2), intendedScore, 1);
lerpCompletion = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpCompletion, intendedCompletion, elapsed, 0.5), intendedCompletion, 1 / 100);
if (Math.isNaN(lerpScore))
{

View file

@ -685,8 +685,8 @@ class SongMenuItem extends FlxSpriteGroup
if (doLerp)
{
x = MathUtil.coolLerp(x, targetPos.x, 0.3);
y = MathUtil.coolLerp(y, targetPos.y, 0.4);
x = MathUtil.smoothLerpPrecision(x, targetPos.x, elapsed, 0.27);
y = MathUtil.smoothLerpPrecision(y, targetPos.y, elapsed, 0.2);
}
super.update(elapsed);

View file

@ -79,8 +79,8 @@ class FunkinSoundTray extends FlxSoundTray
override public function update(MS:Float):Void
{
y = MathUtil.coolLerp(y, lerpYPos, 0.1);
alpha = MathUtil.coolLerp(alpha, alphaTarget, 0.25);
y = MathUtil.smoothLerpPrecision(y, lerpYPos, MS / 1000, 0.8);
alpha = MathUtil.smoothLerpPrecision(alpha, alphaTarget, MS / 1000, 0.325);
var shouldHide = (FlxG.sound.muted == false && FlxG.sound.volume > 0);

View file

@ -49,7 +49,7 @@ class LevelTitle extends FlxSpriteGroup
public override function update(elapsed:Float):Void
{
this.y = MathUtil.coolLerp(y, targetY, 0.17);
this.y = MathUtil.smoothLerpPrecision(y, targetY, elapsed, 0.5);
if (isFlashing)
{

View file

@ -311,7 +311,7 @@ class StoryMenuState extends MusicBeatState
{
Conductor.instance.update();
highScoreLerp = Std.int(MathUtil.smoothLerp(highScoreLerp, highScore, elapsed, 0.25));
highScoreLerp = Std.int(MathUtil.snap(MathUtil.smoothLerpPrecision(highScoreLerp, highScore, elapsed, 0.25), highScore, 1));
scoreText.text = 'LEVEL SCORE: ${Math.round(highScoreLerp)}';

View file

@ -3,7 +3,7 @@ package funkin.util;
/**
* Utilities for performing mathematical operations.
*/
class MathUtil
@:keep class MathUtil
{
/**
* Euler's constant and the base of the natural logarithm.
@ -11,32 +11,6 @@ class MathUtil
*/
public static final E:Float = 2.71828182845904523536;
/**
* Perform linear interpolation between the base and the target, based on the current framerate.
* @param base The starting value, when `progress <= 0`.
* @param target The ending value, when `progress >= 1`.
* @param ratio Value used to interpolate between `base` and `target`.
*
* @return The interpolated value.
*/
@:deprecated('Use smoothLerp instead')
public static function coolLerp(base:Float, target:Float, ratio:Float):Float
{
return base + cameraLerp(ratio) * (target - base);
}
/**
* Perform linear interpolation based on the current framerate.
* @param lerp Value used to interpolate between `base` and `target`.
*
* @return The interpolated value.
*/
@:deprecated('Use smoothLerp instead')
public static function cameraLerp(lerp:Float):Float
{
return lerp * (FlxG.elapsed / (1 / 60));
}
/**
* Get the logarithm of a value with a given base.
* @param base The base of the logarithm.
@ -79,7 +53,7 @@ class MathUtil
}
/**
* Get the base-2 logarithm of a value.
* Get the base-2 exponent of a value.
* @param x value
* @return `2^x`
*/
@ -89,19 +63,125 @@ class MathUtil
}
/**
* Linearly interpolate between two values.
*
* @param base The starting value, when `progress <= 0`.
* @param target The ending value, when `progress >= 1`.
* @param progress Value used to interpolate between `base` and `target`.
* @return The interpolated value.
* Helper function to get the fractional part of a value.
* @param x value
* @return `x - floor(x)`
*/
public static function lerp(base:Float, target:Float, progress:Float):Float
public static function fract(x:Float):Float
{
return base + progress * (target - base);
return x - Math.floor(x);
}
/**
* Linear interpolation.
*
* @param base The starting value, when `alpha = 0`.
* @param target The ending value, when `alpha = 1`.
* @param alpha The percentage of the interpolation from `base` to `target`. Forms a "line" intersecting the two.
*
* @return The interpolated value.
*/
public static function lerp(base:Float, target:Float, alpha:Float):Float
{
if (alpha == 0) return base;
if (alpha == 1) return target;
return base + alpha * (target - base);
}
/**
* Exponential decay interpolation.
*
* Framerate-independent because the rate-of-change is proportional to the difference, so you can
* use the time elapsed since the last frame as `deltaTime` and the function will be consistent.
*
* Equivalent to `smoothLerpPrecision(base, target, deltaTime, halfLife, 0.5)`.
*
* @param base The starting or current value.
* @param target The value this function approaches.
* @param deltaTime The change in time along the function in seconds.
* @param halfLife Time in seconds to reach halfway to `target`.
*
* @see https://twitter.com/FreyaHolmer/status/1757918211679650262
*
* @return The interpolated value.
*/
public static function smoothLerpDecay(base:Float, target:Float, deltaTime:Float, halfLife:Float):Float
{
if (deltaTime == 0) return base;
if (base == target) return target;
return lerp(target, base, exp2(-deltaTime / halfLife));
}
/**
* Exponential decay interpolation.
*
* Framerate-independent because the rate-of-change is proportional to the difference, so you can
* use the time elapsed since the last frame as `deltaTime` and the function will be consistent.
*
* Equivalent to `smoothLerpDecay(base, target, deltaTime, -duration / logBase(2, precision))`.
*
* @param base The starting or current value.
* @param target The value this function approaches.
* @param deltaTime The change in time along the function in seconds.
* @param duration Time in seconds to reach `target` within `precision`, relative to the original distance.
* @param precision Relative target precision of the interpolation. Defaults to 1% distance remaining.
*
* @see https://twitter.com/FreyaHolmer/status/1757918211679650262
*
* @return The interpolated value.
*/
public static function smoothLerpPrecision(base:Float, target:Float, deltaTime:Float, duration:Float, precision:Float = 1 / 100):Float
{
if (deltaTime == 0) return base;
if (base == target) return target;
return lerp(target, base, Math.pow(precision, deltaTime / duration));
}
/**
* Snap a value to another if it's within a certain distance (inclusive).
*
* Helpful when using functions like `smoothLerpPrecision` to ensure the value actually reaches the target.
*
* @param base The base value to conditionally snap.
* @param target The target value to snap to.
* @param threshold Maximum distance between the two for snapping to occur.
*
* @return `target` if `base` is within `threshold` of it, otherwise `base`.
*/
public static function snap(base:Float, target:Float, threshold:Float):Float
{
return Math.abs(base - target) <= threshold ? target : base;
}
/**
* Perform linear interpolation between the base and the target, based on the current framerate.
* @param base The starting value, when `progress <= 0`.
* @param target The ending value, when `progress >= 1`.
* @param ratio Value used to interpolate between `base` and `target`.
*
* @return The interpolated value.
*/
@:deprecated('Use smoothLerpPrecision instead')
public static function coolLerp(base:Float, target:Float, ratio:Float):Float
{
return base + cameraLerp(ratio) * (target - base);
}
/**
* Perform linear interpolation based on the current framerate.
* @param lerp Value used to interpolate between `base` and `target`.
*
* @return The interpolated value.
*/
@:deprecated('Use smoothLerpPrecision instead')
public static function cameraLerp(lerp:Float):Float
{
return lerp * (FlxG.elapsed / (1 / 60));
}
/**
* Backwards compatibility for `smoothLerpPrecision`.
*
* Perform a framerate-independent linear interpolation between the base value and the target.
* @param current The current value.
* @param target The target value.
@ -112,6 +192,7 @@ class MathUtil
*
* @return A value between the current value and the target value.
*/
@:deprecated('Use smoothLerpPrecision instead')
public static function smoothLerp(current:Float, target:Float, elapsed:Float, duration:Float, precision:Float = 1 / 100):Float
{
// An alternative algorithm which uses a separate half-life value: