1
votes

I'm trying to rotate an image using React Native. I have managed to do this as shown in the example below, but I would prefer that the arrow spins to the new bearing using the shorter route (i.e. clockwise in this case instead of counterclockwise).

If the current bearingAnim value is 315 and the next value is 45 the rotation will transition backwards, rather than going from 315 => 45.

How can I adjust the code to use the shortest rotation?

const bearing = this.bearingAnim.interpolate({
  inputRange: [0, 360],
  outputRange: ['0deg', '360deg'],
});

return <Animated.Image style={{ transform: [{ rotate: bearing }] }} source={markerGreen} />;

enter image description here

2
You can calculate the angle between the two values, then decide whether to rotate clockwise or counterclockwise based on that angle. The angle between the two values can be expressed as abs(a - b) and 360-abs(a - b), where abs() represents the absolute value function.Telescope

2 Answers

1
votes

As each direction may be set as a positive or negative angle (clockwise/counter clockwise rotation), e.g. 315 (clockwise) is -45 (counter clockwise), you may compare the absolute values of a delta between current direction and new direction (both clockwise and counter clockwise) and set your new angle to the one having least absolute value of the delta:

const getNewDirectionangle = (direction, newDirection) => {
          const delta = {
            clockwise: (newDirection-direction)%360,
            counterClockwise: (newDirection-direction-360)%360
          }
          return
              Math.abs(delta.clockwise) < Math.abs(delta.counterClockwise) ?
              direction + delta.clockwise :
              direction + delta.counterClockwise
          
      }

Following is a React.js live-demo to illustrate the concept:

const { useState } = React,
      { render } = ReactDOM,
      rootNode = document.getElementById('root')
      
const Pointer = ({rotationAngle}) => {

  return (
    <div className="map">
      <div 
        className="pointer"
        style={{transform: `rotate(${rotationAngle}deg)`}}
      />
    </div>
  )
}

const Controls = ({onRotate}) => {
  const [azimuth, setAzimuth] = useState(),
        onInput = ({target:{value}}) => setAzimuth(value%360)
  return (
    <form>
      <input type="number" onChange={onInput} />
      <input type="button" value="Rotate" onClick={() => onRotate(azimuth)} />
    </form>
  )
}

const App = () => {
  const [direction, setDirection] = useState(0),
        onRotate = newDirection => {
          const delta = {
            clockwise: (newDirection-direction)%360,
            counterClockwise: (newDirection-direction-360)%360
          }
          setDirection(
              Math.abs(delta.clockwise) < Math.abs(delta.counterClockwise) ?
              direction + delta.clockwise :
              direction + delta.counterClockwise
          )
        }
  return (
    <div className="wrapper">
      <Pointer rotationAngle={direction} />
      <Controls {...{onRotate}}/>
    </div>
  )
}

render (
  <App />,
  rootNode
)
.wrapper {
  width: 250px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.map {
  width: 100%;
  height: 120px;
  display: flex;
  flex-direction: row;
  justify-content: center;
  background-image: url("https://i.stack.imgur.com/3yuKI.png");
  background-size: cover;
  margin: 5px
}

.pointer {
  transition: transform 1s ease-in;
  transform-origin: 50% 58%;
  background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMS43NTIgMjYuMjU2Ij48ZGVmcyBpZD0iZGVmczUiPjxjbGlwUGF0aCBpZD0iYSIgY2xpcFBhdGhVbml0cz0idXNlclNwYWNlT25Vc2UiPjxwYXRoIGQ9Ik0xNi4zMy0uNDhoODMuNzM0djEwOS4zNUgxNi4zM3oiIGlkPSJwYXRoMiIgLz48L2NsaXBQYXRoPjwvZGVmcz48cGF0aCBkPSJNMjguOTk5IDg3LjMzNmMtMTYuMDUtMTYuMDUtMTYuMDUtNDIuMDg3IDAtNTguMTM3QzM4LjY4NSAxOS41MTIgNDguMzcgOS44MjYgNTguMDc3LjE0TDg3LjEzNiAyOS4yYzE2LjA1IDE2LjA1IDE2LjA1IDQyLjA4NyAwIDU4LjEzNy0xNi4wNSAxNi4wNS00Mi4wODcgMTYuMDUtNTguMTM3IDB6IiBjbGlwLXBhdGg9InVybCgjYSkiIGZpbGw9IiM3MGFkNDciIGZpbGwtcnVsZT0iZXZlbm9kZCIgdHJhbnNmb3JtPSJtYXRyaXgoLjI2NDU4IDAgMCAuMjY0NTggLTQuNDg4IC0uMDM3KSIgaWQ9InBhdGg3IiAvPjxwYXRoIGQ9Ik0zLjA3NCAxNS4zODJjMC00LjI4OSAzLjQ5NS03Ljc3MyA3Ljc5NC03Ljc3MyA0LjMwNSAwIDcuNzk0IDMuNDg0IDcuNzk0IDcuNzczIDAgNC4yOTQtMy40ODkgNy43NzMtNy43OTQgNy43NzMtNC4zIDAtNy43OTQtMy40NzktNy43OTQtNy43NzN6IiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGlkPSJwYXRoOSIgLz48L3N2Zz4=");
  background-repeat: no-repeat;
  background-size: cover;
  width: 50px;
  height: 60px;
  margin: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
1
votes

I wrote you a function that calculates the correct angle according to the old direction

She needs to get the old and new angle It will return the direction you need to add to reach the new direction (If the arrow needs to move back it will return minus and it will move back)


function newRotate(oldV, newV) {
  const diff1 = newV - oldV;
  let myOldV = oldV;
  if (newV < oldV) newV += 360;
  else myOldV += 360;

  const diff2 = newV - myOldV;
  const shortDiff = Math.abs(diff1) < Math.abs(diff2) ? diff1 : diff2;
  return oldV + shortDiff;
}

Here's how to use it

const value =newRotate(oldValue,newValue)

 Animated.timing(routeAnimtion, {
      toValue: value,
      duration: 10,
    }).start();