3
\$\begingroup\$

I'd like to limit rotation of an object, so I've written following snippet:

if (targetRotation < constraintMin) {
    targetRotation = constraintMin;
} else if (targetRotation > constraintMax) {
    targetRotation = constraintMax;
}

It works fine when moving in clockwise direction (big chunk starting right of blue lines), however nothing I've tried works work counterclockwise (smaller chunk between blue lines) because of Math.PI -> -Math.PI jump.

Circle

Here's working outer angle constraint between -3 and 3. However I'd like to make one between 3 and -3, but cannot think of how to do that.

const SPEED = 0.05;
let div = document.querySelector('div');
let targetRotation = Math.PI - 0.1;
let rotation = 0;

const CONSTRAINT_MIN = -3;
const CONSTRAINT_MAX = 3;

function rotate(dt) {
  let deltaRotation = (targetRotation - rotation) % (Math.PI * 2);

  let constraintDiffMin = ((CONSTRAINT_MIN - deltaRotation) + Math.PI * 4) % (Math.PI * 2);
  let constraintDiffMax = ((CONSTRAINT_MAX - deltaRotation) + Math.PI * 4) % (Math.PI * 2);

  //This works for outer angle
  if (deltaRotation < CONSTRAINT_MIN) {
    deltaRotation = CONSTRAINT_MIN;
  } else if (deltaRotation > CONSTRAINT_MAX) {
    deltaRotation = CONSTRAINT_MAX;
  }

  //But how to make it constraint inner angle between MAX and MIN?

  if (Math.abs(deltaRotation) > Math.PI) {
    deltaRotation -= 2 * Math.PI * (deltaRotation / Math.abs(deltaRotation));
  }

  rotation += deltaRotation * SPEED;
  div.style.transform = 'rotate(' + rotation + 'rad)';

  requestAnimationFrame(rotate);
}

document.addEventListener('mousemove', function(e) {
  targetRotation = Math.atan2(e.y - 100, e.x - 100);
}, false);

requestAnimationFrame(rotate);
div {
  width: 100px;
  height: 30px;
  background: linear-gradient(45deg, #BADA55, red);
  transform: rotate(0);
  margin: 85px 50px 0;
}
<div>
</div>

How can I resolve this?

\$\endgroup\$
1
  • \$\begingroup\$ Add a testcase to your question please. With what limits and what values does it work and fail. \$\endgroup\$
    – Kromster
    Commented Jan 7, 2016 at 20:12

3 Answers 3

1
\$\begingroup\$

Although circles are continuous, it's easy to get confused if we forget that a circle is a poor representation of angles.

Your code treats this calculation as distinguishing between two sections, "inside" and "outside", but that doesn't really represent the values.

If you "unroll" the circle to see the angles as more of a number line, you get something like this:

enter image description here

Now you can directly see why "outside" is a strange concept to compute with a single if-then. (Not to say it can't be done, it's just less intuitive.)

In your specific case, with these hard-coded MIN/MAX values, it may be easiest to first compute if the angle is between them (what you call the 'big chunk'/'clockwise direction'). Then, if you want to invert that calculation, do so.

// 'inner' being the little wedge in your example
// set this to false if you want to constrain to the bigger chunk
let constrainToInner = true;

// first figure out where it is
let angleIsBetween = true;
if (deltaRotation < CONSTRAINT_MIN || deltaRotation > CONSTRAINT_MAX) {
  angleIsBetween = false;
}

// now decide if it needs to be clamped to one of the CONSTRAINT_???s
if( angleIsBetween === constrainToInner ){
  // then let's clamp by setting it equal to the nearest constraint
  let distanceToMin = Math.abs(deltaRotation-CONSTRAINT_MIN);
  let distanceToMax = Math.abs(deltaRotation-CONSTRAINT_MAX);

  if(distanceToMin < distanceToMax){
    deltaRotation = CONSTRAINT_MIN;
  }
  else{
    deltaRotation = CONSTRAINT_MAX;
  }
}

I tested this in C#, though in degrees instead of radians. It should work fine either way.

\$\endgroup\$
1
\$\begingroup\$

Instead of limiting it on a circle define a range of motion , for example -30 to +30 degrees. The define the center of the motion on your circle. For example 180 degrees.

Now you can limit the range of motion by keeping the angle between those values.

Simply add the center value to the angle and you're done.

In your example the big arc could be like range -160 to +160 degrees, center 0 degrees. The small arc would be -20 to +20, center 180 degrees.

(Degrees and radians are ofcourse the same in concept but values degrees is easier for examples).

\$\endgroup\$
0
\$\begingroup\$

I had exactly the same problem and used this to constrain the rotation of my object to a positive maximum and negative minimum. Maybe this helps.

public float RealClamp(float angle, float min, float max)
{
    if (angle > max && angle < 360 - min) {
        if (angle > 180) {
            angle = 360 - min;
        } else {
            angle = max;
        }
    }

    return angle;
}

RealClamp(constrainedAngle, 36, 36)
\$\endgroup\$
2
  • \$\begingroup\$ Try running this code with angle = -9999999. It's not > max so the if statement fails to trigger and the absurdly negative value is returned unchanged. \$\endgroup\$
    – DMGregory
    Commented Jan 9, 2016 at 14:02
  • \$\begingroup\$ True. I know this isn't pretty but you could of course include the "if(angle > max) angle = max;" bit. \$\endgroup\$
    – Biggzlar
    Commented Jan 9, 2016 at 14:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .