This week I have been thankful that I paid attention and did well in my high school trigonometry class. Sine, Cosine, and Tangent really came in handy when the flames of my rocket in Rudiment Rock-it wanted to do detach themselves and do their own thing.
A Trigonometry Story Problem
It had been a very long time since I used trigonometry for anything. This problem presented itself whilst I was innocently developing a game for drummers who are looking for a fun way to learn some of their basic rudiments. In the game, the player taps each side of their mobile device in the pattern specified. The goal is to perform the pattern correctly and evenly as quickly as possible. If its not even, the rocket will tip and the flames on the side of the inaccurate hand will get smaller. Or if the player is going too slowly the flames will go out altogether and the rocket will not go higher. This gives the player feedback on what they need to fix in order to do better. The problem happened when the rocket rotated and the flames would not come with it.
The flames needed to be programmed separately from the rocket. They needed to act independently of each other to give correct and good looking feedback. Yes, I could have asked my animator to graciously draw 50 versions of a rocket with flames. But that would have been a waste of memory (mobile app) and not an efficient use of his time either. The better solution was to tie the separate flames to the rocket and get better results with less strain on the device. I hesitated when I realized that the answer to tying a rotating image to a part of another rotating image required some very difficult trigonometry. “I know some people who could do this faster,” I thought. But no, I wanted the challenge all to myself.
If you’re not into actually doing trigonometry with me, now would be the time to check out and go play Rudiment Rock-it. Have fun, we’ll join you in a minute!
The rotation of the flame was easy: flame.rotation = rocket.rotation. If the rocket always stood up straight, the rest would be easy too:
flame.x = rocket.x and flame.y = rocket.y
Oh, but the flame changes length so we need to make an adjustment: flame.y = rocket.y – (flame.length/2)
This ties the top of the flame instead of the middle. But now we need to actually tie the flame to the engine of the rocket (we’ll just do the left one for now):
flame.y = (rocket.y – 26) – (flame.height/2)
flame.x = rocket.x – 7
But what about when the rocket tips? The flame will detach and look silly. And that’s where the trigonometry comes in. First we need to figure out how far away the flame is (the hypotenuse) because this number will not change. x and y will be changing every frame! So x and y need to be based upon the total distance:
7^2 + 26^2 = 26.93
So now we can use a combination of the hypotenuse and the angle of the rocket’s rotation to determine either x or y first. I think it’s easier to think horizontally, so we’ll do x first. We want “flame.x” to equal “-7” when the rocket is straight since the flame should be directly left of the rocket (we’re ignoring y for now). We have to use cosine because x will the adjacent side once the rocket rotates. So since “cos(-90) = 0” and “cosine = adjacent/hypotenuse” we can do this (assuming that flame.x is -7 and rotation is 0):
flame.x/26.93 + 7 = cos(rocket.rotation-90)
flame.x = (cos(rocket.rotation-90) * 26.93) – 7
But we also need flame.x to be “+7” when the rocket is up-side-down. To fix that we need to modify that “-7” in our equation by finding a way to switch it to a positive number when the angle is 180 degrees. I found this easier to do while thinking of the rotation in 90 degree increments:
flame.x = (cos(rocket.rotation-90) *26.93) + (cos(rocket.rotation)*7)
This way, when the rotation is at 180 degrees, the 7 becomes positive and it’s still negative when the rocket is right-side-up! But now when it on its sides, x should be +/-26 and not the length of the hypotenuse like it is right now. But I really don’t care because it’s not even a whole pixel of difference. Moving on!
We have one more major problem to solve: we still have to compensate for the changing height of the flame. So we modify our hypotenuse because that is the flame’s distance from the rocket. Currently, the middle of the flame is touching the engine. To change that to be the end of the flame, all we do it add half of the length of the flame to the distance from the center of the rocket.
flame.x = ((cos( rocket.rotation -90)*(26.93+( flame.height /2))) – (cos( rocket.rotation )*7))
And of course we should probably add in rocket.x
flame.x = ((cos( rocket.rotation -90)*(26.93+( flame.height /2))) – (cos( rocket.rotation )*7)) + rocket.x
Now for flame.y. Just use sine instead of cosine to find the other axis. Simple as that:
flame.y = ((sin( rocket.rotation -90)*(26.93+( flame.height /2))) – (sin(rocket.rotation)*7)) + rocket.y
flame.x = ((cos( rocket.rotation -90)*(26.93+( flame.height /2))) + (cos( rocket.rotation )*7)) + rocket.x
flame.y = ((sin( rocket.rotation -90)*(26.93+( flame.height /2))) + (sin(rocket.rotation)*7)) + rocket.y
Simple trigonometry, right?