Rotating a circle with mouse with direction - c#

I am rotating a circle with a mouse but I want there to be a limit on how far the circle can be rotated. (Lets say 3 full times). When it reaches it's limit it can no longer be turned in that same direction, but the opposite direction it can be. I got it stopping after the max turns but now I'm trying to find the direction and every time my mouse passes the x-axis of the circle the direction changes because atan2 gives me the angle relative to the x-axis. So the new mouse position is in one quadrant and the last position is in another quadrant so subtracting these angles doesn't give me what I want. Did I explain this well? Any suggestions?
private void HelmPb_MouseMove(object sender, MouseEventArgs e)
{
if ((e.Button != MouseButtons.Left) || _dontTurn) return;
double angle = OffsetAngle();
Point temp = MousePosition;
float degrees = Convert.ToSingle(angle - _offsetAngle);
float diff = _lastHelmAngle - degrees;
float absDiff = Math.Abs(diff) % 360;
if (absDiff > 180) absDiff = 360 - absDiff;
double angle1 = Math.Atan2(_lastHelmPoint.Y, _lastHelmPoint.X);
if (angle1 < 0) angle1 += 2*Math.PI;
double angle2 = Math.Atan2(temp.Y, temp.X);
if (angle2 < 0) angle2 += 2*Math.PI;
double direction = angle1 - angle2;
direction = direction*(180/Math.PI);
_deltaHelmTurn += Convert.ToSingle(absDiff);
if (_deltaHelmTurn >= (_maxHelmTurn*360.0))
{
if (direction > 0 && _lastHelmDirection > 0)
{
_deltaHelmTurn = Convert.ToSingle(_maxHelmTurn*360.0);
degrees = Convert.ToSingle(_maxHelmTurn*360.0)%360;
}
}
_lastHelmDirection = direction;
_lastHelmPoint = MousePosition;
_lastHelmAngle = Convert.ToSingle(degrees);
_sameHelmRotation = Convert.ToSingle(degrees);
HelmPb.Image.Dispose();
WaterDepthPlot.Update();
HelmPb.Image = RotateImage(_originalHelmImage, -degrees);
HelmPb.Update();
}
private double OffsetAngle()
{
int helmXMid = HelmPb.PointToScreen(Point.Empty).X + (HelmPb.Width / 2);
int helmYMid = HelmPb.PointToScreen(Point.Empty).Y + (HelmPb.Height / 2);
double angle = AngleFromPoints(MousePosition, new Point(helmXMid, helmYMid));
return angle;
}
private double AngleFromPoints(Point pt1, Point pt2)
{
Point p = new Point(pt1.X - pt2.X, pt1.Y - pt2.Y);
double alpha;
if (p.Y == 0) alpha = p.X > 0 ? 0d : 180d;
else
{
double f = 1d * p.X / (Math.Sqrt(p.X * p.X + p.Y * p.Y));
alpha = Math.Acos(f) * 180d / Math.PI;
if (p.Y > 0) alpha = 360d - alpha;
}
return alpha;
}

The direction of rotation between two vectors is equivalent to the sign of their cross product.
//Returns 1 for CCW, -1 for CW, 0 for no change
private int Direction(Point from, Point to)
{
double cross = (from.x * to.y) - (from.y * to.x);
return Math.Sign(cross);
}

Related

How to clamp camera rotation axis correctly?

