# [feature request] Rotation tweens should move in the direction of the shortest distance #2494

Closed
opened this Issue May 21, 2016 · 15 comments

None yet

### 4 participants

 Suppose I have a tank and I want to turn it to a specific angle using a rotation tween. The problem with the tween system now is that it only adds or subtracts a value to achieve the target value, but that simply logic doesn't work when you are dealing with angles. The function below will tell you wether adding to the rotation is the shortest direction to turn in to achieve a target angle. Given a current and target angle, when returning true, the tween manager should add, when returning false, it should subtract. That way you don't have the tank do an almost 360 spin to turn less than 10 degrees! Hopefully this code can be implemented in core as I think it is intuitive the tweening should work this way. ``````function addIsShortestDistanceToRotation(rotationAt, rotationGoing) { rotationAt = Phaser.Math.normalizeAngle(rotationAt); rotationGoing = Phaser.Math.normalizeAngle(rotationGoing); if ((rotationAt < rotationGoing) && ((rotationGoing - rotationAt) <= Math.PI)) { return true; } if ((rotationAt < rotationGoing) && ((rotationGoing - rotationAt) > Math.PI)) { return false; } if ((rotationAt > rotationGoing) && ((rotationAt - rotationGoing) <= Math.PI)) { return false; } if ((rotationAt > rotationGoing) && ((rotationAt - rotationGoing) > Math.PI)) { return true; } } ``````
 I wonder if anyone can assist with a hack or workaround. I'd really like to make use of the tween system instead of having to manually tween all my rotations (with a simple +/- angle in updates), which looks horrible without easing and all the other neat features the tween system has. Is there an easy way for me to implement this into core? I've scoured the tween code and couldn't find where something like adding/subtracting the properties of the tween target takes place. I'm running 2.4.7 btw.
 Just a reminder. I would really really really love to have this. :) I am currently doing manually tweens on sprites to get the rotation going the shortest distance and it is extremely problematic, especially over a network (now I need to communicate the direction of the rotation over the network). I miss simply tweening rotations/angles. It was so easy and the product was beautiful!
Contributor
commented Jul 25, 2016 edited
 You can do this yourself, even if you need to hide it behind some abstraction. Since we know angles can wrap and be negative, we can do ourselves a favour and use some math to solve this problem... `var tween = game.add.tween(sprite).to({rotation: -(3.14*2 - (3.14*1.5))}, 2000, "Linear", true, 0, true)` is the same as `var tween = game.add.tween(sprite).to({rotation: (3.14*2 - (3.14*1.5))}, 2000, "Linear", true, 0, true)` Except the latter takes the longer route. :) Replace 3.14 w/ Pi and the number multiplied by 1.5 with your "target". You can use some basic math to figure out which is shorter. If the absolute distance is better to turn in one direction, use the positive sign. Otherwise, use the negative sign. Does this help? You can hide this behind a `TweenFactory` of some sort... not sure if Phaser needs baked in support for something so simple, but if Rich is interested, I can draft a PR that is somewhat generic. Using your method above BTW, you can simply created a `TweenFactory` with a `createTweenWithShortestRotationTo(currentRotation, targetRotation` and then just return the proper tween. Nice and simple. :)
 Oh wow, thanks! I will try your solutions and report back.
Contributor
 Great. Let me know if you have issues, I tested it and it works fine.
commented Jul 28, 2016 edited
 I'm having difficulty understanding how to implement your solution. Suppose I have a sprite with rotation at 0. I want to tween its rotation to 1.5: ``````var rotation = 1.5; var turnNegative = -(Math.PI * 2 - (Math.PI * rotation)); var turnPositive = (Math.PI * 2 - (Math.PI * rotation)); var diffTurnNegative = Math.abs(gameObject.rotation - turnNegative); var diffTurnPos = Math.abs(gameObject.rotation - turnPositive); if (diffTurnPos > diffTurnNegative) { if (rotation > 0) { rotation = -rotation; } } else { if (rotation < 0) { rotation = rotation * -1; } } var moveTween = game.add.tween(gameObject).to({ 'rotation': rotation }, time, Phaser.Easing.Linear.None, true); `````` What am I doing wrong here? I really just don't understand it. Compare the absolute distance of what? Use positive/negative signs on what, the rotation target? I guess it would make much more sense to me if I saw a complete example. Thanks!
commented Jul 28, 2016 edited
 i might be thinking wrong but probably your errors coming from here : `````` `var rotation = 1.5; var turnNegative = -(Math.PI * 2 - (Math.PI * rotation)); var turnPositive = (Math.PI * 2 - (Math.PI * rotation)); var diffTurnNegative = Math.abs(gameObject.rotation - turnNegative); var diffTurnPos = Math.abs(gameObject.rotation - turnPositive); if (diffTurnPos > diffTurnNegative) { if (rotation > 0) { rotation = -rotation; } // there isnt an else statment.. what if rotation < 0 and then what is gonna happen? } else { if (rotation < 0) { rotation = rotation * -1; } // same thing what if rotation > 0 .. } var moveTween = game.add.tween(gameObject).to({ 'rotation': rotation }, time, Phaser.Easing.Linear.None, true); `````` ` Please try to add else statments and give it a try Hope this will fix your problem
commented Jul 28, 2016 edited
 If the rotation is smaller than 0 then the rotation is already negative; therefore, the sign doesn't need to change. This is all based on my interpretation of hilts-vaughan's logic, but I don't understand the math.
