diff --git a/src/mapInteractor.js b/src/mapInteractor.js index a37f2394e1..b7fbbbac02 100644 --- a/src/mapInteractor.js +++ b/src/mapInteractor.js @@ -1156,10 +1156,8 @@ var mapInteractor = function (args) { } if (m_state.zoomrotateAllowRotation) { var theta = m_state.initialRotation + deltaTheta; - /* Compute the delta in the range of [-PI, PI). This is involed to work - * around modulo returning a signed value. */ - deltaTheta = ((theta - m_this.map().rotation()) % (Math.PI * 2) + - Math.PI * 3) % (Math.PI * 2) - Math.PI; + /* Compute the delta in the range of [-PI, PI). */ + deltaTheta = util.wrapAngle(theta - m_this.map().rotation()); /* If we reverse direction, don't rotate until some threshold is * exceeded. This helps prevent rotation bouncing while panning. */ if (deltaTheta && (deltaTheta * (m_state.lastRotationDelta || 0) >= 0 || diff --git a/src/util/index.js b/src/util/index.js index c397b2ba3f..ba39ca3e8a 100644 --- a/src/util/index.js +++ b/src/util/index.js @@ -1020,6 +1020,23 @@ var util = module.exports = { return {min: min, max: max}; }, + /** + * Given a value in radians, return a value wrapped to the range [-PI, PI). + * + * @param {number} value A value in radians. + * @returns {number} The wrapped value. + */ + wrapAngle: function (value) { + /* Module will only ensure that this is between [-2 PI, 2 PI). */ + value = value % (Math.PI * 2); + if (value < -Math.PI) { + value += Math.PI * 2; + } else if (value >= Math.PI) { + value -= Math.PI * 2; + } + return value; + }, + /** * Escape any character in a string that has a code point >= 127. * diff --git a/tests/cases/util.js b/tests/cases/util.js index 090dc77f6c..ec1bfa5306 100644 --- a/tests/cases/util.js +++ b/tests/cases/util.js @@ -124,4 +124,16 @@ describe('geo.util', function () { expect(util.getMinMaxValues(values, 200, 300, true)).toEqual({min: 200, max: 300}); expect(util.getMinMaxValues(values, 100, 400, true)).toEqual({min: 181, max: 303}); }); + + it('wrapAngle', function () { + expect(util.wrapAngle(0)).toBe(0); + expect(util.wrapAngle(2)).toBe(2); + expect(util.wrapAngle(4)).toBe(4 - Math.PI * 2); + expect(util.wrapAngle(7)).toBe(7 - Math.PI * 2); + expect(util.wrapAngle(17)).toBe(17 - Math.PI * 6); + expect(util.wrapAngle(-2)).toBe(-2); + expect(util.wrapAngle(-4)).toBe(-4 + Math.PI * 2); + expect(util.wrapAngle(-7)).toBe(-7 + Math.PI * 2); + expect(util.wrapAngle(-17)).toBe(-17 + Math.PI * 6); + }); });