I am trying to clamp the X and Y axis of my camera, which I have managed to do. However, when the clamped value reaches the MAX threshold it will jump back to the MIN threshold!
Any idea what is causing this in my code?
private void ClimbingLookRotation()
{
if (input.mouseX != 0 || input.mouseY != 0f)
{
orientation.rotation *= Quaternion.AngleAxis(input.mouseX, Vector3.up);
orientation.rotation *= Quaternion.AngleAxis(input.mouseY, Vector3.right);
}
var rotX = orientation.eulerAngles.x;
var rotY = orientation.eulerAngles.y;
rotX = Mathf.Clamp(rotX, 1, 25);
rotY = Mathf.Clamp(rotY, 200, 355);
orientation.eulerAngles = new Vector3(rotX, rotY, 0);
}
Any help would be appreciated!
Thankyou.
I found something the works well so thought I'd answer in case it would help anyone!
public static float ClampAngle(float angle, float min, float max)
{ //Normalises angle value passed in between -180 to 180 to make the angle clampable
angle = NormalizeAngle(angle);
if (angle > 180)
{
angle -= 360;
}
else if (angle < -180)
{
angle += 360;
}
min = NormalizeAngle(min);
if (min > 180)
{
min -= 360;
}
else if (min < -180)
{
min += 360;
}
max = NormalizeAngle(max);
if (max > 180)
{
max -= 360;
}
else if (max < -180)
{
max += 360;
}
return Mathf.Clamp(angle, min, max);
}
public static float NormalizeAngle(float angle)
{ //If the angle is above or below 360 degrees, normalise it
while (angle > 360)
angle -= 360;
while (angle < 0)
angle += 360;
return angle;
}
And then just call the ClampAngle method where ever you need to clamp a value, for example:
private void ClimbingLookRotation()
{
if (input.mouseX != 0 || input.mouseY != 0f)
{
orientation.rotation *= Quaternion.AngleAxis(input.mouseX, Vector3.up);
orientation.rotation *= Quaternion.AngleAxis(input.mouseY, Vector3.right);
}
var rotX = orientation.eulerAngles.x;
var rotY = orientation.eulerAngles.y;
rotX = HelperFunctions.ClampAngle(rotX, -10, 25); //Here
rotY = HelperFunctions.ClampAngle(rotY, 200, 340); //And here
orientation.eulerAngles = new Vector3(rotX, rotY, 0);
}
I've called it in my camera rotation function to clamp both rotX and rotY, which is later applied to the rotation of my orientation game object.
Thankyou again to Ruzihm for your help before :)
You could re-center your euler angles around the center value then clamp the difference:
float centralX = 13f;
float extentX = 12f;
float centralY = 277.5f;
float extentY = 77.5f;
private static float ClampEuler(float val, float center, float extent)
{
return center + Mathf.Clamp((val - center + 360f) % 360f, -extent, extent);
}
private void ClimbingLookRotation()
{
if (input.mouseX != 0 || input.mouseY != 0f)
{
orientation.rotation *= Quaternion.AngleAxis(input.mouseX, Vector3.up);
orientation.rotation *= Quaternion.AngleAxis(input.mouseY, Vector3.right);
}
var rotX = orientation.eulerAngles.x;
var rotY = orientation.eulerAngles.y;
rotX = ClampEuler(rotX, centralX, extentX);
rotY = ClampEuler(rotY, centralY, extentY);
orientation.eulerAngles = new Vector3(rotX, rotY, 0);
}
By the way you may want to do orientation.rotation *= Quaternion.AngleAxis(input.mouseY, orientation.right); to rotate around the local right instead of global right. Rotating around global right may produce a rolling effect over time which you may not want.

Clapming rotation of child objects in unity