commented Jul 28, 2016 edited
 Let's deal with the math then and see the result: `var rotation = 1.5; var turnNegative = -1.57; -((3.14*2) - (3.14 *1.5)) = -(6.28 - 4.71) = -1.57; var turnPositive = +1.57 //same math ^-^ var diffTurnNegative = Math.abs(gameObject.rotation - turnNegative); Okey now here I think at the first place you gameObject.rotation value is prbably zero then var diffTurnNegative = +1.57; (Math.abs(0 - (-1.57)); which gives you a positive 1.57) var diffTurnPos = Math.abs(gameObject.rotation - turnPositive); Okey this one also gives you a positive +1.57 cause of Math.abs() function. So var diffTurnPos = +1.57 Look at if statemants, you will see that you are going into else statment for these results, and ckecking your rotation is smaller then 0. Guess what.. It's not. At the and your moveTween variable give the rotation value of 1.5. Hope you will figure out rest :)
 I've tried writing it like this: `````` var turnNegative = -(Math.PI * 2 - (Math.PI * rotation)); var turnPositive = (Math.PI * 2 - (Math.PI * rotation)); var diffTurnNegative = Math.abs(gameObject.rotation - turnNegative); var diffTurnPos = Math.abs(gameObject.rotation - turnPositive); if (diffTurnPos < diffTurnNegative) { rotation = turnPositive; } else { rotation = turnNegative; } var moveTween = game.add.tween(gameObject).to({ 'rotation': rotation }, time, Phaser.Easing.Linear.None, true); `````` No luck.
Contributor
commented Jul 29, 2016 edited
 Hi, I'll write a Phaser sandbox example. :) I think I explained the "multiply" poorly. See, you do not need to multiply by these values. I only did that because it allowed me to think in terms of "full circles" 0.5 = quarter circle 1 = half circle 1.5 = 3/4 circle etc... see the Phaser sandbox example below for a corrected algorithm.
Contributor
commented Jul 29, 2016 edited
 From one developer to another. <3 I've only tested it from "rotation 0" but it should work fine.. I think? The if might need tweaking if that's the case, but should be O.K. Let me know. There is probably an easier way but it is late and I am tired. Good luck. :) Pay it forward (http://phaser.io/sandbox/edit/zyzPzRcM) Don't understand the math and want to? Send me an e-mail, we can talk.
Contributor
 Oh, and of course, hide this ugliness in another class somewhere. `ProperRotationTweenFactory` might do the trick... or if Rich wants, I can submit a PR w/ the crude math. :)
added a commit that referenced this issue Jul 29, 2016
 photonstorm `Math.getShortestAngle will return the shortest angle between the two …` `…given angles. Angles are in the range -180 to 180, which is what `Sprite.angle` uses. So you can happily feed this method two sprite angles, and get the shortest angle back between them (#2494)` `688752c`
Owner
 Here's my version :) `````` /** * Gets the shortest angle between `angle1` and `angle2`. * Both angles must be in the range -180 to 180, which is the same clamped * range that `sprite.angle` uses, so you can pass in two sprite angles to * this method, and get the shortest angle back between the two of them. * * The angle returned will be in the same range. If the returned angle is * less than 0 then it's a counter-clockwise rotation, if >= 0 then it's * a clockwise rotation. * * @method Phaser.Math#getShortestAngle * @param {number} angle1 - The first angle. In the range -180 to 180. * @param {number} angle2 - The second angle. In the range -180 to 180. * @return {number} The shortest angle, in degrees. If less than zero it's counter-clockwise, otherwise clockwise. */ getShortestAngle: function (angle1, angle2) { var difference = angle2 - angle1; var times = Math.floor((difference - (-180)) / 360); return (difference - (times * 360)) * -1; }, `````` Here's a full example (you can find the assets in the Phaser Examples repo) ``````var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create }); function preload() { game.load.image('arrow', 'assets/sprites/longarrow.png'); game.load.image('lemming', 'assets/sprites/lemming.png'); } var arrow; var arrow2; var lemming; function create() { game.stage.backgroundColor = '#000000'; arrow = game.add.sprite(game.world.centerX, game.world.centerY, 'arrow'); arrow.anchor.set(0, 0.5); // arrow.tint = 0xff0000; arrow2 = game.add.sprite(game.world.centerX, game.world.centerY, 'arrow'); arrow2.anchor.set(0, 0.5); lemming = game.add.sprite(game.world.randomX, game.world.randomY, 'lemming'); lemming.anchor.set(0.5); setNewLocation(); game.input.onDown.add(setNewLocation, this); } function setNewLocation () { arrow2.angle = arrow.angle; lemming.x = game.world.randomX; lemming.y = game.world.randomY; var angleTo = Phaser.Math.radToDeg(arrow.position.angle(lemming.position)); var shortestAngle = game.math.getShortestAngle(angleTo, arrow.angle); var newAngle = arrow.angle + shortestAngle; game.add.tween(arrow).to({ angle: newAngle }, 3000, 'Linear', true); } ``````
commented Jul 29, 2016 edited
 Wooooooooooot! I just tested it now and works like a charm. Here is my example with Richard's method (note that I simply copied his function because it only exists in the dev repo now): ``````function getShortestAngle(angle1, angle2) { var difference = angle2 - angle1; var times = Math.floor((difference - (-180)) / 360); return (difference - (times * 360)) * -1; } var shortestAngle = getShortestAngle(Phaser.Math.radToDeg(newRotation), gameObject.angle); var newAngle = gameObject.angle + shortestAngle; var moveTween = game.add.tween(gameObject).to({ 'angle': newAngle }, time, Phaser.Easing.Linear.None, true); `````` Thanks everyone and I'm so glad to get this issue worked out. It's been plaguing me for a while now!