godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.16k stars 97 forks source link

Add Spring/Berp Tween transition mode #4212

Closed rakkarage closed 1 year ago

rakkarage commented 2 years ago

Describe the project you are working on

I am working on porting this roguelike tilemap 'game' from Unity to Godot:

Describe the problem or limitation you are having in your project

I miss having access to the Spring tween. I am not sure where this easing function came from? I think I first saw it in iTween for Unity. https://github.com/jtothebell/iTween/blob/master/iTween.cs

Describe the feature / enhancement and how it helps to overcome the problem or limitation

It is similar to back or elastic but different enough to notice and seems better suited for ui elements like when OSX and iOS scroll a list past the end and 'spring' back?

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Here is an animation from (https://www.protopie.io/learn/docs/interactions/animation-curves) just to show what it looks like kinda? This one is simpler with no parameters. spring.gif

You can also see spring tween in action at the PixelLevel link posted above.

Here is a pull request implementing/porting it to Godot. https://github.com/godotengine/godot/pull/58999

Here is a simple project for testing and comparing new spring tween: https://github.com/rakkarage/TestTween (4.0 branch!)

If this enhancement will not be used often, can it be worked around with a few lines of script?

No.

Is there a reason why this should be core and not an add-on in the asset library?

Yes, though it is not an official 'Robert Penner' easing function, it is similar in many ways, and many other easing systems include it.

KoBeWi commented 2 years ago

It looks like elastic easing, it already exists in Godot's Tween: https://raw.githubusercontent.com/godotengine/godot-docs/master/img/tween_cheatsheet.png

Try interpolating with TRANS_ELASTIC.

rakkarage commented 2 years ago

Thanks. It does look like elastic. I know about elastic, and said and showed I was using it. I should have included these gifs of the test code running to better show difference.

Linear and Spring: Test.gif All In Order, Spring @ end, is same for all types: TestAll.gif All In Order, Elastic shoots off 300 pixels past edge before Spring hardly gets started: TestSome.gif

And here is the code:

old:

private float spring(float start, float end, float value)
{
    value = Mathf.Clamp01(value);
    value = (Mathf.Sin(value * Mathf.PI * (0.2f + 2.5f * value * value * value)) * Mathf.Pow(1f - value, 2.2f) + value) * (1f + (1.2f * (1f - value)));
    return start + (end - start) * value;
}

new:

static real_t out(real_t t, real_t b, real_t c, real_t d) {
    t /= d;
    t = CLAMP(t, 0.0, 1.0);
    t = (sin(t * Math_PI * (0.2f + 2.5f * t * t * t)) * pow(1.0f - t, 2.2f) + t) * (1.0f + (1.2f * (1.0f - t)));
    return c * t + b;
}
lentsius-bark commented 2 years ago

Thank you for the visualisation! As someone that abuses tweens, i wholeheartedly support the addition. It's just useful to have a number of key and distinct transition modes

rakkarage commented 2 years ago

I just wanted to add some more info, and plead 'our' case some more.

Here are some sources for the code, other 'tweening systems' that use it.

Spring

Berp

"Using Damped Springs for animations" https://humpf.etienne.tech/article

Pros:

Cons:

Thank you for your time and consideration.

TokageItLab commented 1 year ago

I propose that we could allow String values like Hint in EditorProperty to allow custom values for math curve to be put into the tween.

KoBeWi commented 1 year ago

Hmm, is it time to add CustomTweener? 🤔 The idea is that it would be a virtual class with GDVIRTUAL call for step method. This way you could customize it to your liking. My only worry is that interpolating could be slow with script functions.

Ah, but everytime I think about it I then remember that it's basically the same as MethodTweener. You can use custom method to interpolate over any curve. The Tween demo was recently updated to include an example of such method.

Basically Tweens already cover every use-case, but you need to know how to use them. Feel free to ask if you have any effect that you don't know how to achieve.

EDIT: CustomTweener might be useful for GDExtension though.

rakkarage commented 1 year ago

sorry not sure I understand? are you saying tweens cover this use case? https://docs.godotengine.org/en/stable/classes/class_methodtweener.html could not be used to implement spring tween... it just calls a function using the existing ease types? this adds a new ease type... thanks

KoBeWi commented 1 year ago

An easing is just an equation. You can run a MethodTweener over a linear value and then map it to the result of the equation.

Here's an example of interpolating a position over an arbitrary curve (from official Tween demo):

var tweener = tween.tween_method(func(v): icon.position = path.position + path.curve.sample_baked(v),
            0.0, path.curve.get_baked_length(), 3.0).set_delay(0.5)

Spring using this method would be a bit more complicated, but also possible:

func _ready() -> void:
    create_tween().tween_method(func(v): $icon.position.x = spring(v, 100.0, 400.0), 0.0, 1.0, 1.0)

func spring(t, start, end):
    t = (sin(t * PI * (0.2 + 2.5 * t * t * t)) * pow(1.0 - t, 2.2) + t) * (1.0 + (1.2 * (1.0 - t)))
    return start + (end - start) * t
rakkarage commented 1 year ago

Thanks. It is obviously great that there is a way to add custom tween functions. I think I knew that there was a way but not exactly how.

But I beg you to please still consider this for inclusion.

Please let me know if there is anything else I can do to help make this happen? Should I make some better gifs? or a demo using MethodTweener so people can try without building?

Thanks.

rakkarage commented 1 year ago

"This looks like a great function that I think belongs in the base system, being a very common transition type."

https://github.com/godotengine/godot/pull/58999

TokageItLab commented 1 year ago

For now, please Rebase the PR https://github.com/godotengine/godot/pull/64805 properly so that it can pass the latest test. (Although it looks like that PR doesn't implement the Spring Tween correctly in the first place...)

https://docs.godotengine.org/en/stable/contributing/workflow/pr_workflow.html


As long as parameters such as "tension" and "friction" cannot be passed as options, it is unlikely to be fully compatible with Godot's tween system, but since some magic numbers are already used by other tweens, that can be left out of the issue.

Well, it is good for me if it is clear what the magic number of your Spring Tween means. For example, the value 1.70158 in the Back Tween is a magic number that assumes a 10% overshoot.

https://libcinder.org/docs/structcinder_1_1_ease_out_back.html https://stackoverflow.com/questions/46624541/how-to-calculate-this-constant-in-various-easing-functions

rakkarage commented 1 year ago

Not sure why it has to have transition and friction to be correct? It works fine without. It is a simple tween animation not a full physics spring. All tweens take the same 4 parameters real_t t, real_t b, real_t c, real_t d.

Thanks will try to fix that pr again. (Error, will try again sorry :)

TokageItLab commented 1 year ago

Not sure why it has to have transition and friction to be correct? It works fine without.

Since Godot is OSS under the MIT license, so we prefer to avoid magic numbers without the evidence. If it is a proprietary defined value and you just copy and paste it, it is not a good thing license-wise.

If the magic number is not determined by someone's (perhaps the inventor of the spring tween) sense, but is a mathematically definable and meaningful value, then this is proof that it is a value that could have been thought up by someone else, even if not the inventor of the spring tween.

rakkarage commented 1 year ago

I don't think there are any real magic numbers in this spring tween code, gpt can explain it better then me

static real_t out(real_t t, real_t b, real_t c, real_t d) {
    t /= d;
    t = CLAMP(t, 0.0, 1.0);
    t = (sin(t * Math_PI * (0.2f + 2.5f * t * t * t)) * pow(1.0f - t, 2.2f) + t) * (1.0f + (1.2f * (1.0f - t)));
    return c * t + b;
}

This is an implementation of an easing function, specifically an "out" easing function, which is commonly used in animation to create smooth transitions between two states or values.

The easing function takes four parameters:

t: the current time, usually measured in seconds or milliseconds b: the starting value c: the change in value (i.e. the difference between the ending value and the starting value) d: the duration of the animation, usually measured in seconds or milliseconds The function first normalizes the current time by dividing it by the duration, so that the value of t ranges from 0 to 1 over the course of the animation. It then clamps the value of t to ensure that it stays within the range of 0 to 1.

The easing effect is created by the following formula:

(sin(t Math_PI (0.2f + 2.5f t t t)) pow(1.0f - t, 2.2f) + t) (1.0f + (1.2f (1.0f - t)))

Breaking this down, the formula has two main components:

The first component is a sinusoidal function that oscillates between -1 and 1 as t increases from 0 to 1. The frequency of the oscillation increases as t^3, so the function starts out slow and gradually speeds up as t approaches 1.

The second component is a polynomial function that is used to control the shape of the easing curve. Specifically, it uses the pow() function to raise (1-t) to the power of 2.2, which creates a gentle slope towards the end of the animation. The t term at the end of the first component is used to ensure that the curve reaches 1 at the end of the animation. The (1 + 1.2 * (1 - t)) term at the end is used to add a small "bounce" effect to the animation, by adding a small overshoot near the end of the curve.

Finally, the easing function scales the resulting value of t by the change in value (c) and adds the starting value (b) to get the final output value. This creates a smooth transition between the starting value and the ending value, with the easing effect creating a more natural and visually pleasing animation.

rakkarage commented 1 year ago

"do you know what this type of ease is called?" ChatGPT This easing function appears to be a custom implementation and does not correspond directly to any of the standard easing functions that are commonly used in animation. However, the general shape of the easing curve resembles a combination of a cubic easing function and a sine easing function. The polynomial term with an exponent of 2.2 is similar to the "ease-out-cubic" function, which creates a gentle slope towards the end of the animation, while the sine function with increasing frequency resembles the "ease-out-elastic" function, which creates a bounce effect towards the end of the animation. Therefore, one could describe this easing function as a custom combination of "ease-out-cubic" and "ease-out-elastic" easing functions.

TokageItLab commented 1 year ago

Can you explain the rationale for 2.5f and pow(x, 2.2f) in the equation?

Since ChatGPT is not the author of this PR (and cannot be a contributor), it is preferable that you can explain it in your opinion. It does not mean to prohibit the use of ChatGPT, but you must produce a proof that is correct and follows logic.

For example, like the following explanation is expected:

The value N is obtained from the formula N = ABCDE when tention = X, friction = Y. It means always oscillates any value P times and decays over Q percent of the total time length. Here are some videos that prove it is always correct for different values (XY.mp4) (YZ.mp4).

AThousandShips commented 1 year ago

Yeah while ChatGPT can be powerful it's in no way guaranteed to be scientifically correct

rakkarage commented 1 year ago

↜ Spring / Berp Easing Function ↝

static real_t out(real_t t, real_t b, real_t c, real_t d) {
    t /= d;
    t = CLAMP(t, 0.0, 1.0);
    t = (sin(t * Math_PI * (0.2 + 2.5 * t * t * t)) * pow(1.0 - t, 2.2) + t) * (1.0 + (1.2 * (1.0 - t)));
    return c * t + b;
}

↜ Part 1: Damping / Friction ↝

sin(t * Math_PI * (0.2 + 2.5 * t * t * t))

The first component is a sinusoidal function that oscillates between -1 and 1 as t increases from 0 to 1. The frequency of the oscillation increases as t^3, so the function starts out slow and gradually speeds up as t approaches 1.

0.2: can be referred to as damping or friction factor as it controls the initial damping or friction of the easing curve.

2.5: can be referred to as damping or friction multiplier as it controls how quickly the damping or friction increases over time.

↜ Part 2: Stiffness / Transition ↝

pow(1.0 - t, 2.2) + t

The second component is a polynomial function that is used to control the shape of the easing curve. Specifically, it uses the pow() function to raise (1-t) to the power of 2.2, which creates a gentle slope towards the end of the animation. The t term at the end is used to ensure that the curve reaches 1 at the end of the animation.

2.2: can be referred to as slope factor or stiffness or transition as it controls the shape of easing curve.

↜ Part 3: Overshoot ↝

(1.0 + (1.2 * (1.0 - t)))

The third component is used to add a small "bounce" effect to the animation, by adding a small overshoot near the end of the curve.

1.2: can be referred to as scale or overshoot as it controls the amplitude of the overshoot at end.

TokageItLab commented 1 year ago

I understand roughly what the formulas do, but please explain how those constants were determined.

For example, does 1.2 for overshoot mean that 20% overshoot will occur?

If so, then a value of 1.2 would be exact (snapped) compared to a value such as 1.12345, and that would be the reason for adopting that value. (Even if there is no direct relationship to the overshoot value, the value may be calculated from the exact Friction or Tension.)

On the other hand, for example if 1.2 means 37.426% overshoot, then it could mean that your intention is not there and that it was determined by someone else's sense of what they thought was a good motion. It is not yours.

The "because someone else is already using it" thing needs to be avoided unless the license of the original code is clear. iTween is "BSD licensed" and incompatible with Godot. Has SpringTween existed before it and are these constants something anyone could come up with?

rakkarage commented 1 year ago

thanks, idk here are two 9 year old MIT licensed versions

maybe 10 years, if you follow the history through the renames... apache licence?

oh ya iTween @ 11, oldest source I can find idk https://github.com/jtothebell/iTween/blob/master/iTween.cs

Calinou commented 1 year ago

iTween is "BSD licensed" and incompatible with Godot.

We can use BSD-licensed code in Godot (preferably 0/2/3-clause); we already do :slightly_smiling_face:

TokageItLab commented 1 year ago

Ah yes, indeed. Sorry, I misunderstood it with GNU.

Even so, until we fully understand it, I might as well implement these as thirdparty. iTween appears to have an easing called "Punch" in addition to Spring, so you could implement those together as well.

The existing easing values are clear about them, such as the result of an geometric progression or a value that overshoots by X% (although Godot's documentation doesn't note it). So I would like to see them be clear for SpringTween, etc. as well.

My guess is that those constants are probably related to the intersection of the elastic and cubic graphs for a particular value, but I'll have to look into that a bit more. (Ideally the author of PR should be able to explain it perfectly.)

Or, if there is a code that allows friction or tension to be specified as an argument, it may be a value that can be easily derived from it.

rakkarage commented 1 year ago

I agree the punch and stab would be nice too though I personally do not have an immediate need.

"Punch and Stab" demo http://www.pixelplacement.com/itween/examples.php

but it does not take the same parameters as ALL the other tweens, so it does not fit exactly as is, and is not only 3 lines like this one, and u just said avoid "because someone else is already using it"

I am not sure why you or anyone needs to know how it works or how the values were determined since the constants don't change. What will u do with that knowledge? I cannot explain the math but I know it is clear to someone who can...

they seem to me like 'simple' tweaks to an ease curve? like many other constants in the file and many sin and pow in the file?

what does thirdparty mean? would have to install something in every project that uses it?

I appreciate the help.

TokageItLab commented 1 year ago

For example, an optional overshoot could theoretically be implemented for Back easing.

If in the future such optional values are allowed, and no one understands Spring about tension and friction, they could somehow not be maintained and be inconsistent with the other easing.

As long as you don't fully understand it, you should not implement code that no one could potentially maintain. Then, you can implement it as thirdparty and disclaim responsibility for it by not implementing more than what the licensor provides.

Check the thirdparty directory in the godot repository. The custom Tweener may be implemented as a custom module just like the thirdparty modules there.

rakkarage commented 1 year ago

you think parameters going to be added to penners easing functions? maybe we can cross that bridge when we get to it?

TokageItLab commented 1 year ago

What I said about custom parameters is an example and does not imply that they are actually planned.

At least, the source of the code should be noted in a comment or something if you are just quoting it without fully understanding it (The MIT license and iTween's BSD 3-clause license are not public domain and requires notation somewhere), but I don't know how to do that for the MIT license in cases where it is quoted for such a small module. Maybe the product team will be able to make a better decision.

TokageItLab commented 1 year ago

Closed by https://github.com/godotengine/godot/pull/76899