Stabilize object on one axis only - c#

i am trying to stabilize my object to not fall aside. This code works pretty good, but it also stabilize the object to not fall forward or backward.
Is there any possibility to restrict this to only one axis?
var deltaQuat = Quaternion.FromToRotation(_rBody.transform.up, Vector3.up);
deltaQuat.ToAngleAxis(out var angle, out var axis);
_rBody.AddTorque(-_rBody.angularVelocity * 2f, ForceMode.Acceleration);
_rBody.AddTorque(axis.normalized * angle * 5f, ForceMode.Acceleration);

Use the contrints in the rigidbody component:

As I understand your goal is to constraint the rotation on a certain local axis.
You could probably do it somewhat similar to this post just not for positions but rotations going through Rigidbody.angularVelocity.
// get the angularVelocity but in local space relative to the current rigidbody orientation
var localAngularVelocity = Quaternion.Inverse(rigidbody.rotation) * rigidbody.angularVelocity;
// get the euler space representation
var localAngularEulers = localAngularVelocity.eulerAngles;
// now constraint according to your needs e.g. on the local forward axis
localEulerAngles.z = 0;
// now convert back and assign
rigidbody.angularVelocity = rigidbody.rotation * Quaternion.Euler(localAngularEulers);
Note: Typing on smartphone so can't test it right now but I hope the idea gets clear

Related

Why does SignedAngle work properly with transform.position but not with transform.localPosition?

I'm working on a VR motorcycle game and trying to get the steering to work by the player grabbing and rotating the handles. The steering works because of the function CalculateRotation() (in the code block below) which gets the angle between the hands and rotates the handlebars/the bike based on that.
When transform.position is used for the controller positions it works smoothly (the handlebars smoothly rotate to the controller positions), but when transform.localPosition is used it freaks out (the handlebars rotate away from the controller positions as far as they can - 50degrees because the maxRotation variable clamps the rotation to 50degrees) so you can't actually grab onto the handlebars.
I would just use position but when the bike starts moving it can't calculate the angle properly and the handlebars start acting like they do with localPosition.
I either need to figure out why localPosition doesn't work or figure out how to make position work when the bike is moving. But I would like to have some insight on why localPosition isn't working since if I can figure out why it's an issue I can likely solve it.
Some more info:
I tried using transform.TransformDirection/TransformPoint/TransformVector & the inverses but they didn't seem to help. Also the handlebars and the controllers are children of different gameobjects so I think that may be a part of why the localPosition doesn't work but I'm not sure. Any suggestions would be appreciated.
Here's a link of it working with position then not working with localPosition
private Quaternion CalculateRotation()
{
//hand xz + source y (to stay consistent)
//use these positions to figure out do the math for rotation
//USE POSITION NOT LOCALPOSITION FOR IT TO WORK
/*leftControllerPos = leftController.transform.InverseTransformDirection(leftController.transform.position);
rightControllerPos = rightController.transform.InverseTransformDirection(rightController.transform.position);
leftSubPos = leftSub.transform.InverseTransformDirection(leftSub.transform.position);
rightSubPos = rightSub.transform.InverseTransformDirection(rightSub.transform.position);*/
leftControllerPos = leftController.transform.position;
rightControllerPos = rightController.transform.position;
leftSubPos = leftSub.transform.position;
rightSubPos = rightSub.transform.position;
//check which hands are detected to get targetDir
Vector3 firstPos;
Vector3 secondPos;
if (detectedHands == DetectedHands.BothHands)
{
Debug.Log("BothHands");
firstPos = leftControllerPos;
secondPos = rightControllerPos;
Debug.Log("firstPos = " + firstPos + "\nsecondPos = " + secondPos);
}
else if (detectedHands == DetectedHands.LeftHandOnly)
{
Debug.Log("LeftHandOnly");
firstPos = leftControllerPos;
secondPos = rightSubPos;
}
else if (detectedHands == DetectedHands.RightHandOnly)
{
Debug.Log("RightHandOnly");
firstPos = leftSubPos;
secondPos = rightControllerPos;
Debug.Log("firstPos = " + firstPos + "\nsecondPos = " + secondPos);
}
else
{
Debug.Log("None");
firstPos = leftControllerPos;
secondPos = rightControllerPos;
}
//do math with correct targetDir
Vector3 targetDir = secondPos - firstPos;
Vector3 right = transform.right;
float angle = Vector3.SignedAngle(targetDir, right, Vector3.up);
angle = Mathf.Clamp(angle, -maxRotation, +maxRotation);
AdjustedAngle = -angle;
//visualizeDirection.transform.localRotation = Quaternion.Euler(new Vector3(0, -angle, 0));
//Quaternion bikeRotation = FindObjectOfType<BikeController>().transform.localRotation;
Quaternion firstRot = Quaternion.Euler(new Vector3(-24, 0, 0));
Quaternion secondRot = Quaternion.Euler(new Vector3(0, AdjustedAngle, 0));
Quaternion adjustedRotation = firstRot * secondRot; //bikeRotation *
Debug.Log("adjustedRotation = " + adjustedRotation);
return adjustedRotation;
}
I think instead of InverseTransformDirection you would rather want to use InverseTransformPoint!
From the API
You should use Transform.InverseTransformPoint if the vector represents a position in space rather than a direction.
You are currently treating some world space positions as if they were directions - this is only valid for the world origin 0,0,0 ;)
Further it makes no sense to convert each individual object position into it's own local space ... the result would always be 0,0,0!
If you want to calculate in local space of an object it would always need to be the same objects local space!
So since you later compare to this object's transform.right probably
leftControllerPos = transform.InverseTransformPoint(leftController.transform.position);
rightControllerPos = transform.InverseTransformPoint(rightController.transform.position);
leftSubPos = transform.InverseTransformPoint(leftSub.transform.position);
rightSubPos = transform.InverseTransformPoint(rightSub.transform.position);
Then later when you come to the SignedAngle you would also have to use accordingly Vector3.right instead of the world space vector transform.right in order to use local space.
Facit: If world space positions works then why bother around with local space positions?

