lexyeevee

troublesome fox girl

hello i like to make video games and stuff and also have a good time on the computer. look @ my pinned for some of the video games and things. sometimes i am horny on @squishfox



lexyeevee
@lexyeevee

me: damn, how do i find a cubic that crosses the x-axis at these three points

me:

me: oh lmao


lexyeevee
@lexyeevee

it's because i wanted to make this spiral better, though the recorder dropped a couple frames dangit


lexyeevee
@lexyeevee

now, i admit upfront: i did zero research on this. maybe there is a cool clever way to do a parametric spiral that isn't this. my suspicion is that any "simpler" alternative would be a trip through the polar plane and involve a bunch of trig, and we're game developers here, so we do everything with vectors god dammit

the problem

here is what i decided i want from the beginning:

  • when you pick up the key (or a handful of other items, but mainly the key), it animates moving smoothly into the UI.

  • the motion should not be a straight line, because that's boring.

  • the ideal seems to be a sort of spiral around and outwards from the player, which ⓐ feels like a congratulatory motion of "sparkly thing swirling around you" and ⓑ would still roughly end in a straight shot towards the final position in the UI, which is good as a dramatic finish to a motion.

(i'm pretty sure i did not know that i wanted a spiral from the beginning; it sort of emerged as an obvious thing to try. i first added this ages ago though so i'm not completely sure)


so how do we do a spiral if we don't want to use trig? my answer was to make this bad boy parametric. you know, that thing where you have x(t) and y(t) separately, and t is a hidden time variable. but i don't want to control x and y here; that would make everything radically different depending on where the key starts. so i went for a different coordinate system:

screenshot of lexy just about to collect a key, with the key and a point in the ui both marked