I have a game object that contains another game object that should be able to rotate towards target (imagine a tank turret). And so I've created the below script:
public class Rotator : MonoBehaviour {
public GameObject _enemy;
void Update () {
var actualTarget = _enemy.transform.position;
var targetDir = actualTarget - transform.position;
var step = 2 * Time.deltaTime;
var target = Quaternion.LookRotation(targetDir.normalized, Vector3.up);
var actual = target * Quaternion.Inverse(transform.parent.rotation);
var targetRotation = Quaternion.Slerp(transform.localRotation, actual, step);
targetRotation.eulerAngles = ClampRotation(targetRotation.eulerAngles);
transform.localRotation = targetRotation;
}
private static Vector3 ClampRotation(Vector3 eulerAngles) {
var x = Mathf.Clamp(eulerAngles.x > 180 ? eulerAngles.x - 360 : eulerAngles.x, -180, 180);
var y = Mathf.Clamp(eulerAngles.y > 180 ? eulerAngles.y - 360 : eulerAngles.y, -45, 45);
return new Vector3(x, y, 0);
}
}
Objects setup:
The rotation of the object named "parent" is 90deg on the Y axis, everything else is not rotated.
Clamping on the y axis works well - the rotation stays between -45 and 45 degrees. The rotation however doesn't work on the x axis (with, or without clamping).
So the goal here is that when I move the cube left or right, the red one rotates between [-45,45] degrees around the Y axis and when I move it top or down, the red one rotates between [-180,180] degrees around the X axis.
I had some success using the LookAt method of the Transform clas, but for some reason if I try to manually modify eulerAngles of a localRotation it suddenly looses the possibility to rotate backwards on the X axis even though I'm only doing something to the Y values...
After long hours of trial and error and browsing the internet frenzily, I managed to find an answer that I could tailor to my needs. Word of comment to the first if statement of the Clamp method - this is useful if I want the object to also be clamped in it's inverted position (if the target is behind it):
void Update() {
transform.LookAt(_target.transform);
var rotation = transform.localRotation;
var eulers = ClampRotation(rotation.eulerAngles);
transform.localEulerAngles = eulers;
}
private static Vector3 ClampRotation(Vector3 eulerAngles) {
var x = Clamp(eulerAngles.x, -60, 60);
var y = Clamp(eulerAngles.y, -45, 45);
return new Vector3(x, y, 0);
}
private static float Clamp(float angle, float min, float max) {
if ((angle <= 180 && angle >= 180 - Mathf.Abs(min)) || (angle >= 180 && angle <= 180 + max)) {
return Mathf.Clamp(angle, 180 - Mathf.Abs(min), 180 + max);
}
if (angle > 180f) {
angle -= 360f;
}
angle = Mathf.Clamp(angle, min, max);
if (angle < 0f) {
angle += 360f;
}
return angle;
}
EDIT:
As it turns out, sometimes it's better to create your own fine-grained solution, you can modify more easily, so to anyone who is interested, you can do what I wanted to do with the below code as well:
void Update() {
var actualTarget = _enemy.transform.position;
var targetDir = actualTarget - transform.position;
var target = Quaternion.LookRotation(targetDir.normalized, transform.up);
var actual = Quaternion.Inverse(transform.parent.rotation) * target;
actual.eulerAngles = ClampRotation(actual.eulerAngles);
var targetRotation = Quaternion.Slerp(transform.localRotation, actual, 8 * Time.deltaTime);
transform.localRotation = targetRotation;
}
private static Vector3 ClampRotation(Vector3 newRotation) {
var x = Clamp(newRotation.x, -179, 179);
var y = Clamp(newRotation.y, -45, 45);
return new Vector3(x, y, 0);
}
private static float Clamp(float angle, float min, float max) {
if ((angle <= 180 && angle >= 180 - Mathf.Abs(min)) || (angle >= 180 && angle <= 180 + max)) {
return Mathf.Clamp(angle, 180 - Mathf.Abs(min), 180 + max);
}
if (angle > 180f) {
angle -= 360f;
}
angle = Mathf.Clamp(angle, min, max);
if (angle < 0f) {
angle += 360f;
}
if (Mathf.Abs(angle) == 360) {
angle = 0;
}
return angle;
}

Planet Orbit Counter clockwise insead of Clockwise

I made a game where planets orbit clockwise (More of a simulation). I'm wondering how I would make it go counter clockwise.
Host = the object the planet is orbiting
entity = planet
double angle;
if (host.Y == entity.Y && host.X == entity.X) //small offset
angle = Math.Atan2(host.Y - entity.Y + (Random.NextDouble()*2 - 1),
host.X - entity.X + (Random.NextDouble()*2 - 1));
else
angle = Math.Atan2(host.Y - entity.Y, host.X - entity.X);
float angularSpd = host.GetSpeed(s.Speed)/s.Radius;
angle += angularSpd*(time.thisTickTimes/1000f);
double x = entity.X + Math.Cos(angle)*radius;
double y = entity.Y + Math.Sin(angle)*radius;
Vector2 vect = new Vector2((float) x, (float) y) - new Vector2(host.X, host.Y);
vect.Normalize();
vect *= host.GetSpeed(s.Speed)*(time.thisTickTimes/1000f);
host.MoveTo(host.X + vect.X, host.Y + vect.Y);
Try changing the angle += angularSpd... to angle -= angularSpd...

Circle C# Mathematics