Diagonal speed is too fast

How can I keep the diagonal speed to be the same as the horizontal and vertical speed without clamping any value or using ".normaized". I tryed to normalize the values but I lost the joystick values between 1 and 0. Here is my code :
void ListenInput()
{
Vector3 rightDirection = camera.right;
Vector3 frontDirection = camera.GetForwardFromAngleY();
move = new Vector2(
Input.GetAxis("Horizontal"),
Input.GetAxis("Vertical")
);
MoveCharacter(rightDirection * move.x);
MoveCharacter(frontDirection * move.y);
}
void MoveCharacter(Vector3 velocity)
{
transform.position += velocity * Time.deltaTime * runningSpeed;
}
Here, you should clamp the magnitude of the input Vector2.
For example with Vector2.ClampMagnitude() from the Unity API.
That will keep the input non-binary and prevent the diagonal from getting larger than purely horizontal/vertical inputs.
void ListenInput()
{
Vector3 rightDirection = camera.right;
Vector3 frontDirection = camera.GetForwardFromAngleY();
move = new Vector2(
Input.GetAxis("Horizontal"),
Input.GetAxis("Vertical")
);
move = Vector2.ClampMagnitude(move, 1f);
MoveCharacter(rightDirection * move.x);
MoveCharacter(frontDirection * move.y);
}
void MoveCharacter(Vector3 velocity)
{
transform.position += velocity * Time.deltaTime * runningSpeed;
}
If you normalize a vector you will make sure it's length is 1. This is a great way to avoid quirks like "diagonal movement is faster than normal movement".
However, the fact that the length is always 1 also means that there is no "move slowly" or "move at full speed" distinction from the joystick. When you say "I lost the joystick values between 1 and 0" is due to this fact.
One way developers get around this is by using a mathematical formula to scale the speed.
You could:
Use the largest value (horizontal or vertical) to control the speed
Use the smallest value
Use a combination of the two
Another way to do this is to store how long ago the movement started, then scale the speed based on that. This method has its own challenges, but is very familiar to players.
Examples
For instance, if I have:
horizontalInput = 1
verticalInput = 0.5
This means my normalized vector looks like this:
I could:
Use the largest value
Move at full speed (1) on the direction of my vector.
Use the smallest value
Move at half speed (0.5) on the direction of my vector.
Use a Use a combination of the two values
For this instance, lets use the following formula: (x+y)/2.
Move at 3/4 speed (0.75) on the direction of my vector.
NOTE: This formula will not "feel" as nice if you have x=0 and y=1, this is just an example. You most likely want to use Min, Max, Avg and if-clauses to control how the speed works.
You can use different formulas and different techniques to make the movement in your game feel like what you want, but take the time to analyze WHY it feels like that.

