Drag 3D object on specific axis using the mouse cursor - c#

I am trying to build a simple game, where the user can place some elements and move them around.
What I want is to give the user an option to move the objects on specific axis according to his/her decision.
Pretty much like the movement gizmo in unity.
I know how I can drag an object in the world with the mouse but how can I move on specific axis (e.g. Z axis).
I tried to read the "Mouse X" value, but it only work on a specific viewing angle, if I look at the object from different angle the object won't move correctly.
What I did is this:
private void OnMouseDrag()
{
transform.Translate(moveAxis * Input.GetAxis("Mouse X");
}
where moveAxis is a Vector3 that represents the axis, and the script is attached to the arrow gizmo.

Here's one approach:
Convert the moveAxis direction from local space to screen space. Make sure you have a cached Camera field/variable, because calling Camera.main and/or GetComponent<Camera> is a slow operation:
// Get world direction from moveAxis
Vector3 worldDirection = transform.TransformDirection(moveAxis);
Vector2 screenDirection = camera.WorldToScreenPoint(worldDirection + transform.position)
- camera.WorldToScreenPoint(transform.position);
// optionally, normalize the vector in screen space.
// screenDirection.Normalize();
You can normalize the screen direction if you don't want the angle of the axis on the screen to matter to how fast dragging occurs. You probably don't want to normalize it but give it a try if you want a different feel.
Then, calculate the dot product between the axis direction & the movement of the mouse to calculate a magnitude:
Vector2 mouseMovement = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
float translateMagnitude = Vector2.Dot(mouseMovement, screenDirection);
mouseMovement.x must be negative for left and positive is right and mouseMovement.y must be negative for down and positive for up. If either axis is inverted, multiply that axis's value by -1f.
This way, translateMagnitude will be positive if the mouse is moved in the direction of the axis in screen space, and negative if it is moved against it.
Then, multiply the magnitude by some configurable sensitivity factor and multiply that by the axis vector for the Translate call:
public float translateSensitivity;
...
transform.Translate(moveAxis * translateSensitivity * translateMagnitude);

Related

How to rotate around a corner when the distance is unknown

I feel like this is a solvable problem but I am just to stupid to solve it.
I want to able to move a player around a cube in third person in a 3D game. Before I worry about the corners where 3 edges of the cube meet I would first like to be able to move around two edges. If I use the LookAt method while jumping around a corner some problems occur with the vertical rotation of the player. So if he enters the trigger of the edge he turns around 180 degree somehow. That is why I am searching for a different solution.
(Green rectangle is the player. The dotted area is a trigger.)
So what I am given on the entry of the trigger is the edge position P2 and obviously the player position P3. The player should be able to stop the velocity while being mid air, so he could just fall down to the edge thanks to the gravity. That means that I can not just lerp it from the entry point to some exit point. The entry and exit points are dynamic.
What I already calculated is the distance from the player to the edge. Therefore I could tell if the player is closer to the right surface or the left surface judging by whether X or Y is higher.
But if the player enter on a higher position the numbers are also higher.
I am very confused about how to solve this. I need to lerp rotate the player around the corner P2 as long as he is within the two dotted lines.
Here's something to get you started. This will rotate your player so that their down is pointing at the edge, and their forward vector tries to remain unchanged as much as possible. This will mean that moving in playerTransform.forward will move them where they are facing but will rotate them so that as they move around the edge, they will rotate around it.
transform playerTransform;
Vector3 edgeWorldDirection;
Vector3 edgeWorldPosition;
Vector3 playerWorldPosition;
// Determine up direction
Vector3 player2Edge = edgeWorldPosition - playerWorldPosition;
Vector3 playerUpDirection = (
Vector3.Dot(player2Edge, edgeWorldDirection) * edgeWorldDirection
- player2Edge).normalized;
// rotate player to align local up with playerUpDirection and forward with
// previous forward as much as possible.
Vector3 playerRightDirection = Vector3.Cross(playerUpDirection, playerTransform.forward);
// handle player looking "up"
if (playerRightDirection == Vector3.zero) playerRightDirection = player.Transform.right;
Vector3 playerForwardDirection = Vector3.Cross(playerRightDirection, playerUpDirection);
playerTransform.rotation = Quaternion.LookRotation(playerForwardDirection, playerUpDirection);
For corners it's actually easier because your up is based on the location of the corner, you don't have to calculate the closest point on a line.
transform playerTransform;
Vector3 cornerWorldPosition;
Vector3 playerWorldPosition;
// Determine up direction
Vector3 playerUpDirection = (playerWorldPosition - cornerWorldPosition).normalized;
// rotate player to align local up with playerUpDirection and forward with
// previous forward as much as possible. Same logic here as previously.
Vector3 playerRightDirection = Vector3.Cross(playerUpDirection, playerTransform.forward);
// handle player looking "up"
if (playerRightDirection == Vector3.zero) playerRightDirection = player.Transform.right;
Vector3 playerForwardDirection = Vector3.Cross(playerRightDirection, playerUpDirection);
playerTransform.rotation = Quaternion.LookRotation(playerForwardDirection, playerUpDirection);
Depending on how you're handling movement & player orientation, you may not actually want to rotate the player's transform, but with the results of Quaternion.LookRotation(...) you can multiply the local direction the player tries to go by that rotation to get the world direction that would correspond to.:
Vector3 localDesiredMove;
Quaternion boxOrientationQuat = Quaternion.LookRotation(
playerForwardDirection,
playerUpDirection);
Vector3 worldBoxOrientedMove = boxOrientationQuat * localDesiredMove;

Arrow rotating to face cursor needs to only do so while inside an angle made by two given directions

I have a 2d arrow rotating to always face the a target (the target in this case is the cursor), the pivot is my player character. I need to restrict this arrow to only follow the target if it is inside an angle of the player, an example would be 90 degrees, so it would only follow if the cursor is in the top right part of the screen.
I have worked with vector directions and methods such as Vector2D.angle, but they all seem to have some restriction i can't workaround, Vector2D.angles restriction is that the 3rd position it uses to calculate the angle is the world center(0, 0), my player is mobile so that doesn't work.
So i think what im asking is if theres a way to store an angle, and then check if something is within that.
Here is the code i use for rotating my arrow, theres more to the script but i removed the unnecesary parts:
public float speed;
public Transform target;
void Update()
{
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 direction = target.position - transform.position;
target.position = mousePosition;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
Quaternion rotation = Quaternion.AngleAxis(angle, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, speed * Time.deltaTime);
Sorry if this is formatted poorly, its my first time posting here, thank you a million times if you are able to help me, i have been stuck on this for days.
Was asked to clarify question so here is an attempt:
This picture shows an example of what i mean, the arrow is rotating around the center of the circle, (it is rotating so it always points towards my cursor). What i need is a way to restrict it so it only points towards the cursor if it is within a specific angle (red lines in picture), that's what im having trouble with. I can't find a way to store this threshold and i can't seem to find a way to compare the cursors direction with it.
I think it would be possible if it was possible to choose a custom center for Vector2D.angle, but that doesn't seem to be the case.
I hope this clarifies what my question, i may just very well be stupid and overlooking something obvious but i really can't find a way to make this possible.
thanks again.
Important fields/inputs
First, we need to know the boundary directions in world space:
public Vector2 boundaryDirectionLeft;
public Vector2 boundaryDirectionRight;
The important piece is that the angle made clockwise from boundaryDirectionLeft to boundaryDirectionRight is the region the arrow shall remain inside. In the case of your image, boundaryDirectionLeft could be Vector2.up and boundaryDirectionRight could be something like new Vector2(1f,-1f).
We also need to know which local direction the arrow is facing before any rotation is applied. E.g., if the arrow is always pointing with the local red arrow axis (the local right direction), this would be Vector2.right:
public Vector2 localArrowDirection;
And lastly, we need a top speed for our rotation, so we don't warp the arrow around when it's time to rotate. A good value to start with might be 360f, 360 degrees per second. Try experimenting with different values to find one that you like:
public float maxRotationSpeed;
The update procedure
In Update, determine the direction to the target:
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 direction = mousePosition - transform.position;
Then, we need to know where the arrow is currently pointing. We can use Transform.TransformVector to find where the local localArrowDirection is pointing in world space:
Vector2 currentDirection = transform.TransformVector(localArrowDirection);
Then, determine the signed angle from boundaryDirectionLeft to the target direction, the same from boundaryDirectionLeft to boundaryDirectionRight, and the same from boundaryDirectionLeft to the current facing direction:
float directionAngle = Vector2.SignedAngle(boundaryDirectionLeft, direction);
float boundaryAngle = Vector2.SignedAngle(boundaryDirectionLeft, boundaryDirectionRight);
float currentAngle = Vector2.SignedAngle(boundaryDirectionLeft, currentDirection);
These values range from [-180,180], but we want them expressed in the range [0,360) to make the math easier later, so we can add 360f and use Mathf.Repeat on that sum:
directionAngle = Mathf.Repeat(directionAngle+360f, 360f);
boundaryAngle = Mathf.Repeat(boundaryAngle+360f, 360f);
currentAngle = Mathf.Repeat(currentAngle+360f, 360f);
At this point directionAngle is how many clockwise degrees from boundaryDirectionLeft that target is, and boundaryAngle is how many boundaryDirectionRight is, and currentAngle is the same for what direction we're currently facing.
So, now, we need to know how to properly clamp the angle between 0 and boundaryAngle. Anything too far above boundaryAngle is actually closer to the left boundary and should be clamped to the left boundary. In fact, since everything is between 0 and 360, anything higher than boundaryAngle+(360f-boundaryAngle)/2f is closer to the left. So, we just set anything higher than that to be 0 degrees away from boundaryDirectionLeft:
if (directionAngle > boundaryAngle + (360f - boundaryAngle)/2f)
{
directionAngle = 0f;
}
So, now we can clamp directionAngle with a high of boundaryAngle (it is already bottom clamped at 0f, so we can use Mathf.Min here):
directionAngle = Mathf.Min(directionAngle, boundaryAngle);
Now we can limit the angular difference between directionAngle and currentAngle using our maxRotationSpeed:
float deltaAngle = Mathf.Clamp(directionAngle-currentAngle,
-maxRotationSpeed * Time.deltaTime,
maxRotationSpeed * Time.deltaTime);
Now we can rotate the transform deltaAngle degrees clockwise (in world space):
transform.Rotate(0f,0f,deltaAngle,Space.World);

Changing the x and y rotation weirdly changes the z too in Unity

I tried to make a first person controller in Unity so that I know every single detail of it. I made the movement, but when I created the camera rotation i simply got stuck. Ok, it worked on the x axis, on the y axis, but why does the z axis also change?
void Update () {
transform.position = Character.transform.position + offset;
float h = Input.GetAxis("Mouse X") * horizontalSpeed;
float v = Input.GetAxis("Mouse Y") * verticalSpeed;
transform.Rotate(h, v, 0, Space.World);
}
When working with rotations and euler angles you have to understand that there are different coordinate spaces, like the object's local space and a world space. the x,y,z directions in world space will always look at the same direction, whereas the local space is the coordinate space of the local object (for example your camera). Thats why I would recommend you to rotate vertically in local space and horizontally in world space.
transform.Rotate(0,h,0,Space.World);
transform.Rotate(-v, 0, 0, Space.Self);
Rotating your camera vertical in local space will change the object's local up axis (y-axis). this is why then rotating around that axis will give you your unwanted result. Just select the gameobject in your Scene view and observe it's local coordinate system while rotating and you might be able to better visually understand.
To also give you a simple,fun reallife example to observe yourself, sit upright on your office chair and look straight ahead and rotate on your chair. If you now look a little upwards or downwards and rotate your head instead you will notice a difference in rotation between rotating your neck and rotating on your chair, which should always have the same up-axis, which is different from the local up axis of your neck.
ps.: there are already many solutions to implement a first person camera, like in Unity's own standard assets, or SmoothMouseLook
Looking at tutorials, following them and trying to understand them might bring you further than trying to figure it out on your own.
Euler angles are weird. When you rotate with euler angles, the rotations stack, and a rotation around one axis changes the other axis of rotation. It's best not to "add" euler angles (essentially what transform.Rotate() is doing) and best to work with them in absolute terms. In this case, your code should keep the "z axis" at 0 if you do the following:
void Update () {
transform.position = Character.transform.position + offset;
float h = Input.GetAxis("Mouse X") * horizontalSpeed;
float v = Input.GetAxis("Mouse Y") * verticalSpeed;
# Get the current euler angle in absolute terms
Vector3 eulers = Character.transform.localEulerAngles;
# now modify that euler angle, creating a new absolute euler angle
eulers.x += h;
eulers.y += v;
eulers.z = 0;
# and now assign the new euler angle back to the transform, overwriting the old value
Character.transform.localEulerAngles = eulers;
}
In general I'd advise against using Transform.Rotate() for... most everything. I would also advise using quaternions wherever you're able. You don't need a complete understanding of quaternions for them to be very powerful.

MonoGame - Have an object circle around a point

I have an object with a Vector2 Position, and a cursor with Vector2 Position.
When I hold a certain key, I want the object to circle around the object, but I'm having trouble calculating the correct coordinates.
I've managed to make the object circle around the cursor (but it's not going in a perfect circle, but more of a spiral) with this code:
Vector2 diff= Vector2.Normalize(cursor.Location - this.Location);
float angle = (float)Math.Atan2(diff.Y, diff.X) + (float)(90 * (Math.PI / 180));
this.Position += new Vector2((float)(speed * Math.Cos(angle)), (float)(speed* Math.Sin(angle)));
I calculate the angle between cursor's and object's locations, and add 90° (in radians) to that value, which, by my logic, should make the object travel in a perfect circle. However, the distance between the cursor and the object quickly spreads.
What am I calculating wrong here?
Usually when you want something to circle around a point, you define the distance to an amount and you incrementally change the angle in your Update method. THEN in your draw method you can draw it where you should by calculating the position from the cursor.Location, the distance from the cursor and the desired distance.
In most situations like these you want your orbiter to have the same loation like your cursor, so calculating the new position in the Draw method works best, given that these calculations are cheap and super fast (you generally do not want to hog down your Draw method).
I am not able to check it right now, but what you should be doing is something in these lines:
Given that your object should rotate D distance away from your cursor with an angular velocity of AngularVelocity (per second), then when this initially happens, set a variable angle to zero. Then in your update do:
angle += (gameTime.ElapsedGameTime.TotalSeconds * AngularVelocity)
and in your Draw method do:
var displacedPosition = new Vector2(D * Math.Sin(angle), D * Math.Cos(angle));
and render your orbiter using the displacedPosition instead of the normal position if it is currently orbiting.

Simulating steering control using Kinect

I want to use Kinect to simulate a gesture which will allow the user to hold out his both hands as if holding an imaginary steering in mid air. I want to calculate the rotation angle of the vector formed between both his hands and depending upon the amount of rotation(towards his left or right) I want to fire keyboard key press. Cannot figure out how to get the rotation angle. I am using c# in wpf application. Please any help would be appreciated.
Vector3 hr_hl = KinectHelper.VectorBetween(skeleton, JointType.HandRight, JointType.HandLeft);
Vector3 axis_spine = new Vector3(spine.Position.Z);
float angle = (float)Math.Acos(Vector3.Dot(hr_hl, axis_spine));
Vector3 axis = Vector3.Cross(axis_spine, hr_hl);
Quaternion rot = Quaternion.CreateFromAxisAngle(axis, angle);
float rot_radian = KinectHelper.QuaternionAngle(rot);
In short i want the amount of roll angle of the vector joining my right and left hand, my spine Z axis being the axis of rotation.

Categories