Ok...this question will get complicated for most...I'm trying to figure out a formula for 2 things so i can write all of this in nearly one line of code and have it go on for as long as I'd like..
formula for commented sections so i can get rid of the need for i > && i < by using ratio's in some way.
formula for angle 2.4f, 1f, 0.6f 0.5f(probably can be more precise but...how?!
Don't need help with radius it's perfect increment of 1.2f this is probably part of the formula needed to find out the other 2 in someway?
There is so much math going on here that it is just getting a little complicated for me and I just can't figure out how to reduce it down past this point...
pointNum = (i * 1.0f) / 7;
if (i > 0 && i <= 6) //cannot divide by 0
{
angle = pointNum * Mathf.PI * 2.4f;
radius = 1.2f;
}
else if(i > 6 && i <= 20) //3.3333~
{
angle = pointNum * Mathf.PI * 1f;
radius = 2.4f;
}
else if(i > 20 && i <= 43) //2.15
{
angle = pointNum * Mathf.PI * 0.6f;
radius = 3.6f;
}
else if(i > 43 && i <= 79) //1.837209302325581
{
angle = pointNum * Mathf.PI * 0.5f;
radius = 4.8f;
}
float x = Mathf.Sin(angle) * radius;//radiusX;
float z = Mathf.Cos(angle) * radius;//radiusZ;
pos = new Vector2(x, z);
The end result looks like:
The radius of concentric circles will be the small circle diameter plus a separation by a integer factor.
C1 Diameter = 1 * ((2*R) + S);
C2 Diameter = 2 * ((2*R) + S);
To know how many small circles can be created, you have to calculate the angle (green filled) that made yellow lines. This angle is easily calculated if you take the triangle made by yellow, green and blue lines.
cos = green line length / yellow line length;
green line length = C1 Diameter;
yellow line length = sqrt( blue line length * blue line length + green line length * green line length);
with the cosine you can calculate the angle with acos function.
later you can divide 360 by the angles and you get the number of circles.
of course it will not by exact, but the decimal part can by distributed among all circles.
Alright so...Here is both methods i got part of Blau's answer translated however...the performance was already far worse just from that it turns out my way is wayyyy faster so guess doing it manually and creating ranges is going to be my only choice thanks for the information though guys.
void CreateConcentricCircles(int i)
{
//Method1
pointNum = (float)i / 7;
if (angleCache != pointNum * Mathf.PI)
angleCache = pointNum * Mathf.PI;
if (i > 0 && i <= 7) //cannot divide by 0
{
angle = angleCache * 2f;
radius = 1.2f;
}
else if(i > 7 && i <= 21) //3.3333~
{
angle = angleCache * 1f;
radius = 2.4f;
}
else if(i > 21 && i <= 44) //2.15
{
angle = angleCache * 0.6f;
radius = 3.6f;
}
else if(i > 44 && i <= 72) //1.837209302325581
{
angle = angleCache * 0.5f;
radius = 4.8f;
}
else if(i > 72 && i <= 103)
{
angle = angleCache * 0.45f;
radius = 6f;
}
else if(i > 103 && i <= 138)
{
angle = angleCache * 0.4f;
radius = 7.2f;
}
else if(i > 138 && i <= 151)
{
angle = angleCache * 0.37f;
radius = 8.4f;
}
float x = Mathf.Sin(angle) * radius;//radiusX;
float z = Mathf.Cos(angle) * radius;//radiusZ;
pos = new Vector2(x, z);
//Method2
/*if (i > 0 && i <= 6) //cannot divide by 0
radius = 1.2f;
else if(i > 6 && i <= 20) //3.3333~
radius = 2.4f;
else if(i > 20 && i <= 43) //2.15
radius = 3.6f;
else if(i > 43 && i <= 71) //1.837209302325581
radius = 4.8f;
else if(i > 71 && i <= 102)
radius = 6f;
else if(i > 102 && i <= 150)
radius = 7.2f;
float C1 = 1 * ((2*radius) + i);
//float C2 = 2 * ((2*radius) + i);
//what's blue line? is it C2?
float anglex = Mathf.Sin(C1) * radius;
float anglez = Mathf.Cos(C1) * radius;
pos = new Vector2(anglex, anglez);*/
}

Rotate angle to target angle via shortest side

Hi and thanks for reading.
I need to change this void I wrote so that it can work with negative angles.
The goal of this function is to rotate an ANGLE towards the DIRECTION by adding INCREMENT to either clockwise or counterclockwise (+ or -).
However the problem as I said is that it does not work with numbers less than 0 or greater than 360 (2pi). I need to be able to use negative angles as well.
I tried several stuff but couldn't get it to work for a while. Can anyone lend me a hand? I'll be grateful. :D
public void ToDirection(float Increment, float Direction)
{
if (CurrentAngle != Direction)
{
float ClockwiseDifference;
float CounterClockwiseDifference;
//Clockwise
if (Direction < CurrentAngle)
{
ClockwiseDifference = CurrentAngle - Direction;
}
else
{
ClockwiseDifference = Constants.Rotation_360 - (Direction - CurrentAngle);
}
//CounterClockwise
if (Direction > CurrentAngle)
{
CounterClockwiseDifference = Direction - CurrentAngle;
}
else
{
CounterClockwiseDifference = Constants.Rotation_360 - (CurrentAngle - Direction);
}
float CurrentFaceSpeed = Increment;
if (ClockwiseDifference == CounterClockwiseDifference)
{
if (Globals.Randomizer.Next(0, 2) == 0)
{
if (ClockwiseDifference < CurrentFaceSpeed)
{
CurrentAngle = Direction;
}
else
{
CurrentAngle -= CurrentFaceSpeed;
}
}
else
{
if (CounterClockwiseDifference < CurrentFaceSpeed)
{
CurrentAngle = Direction;
}
else
{
CurrentAngle += CurrentFaceSpeed;
}
}
}
else if (ClockwiseDifference < CounterClockwiseDifference)
{
if (ClockwiseDifference < CurrentFaceSpeed)
{
CurrentAngle = Direction;
}
else
{
CurrentAngle -= CurrentFaceSpeed;
}
}
else
{
if (CounterClockwiseDifference < CurrentFaceSpeed)
{
CurrentAngle = Direction;
}
else
{
CurrentAngle += CurrentFaceSpeed;
}
}
}
if (CurrentAngle >= Constants.Rotation_360)
{
CurrentAngle -= Constants.Rotation_360;
}
else if (CurrentAngle < 0)
{
CurrentAngle += Constants.Rotation_360;
}
}
Simply unwrap the angle. Then you'll always have angles from 0 to 360. Unwrap the starting angle and the target angle, then perform your turn. Here's a working example (the only method you really need is UnwrapAngle()).
internal class Program {
private static object UnwrapAngle(double angle) {
if (angle >= 0) {
var tempAngle = angle % 360;
return tempAngle == 360 ? 0 : tempAngle;
}
else
return 360 - (-1 * angle) % 360;
}
private static void TestUnwrap(double angle, double expected) {
Console.WriteLine(String.Format("{0} unwrapped = {1}, expected {2}", angle, UnwrapAngle(angle), expected));
}
private static void Main(string[] args) {
TestUnwrap(0, 0);
TestUnwrap(360, 0);
TestUnwrap(180, 180);
TestUnwrap(360 + 180, 180);
TestUnwrap(-270, 90);
TestUnwrap(-270 - 720, 90);
TestUnwrap(-725, 355);
Console.ReadLine();
}
}
This answer seems to cover the topic quite well:
Work out whether to turn clockwise or anticlockwise from two angles
RE: angles that are less than 0 or greater than 360. Basically, -10 is the same as 350. 720 is the same as 360. So if you translate the incoming angle so that it lies between 0 and 360, all your problems are solved (presuming your code works for values between 0 & 360 as you suggest).
This is something I've done myself (in a different language before):
var wantDir;
var currDir;
var directiondiff;
var maxTurn;
// want - this is your target direction \\
wantDir = argument0;
// max turn - this is the max number of degrees to turn \\
maxTurn = argument1;
// current - this is your current direction \\
currDir = direction;
if (wantDir >= (currDir + 180))
{
currDir += 360;
}
else
{
if (wantDir < (currDir - 180))
{
wantDir += 360;
}
}
directiondiff = wantDir - currDir;
if (directiondiff < -maxTurn)
{
directiondiff = -maxTurn
}
if (directiondiff > maxTurn)
{
directiondiff = maxTurn
}
// return the resultant directional change \\
return directiondiff

Categories