normalizing euler angles rotation vector

I need to display the rotation in Euler angles of an object's certain axis.
I am aware that retrieving the rotation of an object in Euler angles gives inconsistent results, some of which can be solved by simply using modulo 360 on the result. however one permutation that unity sometimes does when assigning a vector with the value of "transform.localRotation.eulerAngles" is instead of retrieving the Vector3 "V", it retrieves "(180, 180, 180) - V".
to my understanding, "(180, 180, 180) - V" does not result in the same real world rotation as V, unlike "(180, 180, 180) + V" which does leave the actual rotation unaffected.
what is the explanation for the phenomenon, and what is the best way of normalizing an Euler angles rotation vector assuming I know the desired and feasible value of one of its axes? (for example, to normalize it such that all of it's values are mod 360 and it's Z axis equals 0 assuming it does have a representation in which Z = 0)
I don't know about the first part of the question (it is different enough to be its own question imo) but I can answer your second one.
So, you have these inputs :
Quaternion desiredRotation;
float knownZ;
And you're trying to find Vector3 eulers where eulers.z is approximately knownZ and Quaternion.Euler(eulers) == desiredRotation.
Here's the procedure I would use:
First, determine the up direction rotated by desiredRotation and the up and right direction rotated by a roll of knownZ:
Vector3 upDirEnd = desiredRotation * Vector3.up;
Quaternion rollRotation = Quaternion.Euler(0,0,knownZ);
Vector3 upDirAfterRoll = rollRotation * Vector3.up;
Vector3 rightDirAfterRoll = rollRotation * Vector3.right;
We know the local up direction after desiredRotation is applied and that the only thing that can adjust the up direction after the roll knownZ is applied is the rotation done by the euler pitch component. So, if we can calculate the angle from upDirAfterRoll to upDirEnd as measured around the rightDirAfterRoll axis...
float determinedX = Vector3.SignedAngle(upDirAfterRoll, upDirEnd, rightDirAfterRoll);
// Normalizing determinedX
determinedX = (determinedX + 360f) % 360f;
...we can determine the x component of eulers!
Then, we do the same with the yaw component of eulers to make the new forward direction line up with the end forward direction:
Vector3 forwardDirEnd = desiredRotation * Vector3.forward;
Quaternion rollAndPitchRotation = Quaternion.Euler(determinedX, 0, knownZ);
Vector3 forwardDirAfterRollAndPitch = rollAndPitchRotation * Vector3.forward;
Vector3 upDirAfterRollAndPitch = upDirEnd; // unnecessary but here for clarity
float determinedY = Vector3.SignedAngle(forwardDirAfterRollAndPitch, forwardDirEnd, upDirAfterRollAndPitch );
// Normalizing determinedY
determinedY = (determinedY + 360f) % 360f;
Vector3 eulers = new Vector3(determinedX, determinedY, knownZ);
To ensure that the given quaternion can be made with the given component, you could check if the axes given to SignedAngle actually can rotate the input vector to the target vector, or you can just compare the calculated eulers and the given quaternion:
Quaternion fromEuler = Quaternion.Euler(eulerAngles);
if (fromEuler==desiredRotation)
{
// use eulerAngles here
}
else
{
// component and quaternion incompatible
}
Hopefully that helps.
I'm not quite sure I understand your question correctly, but the euler angles just represent the angles of 3 rotations applied around the 3 axis in a specific order, right? So why would you normalize it by adding 180 everywhere? You should bring each angle individually into the range 0-360 by modulo-ing them.
Your question seems to imply that you can obtain any orientation by only rotating around two axis instead of three... is that what you are trying to achieve?
Using quaternions could possibly help you, in fact an orientation can be defined with just 4 scalar values: an axis and an angle

unity3d - Accelerometer sensitivity

