- Home /
Transform rapidly spins when flipped upside down.
Hello all, I am programming a game where the character is a bug that sticks to walls. The system works perfectly fine, until you go upside down. Once the character climbs to a point where a wall turns into a ceiling (at or very close to 180 degrees in the x axis) the character starts glitching up and rapidly rotating.
The following code has been modified to remove excess stuff, if the additional code is needed, I can paste the full thing of it.
var gravity : Vector3 = Vector3(0,20,0);
var isGrounded: boolean;
var deltaGround: float = 0.2; // character is grounded up to this distance
var PredictionAccuracy : float = 5;
private var myNormal: Vector3; // character normal
private var QNormal : Quaternion;
private var turnAngle: float = 0; // current character direction
var isJumping : boolean = false; // flag "I'm jumping to wall"
private var RotationX = 0.0;
function FixedUpdate(){
var ray: Ray;
var hit: RaycastHit;
// rotate character to myNormal...
var rot = Quaternion.FromToRotation(Vector3.up, myNormal);
RotationX -= Input.GetAxis("Mouse X") / 15;
var v : Vector3 = Vector3(Mathf.Cos(RotationX),0,Mathf.Sin(RotationX));
var angle : float = Mathf.Atan2(v.x,v.z)*Mathf.Rad2Deg;
turnAngle = angle;
rot *= Quaternion.Euler(0,turnAngle,0); // and to current direction
transform.rotation = rot;
ray = Ray(transform.position, -myNormal); // cast ray downwards
if (Physics.Raycast(ray, hit, (deltaGround-0.1)*2)){ // use it to update myNormal and isGrounded
isGrounded = hit.distance <= deltaGround;
if(isGrounded == true){
QNormal = Quaternion.LookRotation(myNormal);
QNormal = Quaternion.Slerp(QNormal, Quaternion.LookRotation(hit.normal), Time.deltaTime * 12);
myNormal = QNormal*Vector3.forward;
}
}else{
isGrounded = false;
}
if(isGrounded == false){
PredictLanding();
myNormal = QNormal*Vector3.forward;
}
}
}
function PredictLanding(){//The entire point of this function is to chart a trajectory when the character jumps. It then looks at the normal of the face where it will land and rotates the player so he will land perfectly on it.
var hit: RaycastHit;
var ARay = new Ray[PredictionAccuracy];
var Vel : Vector3 = Vector3(0,0,0);
for(i = 0; i < PredictionAccuracy; i++){
var Pos : Vector3 = Vector3((rigidbody.velocity.x*i)/PredictionAccuracy,((rigidbody.velocity.y+Vel.y)*i)/PredictionAccuracy,(rigidbody.velocity.z*i)/PredictionAccuracy);
Vel -=gravity/PredictionAccuracy;
var Pos2 : Vector3 = Vector3((rigidbody.velocity.x*(i+1))/PredictionAccuracy,((rigidbody.velocity.y+Vel.y)*(i+1))/PredictionAccuracy,(rigidbody.velocity.z*(i+1))/PredictionAccuracy);
Debug.DrawLine(transform.position + Pos,transform.position + Pos2);
ARay[i] = Ray(transform.position+Pos, Pos2);
if (Physics.Raycast(ARay[i], hit, (rigidbody.velocity.magnitude*(deltaGround-0.1)*2)/PredictionAccuracy)){ // use it to update myNormal and isGrounded
QNormal = Quaternion.LookRotation(myNormal);
QNormal = Quaternion.Slerp(QNormal, Quaternion.LookRotation(hit.normal), Time.deltaTime * 4);
return;
}else {
QNormal = Quaternion.LookRotation(myNormal);
QNormal = Quaternion.Slerp(QNormal, Quaternion.LookRotation(Vector3.up), Time.deltaTime * 4);
}
}
}
I have tried changing the Quaternion.Euler values to other things but to no avail. After looking at the rotation values in the inspector, it looks like a combination of all 3 rotations are rapidly spazzing out as soon as it flips totally (or near totally) upside down. The glitch is incredibly disruptive because it limits me to designing courses that never go upside down, which is really a downer in a game that relies on sticking to walls. Some more information on the glitch is that the character seems drawn to go downwards. If you are on a sphere and position yourself and your angle such that you are going perfectly alone the equator of the sphere, it will quickly turn you if you go forward to draw you to the bottom. And the problem is, as soon as you reach the bottom of the sphere, I assume it is trying to continue downward when you are already upside down, so it just flips out. Any advice on how to fix the issue?
At the top you wrote: ceiling bad, walls good; but then below you write the side of a sphere is a also problem. It looks fine to me, but my guess is that more testing would show you have a problem on anything that isn't straight up. $$anonymous$$ore of a problem the more tilted you are.
Try commenting stuff out, like no turning -- see if straight up a loop-the-loop works.
I know this problem (which I call South Pole $$anonymous$$adness) but have not found a solution yet. I suspect that the cause is the following: a quaternion is a rotation of some angle around an arbitrary axis; when we use FromToRotation, Unity must figure out which axis to use, what it probably calculates with a cross product of the from and to vectors. When the angle between the vectors is almost 180 degrees, however, the resulting vector axis becomes too small, maybe small enough to give an unstable vector due to the limited float precision (many math operations are used to calculate the cross product and normalize it, each one contributing with a small error).
I'm working on this; stay tuned, and I'll let you know about any solution I find.
@Owen. I tried removing rotations before to no avail. Also about the ceiling and stuff. For some reason, when on a perfectly flat surface, he acts just fine. It is only when the mesh has any sort of curves on it that he acts bizarrely. The same thing happens on terrain. When I press forward on a hill, he automatically tries to orient so he is going to the lowest point.
Answer by MirrorIrorriM · Oct 19, 2011 at 12:46 AM
I managed to solve it. Although the character rotates along the world z axis when he jumps between opposite walls (a trait I find undesirable), he doesn't freak out when he is on the bottom of something.
Instead of using Quaternion.FromToRotation() I decided to create a point coming off of the normal and find the angle to reach it and use that.
var E : Vector3 = transform.InverseTransformPoint(transform.position + myNormal);
var anglex : float = Mathf.Atan2(E.z,E.y)*Mathf.Rad2Deg;
transform.Rotate(anglex,0,0);
E = transform.InverseTransformPoint(transform.position + myNormal);
var anglez : float = -Mathf.Atan2(E.x,E.y)*Mathf.Rad2Deg;
transform.Rotate(0,0,anglez);
RotationX = Input.GetAxis("Mouse X") * 8;
transform.Rotate(0,RotationX,0);
This solves the issue of flipping out like a maniac quite well. If anyone has any ideas on how to keep him from rotating along the world z axis when he jumps between walls, and instead use his relative z axis, please tell me.
Can rotate on locale z using zLocRot=Quat.AngleAxis( zAng, transform.forward);
(but zAng is?) That gets you ballpark, then you should be able to use that Quat as a waypoint (in the sense 10 is a waypoint in 10-2=8, but doesn't "go through" 10) by combining with Quat.FromToRot(zFlppedUp, targetnormalUp).
To figure zAng, start with the standard trick to convert one local space to another: use Quat.FtoR(trans.up, vector3.up) to figure the rotation to twist your up into world space. Don't bother doing it, since you know the answer is (0,1,0). Apply that to the target normal (your InverseTransformPoint may be doing that already -- hurts my head.) You now have target.norm in your local coords. Project to x,y (you have that part) and atan, and you have zAng for best spin on local z.
To much set-up for me to test this.
Answer by Owen-Reynolds · Oct 19, 2011 at 01:41 AM
I'm thinking the problem is the extra degree of freedom in FromToRotation.
Imagine the normal is sticking out forwards and to the right (so the slope ahead is angled down and to the right.) In your mind, Q.FtoR should tilt you forwards (around x) then sideways (around z.) Technically, once you are lined up, the extra y-spin could be anything. But, it shouldn't add extra, so should have 0 y-spin and basically be facing forwards.
BUT, it might be quicker to first spin around y, to face the normal, then spin forwards on local x. In that case, the model is facing rightish. It's really worst than that -- quaternions aren't x,y,z spins and don't even understand the concept of "not changing the y-spin."
This snippet of test code (yours, stripped down) shows the problem (floor is a plane: tilt the plane in scene view. As it tilts more sideways, the facing squirms around on us as a y-spin becomes better. At nearly upside down, it is stable, but tiny changes affect y-spin):
public Transform floor;
public float spin = 0;
void FixedUpdate () {
Vector3 norm = floor.up;
Quaternion QQ = Quaternion.FromToRotation(Vector3.up, norm);
transform.rotation = QQ;
transform.position = floor.position + floor.up*10;
if(Input.GetKey("left")) spin-=0.1f; // no problems with this spin,
if(Input.GetKey("right")) spin+=0.1f; // except based off messed-up
Quaternion RR = Quaternion.Euler(0,spin,0); // normal spin
transform.rotation *= RR;
}
Since we are tilting from Vector3.up, the best tilt tends to twist y slightly towards the normal, which is "down."
I would take advantage of that mystical "best combo of x,y,z" process. Instead of using Quat.FromtoRot(up,norm)
, use Quat.FromToRot(trans.up, norm)
. That will give the best flip for the bug. So, if you are leaning a bit forwards and jump to the roof, you'll finish the summersault. If you "pop a wheelie" and then jump to the roof, you'll complete the rest of it. If you start yourself leaning to the left and jump, the "best" spin will be to keep spinning left and land on the ceiling facing forwards. In theory, learning to "start the roll" the way you want might actually be fun.
I think you'd have to redo turnangle
as a change to current, instead of an absolute, if you did that.
Your right, using Quat.FTR(trans.up) ins$$anonymous$$d of an absolute vector solved the problem. Thanks for your help! Only thing I really changed was ins$$anonymous$$d of using trans.up I used trans.right. This made so he would always rotate in a way that would make the camera twist, not hurl it forward.