the original position of the key is the origin, and the final resting place in the UI is the point (0, 1). (note that as part of the UI, the animation happens in screen space, not world space; i immediately subtract the key's position from the camera offset and only work in screen coordinates from then on.)

to avoid confusion with screen or world coordinates, let's call these new axes u and v, where u is the perpendicular axis and v is the orthogonal axis (the one pointing towards the destination). though it doesn't really matter which is which since i'm about to treat them separately.

converting from uv back to screen coordinates is easy; one unit of v is the original delta vector, dest - source. one unit of u is a vector perpendicular to that, which is trivial to compute (swap the coordinates and negate one of them). so screen coordinates are

u * delta:perpendicular() + v * delta

which is like no effort at all. so now i can think in terms of u and v and not even worry about the screen, or the distance, or anything except the shape of the animation! that's great. and now i'm in a good place to split this messy 2D problem into two 1D problems.

the orthogonal axis

first i want to think about the key's movement directly towards the UI. as an Artist™, i have a tenuous grasp of a few wisps of animation basics, so my instinct is to make this feel real good by adding a little anticipation. that means a "wind-up", where the key actually starts out moving away from the UI. i think this adds emphasis because ⓐ "backwards to forwards" is a more dramatic change than "still to forwards" and ⓑ backing up first means the important part of the motion now covers a greater distance. (the forwards motion also now has to be faster to cover the distance in the same amount of time, which feels more dramatic.)

so how do i do that? i am but a simple fox, and this is a platformer, so there can only be one answer for me: a parabola. essentially it's like the key is doing a jump off of a cliff and landing on the ground below. except, you know, upwards.

i don't need to model this like physics, though; i already know a lot about the parabola i want, so i can jump directly to expressing it as a function, v(t) = at² + bt + c.

  • i know that at time t = 0, the key is at its starting position, i.e., 0. so v(0) = 0, which swiftly implies that c = 0.

  • i don't know how long the animation should be yet, but i might want to change that later anyway. so i'll just say the key reaches its endpoint at t = 1 and scale it later if i want to. that means v(1) = 1 (remember, the destination is 1 unit away, because i decided it is), so a + b = 1. i don't want to plug that in quite yet because it makes the function slightly messier, but i'll keep it in mind; as soon as i know what either a or b actually is, i can find the other very easily.

that's already taken care of two of my coefficients. what about the third? i don't have a third constraint, so it can be whatever i want. but a and b don't directly correspond to any obvious property of the behavior here. i'd like something intuitive that i can adjust.

i think a natural question is to ask how far back the key winds up. i'll call that h, since it's the maximum height of the arc. or, uh, maximum... un... height. remember, it should be a negative fraction of the total distance, since it's a movement away from the target, 1.

how does h actually fit into my parabola, though? well, it's the v-value of the parabola's local minimum. there's an expression for that, but i always forget what it is. i do know (and can readily derive) that the t-value of that point is −b/2a, so i can just plug that in:

v(t) = at² + bt
v(−b/2a) = a(−b/2a)² + b(−b/2a)
= a(b²/4a²) − b²/2a
= b²/4a − b²/2a
= −b²/4a

ah, so h = −b²/4a. but i know h, and i want to find a and b. luckily i know how to express a in terms of b, which turns this into a quadratic:

h = −b²/4a
h = −b²/4(1 - b)
4h − 4hb = −b²
b² − 4hb + 4h = 0
b = (4h ± √(16h² − 16h)) / 2
b = (4h ± 4√(h² − h)) / 2
b = 2 · (h ± √(h² − h))

hmm. do i want the + or − there? if you graph an example, you'll find that the difference is that one solution has the peak happen before t = 0, which is... not helpful here.

i can distinguish them by noticing that i want the slope to be negative at t = 0, since the key should start out moving backwards. that's given by v'(t) = 2at + b, which at t = 0 is just b. how convenient! so b should be negative, which forces using the − solution. note that this is guaranteed to be negative as long as h is; is less than h² − h for negative h, so h will be less than √(h² − h).

now i have b, and that gives me a. and i have a parabola, as well as an extra parameter h that i can play with, even dynamically if i want.

gif of the key animation, but only the orthogonal axis, so it winds up and then shoots directly towards the UI

the perpendicular axis

this has some nice oomph to it, but it's still fairly obvious that this is movement along a straight line. so now i can think about how to move perpendicularly, at the same time. it's nice to have this separate, because no matter what i do here, it will not affect the impact of the original parabola.

spirals are appealing, and this animation just feels like it wants a spiral. i'll give it a try, why not.

i don't care about any formal definition of what a spiral is. i just want to go roughly around the player. i'm already doing that in one dimension: the key moves to one side of the player, then the other. to get something spiral-like, i just have to do that a second time.

already i have several constraints falling into place:

  • u(0) = 0, because the key starts out with no perpendicular displacement

  • u(1) = 0, because the key also ends with no perpendicular displacement

  • between 0 and 1, u(t) takes on positive values for a while, and also negative values for a while

  • ...which implies that there is a third zero somewhere in there

now, this sort of sounds like a wave, right? it goes up, then down. (or maybe down, then up; it doesn't seem to matter.) and originally, i used a sine wave here. but that always looked a bit wonky to me, and one of the big things i changed in this recent touch-up was to not do that. here's why.

a very good question to ask is, when should u be zero? that is, when should it cross back through the path of the player? and i think for maximum impact and maximum spirality, there is only one possible answer: at the peak of the wind-up. otherwise the animation has different kinds of extremes happening at different times for the same movement, and that's probably why the sine approach looked wonky.

when is the peak of the wind-up, though? i already know that — it's −b/2a. i don't know what that is, exactly, since those coefficients are based on some other thing... but i can figure it out, which is good enough, so i don't actually care about its value. just call the whole thing s, where s is mathematics shorthand for "it's a number but i don't have a good name for it and t is already something else".

and this is what prompted me to realize that a sine wave is not such a great idea. sine waves like to repeat, which means each wave is the same length. but i have two waves here: one from 0 to s, and one from s to 1. it seems unlikely that they are exactly the same size. i could fuss with the sine wave to make it work, but surely there's a simpler approach here.

and there is! remember, i don't care whether the function repeats. i only care what it does between 0 and 1. outside of that range, it can do whatever the hell it wants. so what do i need sine for? after coming up with that quadratic function, i can immediately think of one type of curve that has both a peak and a valley: a cubic.

which, at last, brings me back to my original question: just how do i find a cubic that's zero at three specific points? i can plug them in but then i have three equations and four variables, which sounds annoying—

and then i remembered how algebra works, so

u(t) = t · (t - s) · (t - 1)

and that's it.

well, almost. there is also one free parameter here, because i can multiply the whole thing by a constant without changing the zeroes.

u(t) = ct · (t - s) · (t - 1)

the question is, what should that constant be? it controls how high the peaks are, i.e. the "width" of the spiral. but i can't get a good sense of it without figuring out what the heights of the peaks are.

unfortunately it turns out the heights of those peaks are fucking nightmarish hellscapes of square roots. so at this point i finally take off my mathematician hat and put my game developer hat back on.

  1. i don't actually care about the heights of the peaks. i just want the animation to look good.

  2. the maximum heights seem to happen when s = ½. i don't have a mathematical basis for this (though there are interesting properties of cubics that could probably help); i just tried it on wolfram alpha and noticed that the peaks are both shallower than this maximum value if s is anything else.

  3. that maximum height is 4/27 (thank u again wolfram alpha), which is roughly 0.15. of course as already mentioned, s is very unlikely to be ½ (in fact it's impossible, which you can convince yourself of if you think about v in terms of projectile motion), so the height in practice will be even smaller than this.

  4. a good spiral is kinda circle-y, so the range should probably be closer to, like, 1

  5. i guess i'll multiply it by 6 or something. hmm, maybe 8. yeah 8 looks better. 12 is too much

and that gets me my spiral. the only other detail i skipped is that i reverse the direction of the spiral (just by negating the perpendicular part) depending on which side of the screen the key starts on.

the final result

trimmed down to minimize the syntax cruft and leave just the math part:

    h = -1
    b = 2 * (h - sqrt(h*h - h))
    a = 1 - b
    s = -1/2 * b / a

    t = time / ttl

    ortho = t * (a * t + b)
    perp = 8 * t * (t - 1) * (t - s)

    delta = dest - source
    sprite:draw_at(source + delta * ortho + perp_sign * perp * delta:perpendicular())

also sorry

this is the kind of post that is just begging for some graphs and diagrams and stuff, but i am tired and there is just slightly too much friction on putting images inline on cohost


You must log in to comment.

in reply to @lexyeevee's post:

i assume the joke is that you just wanted the line y = 0? x)

but ofc there's an infinite number of cubic curves that satisfy that; like, any cubic bezier A,B,C,D for which A=X₁, D=X₃, and B & C are on the y=x₃ line, on on opposite sides of and equidistant from the x-axis

...im sure you already know that i just really like bezier curves

my tattoo of a cubic bezier made into a flower

edit: i don't think they even need to be equidistant, i just woke up and am sleepy

most (nearly all?) cubic beziers on a cartesian plane will not actually be functions though

aside from a scaling factor, there is exactly one cubic fitting this condition, and it is... y = (x - a)(x - b)(x - c)

I thought the joke is that it’s just y = D(x-A)(x-B)(x-C)?
Where D is any nonzero value that scales the amplitude of the curve (and could be omitted entirely), and A,B,C are the X-axis intercepts, since the right-hand side will evaluate to 0 when X is equal to any of A,B,C?

edit: ah, eevee beat me to it

in reply to @lexyeevee's post:

Ugh, I love this, this sort of stuff makes me want to Make A Game, but then I see that trick you did with the uv coordinate system and think “oh I never would have come up with a solution that clean and clever”

i assure you you can make a game without doing stuff like that! and maybe it will take less than N years

fwiw it's not even that clever — 50% of it is "well i'd like to use vectors and i've really only got the one so i guess i'll use that". the other 50% is taking a linear algebra class, but that part's optional because you can just see someone else do a trick like this and now you already know it