I am testing the accelerometer code in Unity3D 4.3. What I want to do is simple change the object angle while tilting the ipad, to fake view angle like real live. Everything works fine except for the fact that the accelerometer is a bit too sensitive and I can see the GameObject is like of flickering even I put it on table. How can I make it less sensitive so that even when you hold with your hand the angle will change according to the tilt and the object remain steady?
Here are my code:
void Update () {
Vector3 dir = Vector3.zero;
dir.x = Mathf.Round(Input.acceleration.x * 1000.0f) / 1000.0f;
dir.y = Mathf.Round(Input.acceleration.y * 1000.0f) / 1000.0f;
dir.z = Mathf.Round(Input.acceleration.z * 1000.0f) / 1000.0f;
// clamp acceleration vector to the unit sphere
if (dir.sqrMagnitude > 1)
dir.Normalize();
// Make it move 10 meters per second instead of 10 meters per frame...
dir *= Time.deltaTime;
dir *= speed;
acx = dir.x;
acy = dir.y;
acz = dir.z;
transform.rotation = Quaternion.Euler(dir.y-20, -dir.x, 0);
}
You may need to use a low pass filter (s. Exponential Moving Average for a better description regarding software) before using the signal output. I always use native code to get accelerometer and gyroscope values on iPhone so I am not 100% sure how Unity handles this. But from what you are describing the values appear unfiltered.
A low pass filter calculate a weighted average from all your previous values. Having for example a filter factor on 0.1 your weighted average is:
Vector3 aNew = Input.acceleration;
float a = 0.1f * aNew + 0.9f * a;
This way your values are smoothed at the expense of a small delay. Running the accelerometer with 50 Hz you won't notice it.
I couldn't make Kay's example work as it was not multiplying the last part, so here's my small correction:
Vector3 aNew = Input.acceleration;
a = (0.1 * aNew) + (0.9 * a);

Correct rotation with Quaternion

I have some problems with a rotating marble.
I've tried it with Matrix.CreateFromYawPitchRoll and Matrix.CreateRotation but there were some problems, I think it's due to the Gimbal lock effect.
So, I've tried using quaternions instead, but nothing changed.
When moving on only an axis it works fine, but when the rotation occurs on two different axes the marble still rotates on wrong axes.
Here's my code:
// declarations
Vector3 Position = Vector3.Zero;
Vector3 Rotation = Vector3.Zero;
Quaternion qRotation = Quaternion.Identity;
AbsoluteBoneTransforms = new Matrix[Model.Bones.Count];
Model.CopyAbsoluteBoneTransformsTo(AbsoluteBoneTransforms);
In the Update method:
Position += speed;
Rotation = speed * MathHelper.ToRadians(-1.5f);
Quaternion rot = Quaternion.CreateFromAxisAngle(Vector3.Right, Rotation.Z) *
Quaternion.CreateFromAxisAngle(Vector3.Backward, Rotation.X);
qRotation *= rot;
And in the Draw method:
effect.World = AbsoluteBoneTransforms[mesh.ParentBone.Index] *
Matrix.CreateFromQuaternion(qRotation) * Matrix.CreateTranslation(Position);
What's wrong? Is it wrong to use Quaternion.CreateFromAxisAngle on multiple axes?
EDIT
I've tried calculating directly the axis of rotation of my marble, instead of using combination of multiple axes:
angle += speed.Length() * angularVelocity;
qRotation = Quaternion.CreateFromAxisAngle(Vector3.Cross(speed, Vector3.Up), angle);
qRotation.Normalize();
angle is a float that keeps track of the current movement.
This solution doesn't seem to create Gimbal lock, but marble rotations aren't correct, it seems that the rotating speed is not constant, but became faster and slower over time, I can't understand why.
If I "concatenate" the quaternions I get every frame using
qRotation *= Quaternion.CreateFromAxisAngle(Vector3.Cross(speed, Vector3.Up), angle)
the Gimbal lock effect is still visible.
Here's how I've tackled that:
I'm assuming speed is a vector representing the direction the ball is rolling and whose magnitude represents the rate it is traveling in that direction.
Vector3 axis = Vector3.Cross(speed, Vector3.Up);
float angle = speed.Length();//factor by delta time if neccesary
Quaternion rotationThisFrame = Quaternion.CreateFromAxisAngle(axis, angle * (1/radiusOfBall));
then you can concatenate that to your qRotation. Also, you may need to normalize your quaternion after concatenation.
Update: The correct answer to this question/thread was reversing the order that quaternions concatenate in. With respect to XNA, matrices combine left to right, quaternions combine right to left.

Categories

Resources