Calculating LookAt without applying it - c#

I need to clamp the rotation of a bone while using LookAt.
The LookAt function works great, but it doesn't offer any clamp possibilities, and to my knowledge, LookAt doesn't provide a way seeing the resulting rotation without
without applying it to a transform.
I would therefore like to know if it's possible to first calculate the results of LookAt without applying it first.

Calculate the direction vector you need:
Vector3 direction = bone.transform.position - targetTransform.position;
Use Quaternion.LookAt to calculate the rotation you require:
Quaternion newRotation = Quaternion.LookAt(direction);
Now perform any mathematics you want to perform on the supplied rotation, for example if you only want to rotate the bone a maximum of 10 degrees per second you would do this:
newRotation = Quaternion.RotateTowards(bone.transform.rotation, newRotation, 10f * time.deltaTime);
Finally, apply the rotation to the bone:
bone.rotation = newRotation;

Just apply LookAt, see the result and then clamp the result, applying the final rotation.
It's not uncommon to apply intermediate transforms during a frame (update function or similar). The important thing is that at the end of the Update (or LateUpdate or FixedUpdate) function the transform to be the desired one.

Related

Constrain rigidbody movement to spline at high speeds / sharp curves

I have a 2.5d platformer game. The character is using rigidbody movement on a spline (using the curvy splines asset) which curves into 3d space in all sorts of ways, while the camera stays fixed to the side so that you see the path and background turning, but maintain a 2d side scrolling perspective.
I'm essentially creating a look rotation based on the spline, then moving the player using that forward vector, and making sure to remove any velocity perpendicular to the path so that the player stays centered on the path even when curving. I'm removing the velocity on that vector instead of projecting all the velocity in the direction of the path so that the player can still jump and fall like normal.
void SetLookRotation()
{
// get nearest TF and point on spline
Vector3 p;
mTF = Spline.GetNearestPointTF(transform.localPosition, out p);
// Get forward and up vectors of point on spline
_localHorizontal = Spline.GetTangentFast(mTF);
_localVertical = Spline.GetOrientationUpFast(mTF);
// Set look rotation to path
transform.rotation = Quaternion.LookRotation(Vector3.Cross(_localHorizontal, _localVertical), _localVertical);
}
void Movement()
{
Vector3 m = transform.right * groundAcceleration * moveInput;
rb.AddForce(RemoveCrossVelocity(m));
rb.velocity = RemoveCrossVelocity(rb.velocity);
Vector3 localVelocity = transform.InverseTransformDirection(rb.velocity);
localVelocity.z = 0;
rb.velocity = transform.TransformDirection(localVelocity);
}
Vector3 RemoveCrossVelocity(Vector3 v)
{
// get magnitude going in the cross product / perpindicular of localHorizontal and localVertical vector
// (essentially the magnitude on "local Z" or to the sides of the player)
Vector3 crossVelocity = Vector3.Project(v, Vector3.Cross(transform.right, transform.up));
// and remove it from the vector
return v -= crossVelocity;
}
The first 2 functions are happening in FixedUpdate() in the order shown.
The problem is, when hitting sharp corners at high speeds, some inertia causes the player to deviate off the center of the path still just ever so slightly, and a lot of that momentum turns into upward momentum, launching the player upwards. Eventually the player can fall off the path completely (I do have a custom gravity acting towards the spline though). It works perfectly at lower speeds though, even when dealing with sharp corners. At least as far as I can tell.
I tried a bit of code from https://answers.unity.com/questions/205406/constraining-rigidbody-to-spline.html too but no luck.
Is there a way I could constrain the player rigidbody on a vector that is not one of the global x/y/z axes? I've tried a host of other solutions like setting the transform of the player towards at the center of the spline but I can't seem to get it without feeling very jerky. Using forces makes the player "rubber band" towards and past the center back and forth. Maybe there is something in my math wrong. In any case, I'm hoping someone could help me make sure that the player will always stay on the center of the spline but only on the vector to the sides of the player's face direction, so that it doesn't mess with jumping. Thank you very much in advance!
For potential future visitors, I have figured this out. There are a few components (and a lot more if you're trying to do full spline based physics, but just to start with movement...)
First we must orient our character, so that our local coordinate system can be referenced with transform.right etc. Luckily this package provides these functions which return useful vectors. I'm sure there is math beyond me to do this otherwise if you are building your own spline system.
void SetLookRotation()
{
// get nearest TF and point on spline
Vector3 p;
playerTF = currentSpline.GetNearestPointTF(transform.localPosition, out p);
// Get forward and up vectors of point on spline
_localHorizontal = currentSpline.GetTangentFast(playerTF);
_localVertical = currentSpline.GetOrientationUpFast(playerTF);
// Set look rotation to path
transform.rotation = Quaternion.LookRotation(Vector3.Cross(_localHorizontal, _localVertical), _localVertical);
}
Here I am setting a velocity directly but if you're using forces it's the same principle.
if (Mathf.Abs(localVelocityAs_X) <= maxDashSpeed * Mathf.Abs(moveInput))
{
Vector3 m = transform.right * maxDashSpeed * moveInput;
rb.velocity = RemoveCrossVelocity(m);
}
localVelocityAs_X is defined as (ran in fixedUpdate/ physics step):
float currLocalVelocityX = (playerTF - prevPositionX) / Time.deltaTime;
localVelocityAs_X = Mathf.Lerp(localVelocityAs_X, currLocalVelocityX, 0.5f);
prevPositionX = playerTF;
Where playerTF is your position on a spline (in this case, using the curvy spline package from the unity asset store. Those spline positions return very small floats so in my case I multiplied playerTF by around 10,000 to make it a more easily readable metric). This is essentially just manually calculating velocity of the player each frame by comparing current position on the spline to last frame's.
RemoveCrossVelocity is the same as above. Comment explanations should suffice.
Vector3 RemoveCrossVelocity(Vector3 v)
{
// get magnitude going in the cross product / perpendicular of local horizontal and local vertical vectors
// (essentially the magnitude on "local Z" of the player)
Vector3 crossVelocity = Vector3.Project(v, Vector3.Cross(transform.right, transform.up));
// and remove it from the vector
return v -= crossVelocity;
}
Finally the solution to the drift. My crude fix was essentially to just adjust the player to the center of the spline every frame. Horizontally, there is no change because it grabs the closest spline point which is calculated by this package to be sort of a float clamped between the start and end of the spline. Vertically, we are being set to the distance the player is from the spline in the local up direction - a fancy way of saying we're not moving vertically at all. The reason this must be done is to avoid the spline vertical position overwriting the players, and we obviously can't set this vector back to playerPos.y in our local coordinate space, so we must resort to using a direction vector * the distance from our everchanging floor. This isn't absolutely ideal at the end of the day, but it works, and there isn't any extra jitter from it (interpolate on your player's rigidbody and some camera dampening helps). All in all these together combine to make a player able to accelerate quickly around sharp corners of a spline with physics and intertia will never cause the player to fly off or drift from the center. Take that, rocket physics!
void ResetPlayerToSpline()
{
Vector3 P; //closest spline point to player
float pTf = currentSpline.GetNearestPointTF(transform.position, out P);
playerHeight = Vector3.Distance(transform.position, P);
transform.position = P + (transform.up * Vector3.Distance(transform.position, P));
}
Ultimately for those possibly looking to do some kind of implementation in the future, the biggest thing you'll run into is a lack of cardinal direction, global oriented axis-based functions and properties normally provided by a game engine. For a primer, here are a few I would use (not including gravity, which is simply opposite your up vector times whatever magnitude):
This one allows you to create a vector using x and y like normal (and z in theory) and run this function to convert it when you actually use the vector in a local space. That way, you don't have to try and think in directions without names. You can still think of things in terms of x and y:
Vector3 ConvertWorldToLocalVector(Vector3 v)
{
Vector3 c;
c = transform.right * v.x + transform.up * v.y;
return c;
}
This is basically the same as what is happening in RemoveCrossVelocity(), but it's important to reiterate this is how you set velocity in a direction to 0. The second part shows how to get velocity in a certain vector.
void Velocity_ZeroY()
{
rb.velocity -= GetLocalVerticalVelocity();
}
public Vector3 GetLocalVerticalVelocity()
{
return Vector3.Project(rb.velocity, _localVertical);
}
Getting height, since you cannot just compare y positions:
height = Vector3.Distance(transform.position, P);
I think that's all the good stuff I can think of. I noticed a severe lack of resources for created spline based physics movement in games, and I'm guessing now it's based on the fact that this was quite an undertaking. It has since been brought to my attention that the game "Pandemonium"(1996) is a curvy 3d spline based sidescrolling platformer - just like mine! The main difference seems to be that it isn't at all based on physics, and I'm not sure from what I can tell if it has pitch changes and gravity to compliment. Hope this helps someone someday, and thank you to those who contributed to the discussion.

What does transform.TransformDirection(vector3.forward) * Time.deltaTime * movementspeed do?

I'm trying to make a moving 3d character in unity and I stumbled upon a tutorial that told me to enter this line of code for when the user presses the "w" key and I don't know what the different components mean, could somebody help me?
Start to read the API!
transform is the Transform component attached to the same GameObject as this script is attached to.
Transform.TransformDirection
Transforms direction from local space to world space.
Vector3.forward
Shorthand for writing new Vector3(0, 0, 1)
Time.deltaTime
The completion time in seconds since the last frame
It is mostly used to convert any velocity from units / frame into a value of units / second.
So lets bring it all together:
You get the GameObject's Transform reference via transform.
You take the Vector Vector3.forward which equals 0,0,1 and use transform.TransformDirection in order to convert it to a worldspace forward vector of your GameObject.
Note: This is completely redundant and you shouldn't use this!
Rather directly use transform.forward which does exactly this already for you ;)
Returns a normalized vector representing the blue axis of the transform in world space
Finally you multiply this resulting vector by a magnitude. Currently it has a magnitude of 1 since it is a normalized vector so you want to use its direction but assign it a certain "speed".
So you multiply it by movementspeed a predefined fixed speed value and use Time.deltaTime in order to convert it from a value of movementspeed / frame into a value of movementspeed / second
The transform of an object is its position, rotation and scale.
Transform.direction is used to move the transforms position to a specified vector
Vector3.forward is a vector of x0,y0,z1
Time.deltaTime returns how many seconds passed between one frame to the next
movementspeed would be a variable that you assign with an int or float etc..
basically it moves the object this line is attached to forward by whatever your movement speed is per second

How to get a Vector3 rotation between two Quaternions

I'm writing a script which rotates a Rigidbody using a Configurable Joint. I've got the targetRotation figured out, but now I'm struggling with targetAngularVelocity, which should help me avoid wobbliness if set correctly.
targetAngularVelocity is defined like this in the documantation: "This is a Vector3. It defines the desired angular velocity that the joint should rotate into". The problem is that I don't know how to get this Vector3 based on two Quaternions - current rotation of the object and the target rotation.
Am I not understanding it correctly? Is there a function that returns a rotation vector based on two Quaternions?
So mathematically a Quaternion represents the orientation of a rigid body. Consider the forward problem first, and see how the orientation q_1 transforms to another orientation q_2 after a rotational velocity ω is applied for t time.
Mathematically the rotation vector has a magnitude ω and a direction k such that ω = ω* k
This is done with quaternion multiplication as
q_2 = q_ω * q_1
Where q_ω represents a rotation about the axis of k of an angle θ=ω*t.
In reverse, you need to find q_ω with
var q_ω = q_2 * Quaternion.Inverse(q_1);
and extract the rotation axis and angle
q_ω.ToAngleAxis(out float angle, out Vector3 axis);
and compose the rotational velocity vector, that corresponds to this transformation in time seconds.
var ω = (angle/time)*axis;

Unity3d Quaternion LookRotation not returning proper value

I wanted a projectile to look at the target object, to do that I am using Quaternion LookRotation as below
targetRotation = Quaternion.LookRotation(targetPosition - projectile.transform.position);
if(targetRotation.eulerAngles.magnitude <= 60)
projectile.transform.rotation = targetRotation;
here I have put if condition to make it more realistic turn towards target, otherwise projectile should not turn if its more 60 degree turn.
now as in below image, we can see that the target object in not more than at 60 degree angle, but still while debugging I am getting 328 as targetRotation.eulerAngles.magnitude, which is getting the if condition failed and projectile is not rotating towards the target object.
Quaternion.LookRotation(targetPosition - projectile.transform.position) means "Give me a quaternion that represents a rotation of a vector from up towards targetPosition - projectile.transform.position". eulerAngles is just another representation of the rotation and you won't get anything meaningful from its magnitude.
I suspect you don't want that. Instead, I suspect you want to know whether or not the projectile would need to turn more than 60 degrees from its current forward direction. In that case, you probably want to check the angle between the projectile's forward vector and its direction vector towards the target.
I don't have Unity open so I don't know if this compiles, but it should go something like this:
var directionToTarget = targetPosition - projectile.transform.position;
var angleToTarget = Vector3.Angle(projectile.transform.forward, directionToTarget);
if (angleToTarget < 60) ...
You mentioned you want a more "realistic" turn. What do you want the projectile to do if it's more than 60 degrees?

Calculate target angle given current angle and target position?

I've been struggling with what I think should be a very simple problem:
.
I know the current heading angle (say 15 deg), and given a target gameObject's transform, I want to calculate the angle that I should rotate towards to face the target. In the above example I want to calculate say 335ish deg.
Note that I need to calculate the target angle, I have another class that, given an angle, takes care of rotating the heading to the desired angle. I'm aware of the Transform.LookAt() and other similar functions, they don't apply in this situation because reasons (in the other class I basically calculate a Euler Quaternion and Lerp until I reach the target angle, due to project constraints, Transform.LookAt() doesn't work).
In the first approach, I tried calculating angle theta with the Dot product of Vector3.forward and the direction vector, didn't quite work right (either rotated forever or in the wrong direction)
targetAngle = Vector3.Angle(Vector3.forward, (target pos - current pos).normalized);
I thought maybe if I could calculate the current heading direction vector, I could take the Dot product of that and the target direction vector to get angle theta, then use theta and the current angle to figure out the target angle (360 - theta or something?) to get the angle I want to rotate to. Thing is, I only have the current angle and current pos, I don't understand how to calculate the current heading direction vector from that info. Do I just add some arbitrary constant to the current position's Z value and subtract from that the current position, to get a dir vector? Seems hacky and like it shouldn't work.
Any ideas are welcome.
Edit: Additional Info
The orientation code /u/Asad asked about:
// Calculate the target Quat to rotate all children by
Quaternion targ = Quaternion.Euler(0, 0, targetAngleHeading);
// Calculate the Linear interpolation to apply to all children
Quaternion lerp = Quaternion.Lerp(children[0].transform.localRotation, targ, Time.deltaTime * speedHeading_dps);
foreach (GameObject c in children)
{
// Apply lerp calcualted above to all children
c.transform.localRotation = lerp;
}
// Update the current heading angle 1st child's local z angle
currentAngleHeading = turrets[0].transform.localEulerAngles.z;
Then in FixedUpdate() I have:
if (currentAngleHeading != targetAngleHeading)
{
DesiredHeading();
}
You can take your transform's forward, and the vector to your target, and get an angle or rotation from those:
Vector3 vectorToTarget = target.transform.position - transform.position;
Vector3 facingDirection = transform.forward; // just for clarity!
float angleInDegrees = Vector3.Angle(facingDirection, vectorToTarget);
Quaternion rotation = Quaternion.FromToRotation(facingDirection, vectorToTarget);
Note that the rotation is relative; i.e., it's the rotation you need to apply from the current position to be facing the target.
try this instead
you can directly take the vector distance vector from transform.position and then
vector3.normalize (target.position - object.position)
this is the angle object'll want to move

Categories

Resources