- Home /
[SOLVED] Rotation/angled offset in Quaternion.Lerp for a carried GameObject
I'm working on a first person game (more of an anti game) where the player can carry and throw various game objects.
When a key is pressed, the script first checks to see if an object can be grabbed or used, and if it can be grabbed it starts a coroutine to hold and carry the object. The coroutine functions for the duration that the object is held, Slerping the heldObject position and Lerping the heldObject rotation against the position and rotation of the main camera respectively. It performs very well, and any direction the camera rotates in, the heldObject also rotates in that direction.
However, due to the way Lerp/Slerp function, when grabbing the heldObject the heldObject rotates to face away from the camera, no matter what orientation the object was in when it was picked up. As a result, items on the floor or against a surface often rotate immediately into surrounding objects, either pushing the other objects or stalling the heldObject in some way, causing issues.
What I would like to do is add an offset to the Lerp rotation so that the heldObject maintains its initial orientation from the mainCamera perspective, but still Lerp rotation from the mainCamera.
As an example, if i grab a cube with the left side of the cube facing the mainCamera, I want the left side to continue facing the mainCamera when the player carries the object around. currently, grabbing a cube quickly rotates it so the back side is facing the camera, and that causes issues.
the relevant portion of my script is as follows:
IEnumerator HoldObject (GameObject heldObject) {
holding = true;
heldObject.transform.SetParent (null); //detach heldObject from any parent, like drawers or surfaces
heldObject.gameObject.GetComponent<Rigidbody> ().velocity = Vector3.zero; //prevent existing velocity from affecting carry
heldObject.gameObject.GetComponent<Rigidbody> ().angularVelocity = Vector3.zero; //prevent existing angular veloctiy from affecting carry
heldObject.gameObject.GetComponent<Rigidbody> ().useGravity = false; //prevent gravity from affecting carry
distance = Vector3.Distance (heldObject.transform.position, mainCamera.transform.position); //set initial carry distance to heldObject initial distance from player
while (holding == true) { //the "HoldObject" coroutine only functions while holding an object
//TODO: figure out how to calculate angular offset to shift rotation so that the object doesn't automatically snap forward
heldObject.transform.position = Vector3.Slerp (heldObject.transform.position, mainCamera.transform.position + mainCamera.transform.forward * distance, 0.1f);
heldObject.transform.rotation = Quaternion.Lerp (heldObject.transform.rotation, mainCamera.transform.rotation, 0.1f);
if (Input.GetAxis ("Mouse ScrollWheel") > 0f) { //scroll mousewheel up to increase distance of heldObject
if (distance < 3f) {
distance += 0.2f;
}
}
if (Input.GetAxis ("Mouse ScrollWheel") < 0f) { //scroll mousewheel down to decrease distance of heldObject
if (distance > 1f) {
distance -= 0.2f;
}
}
yield return new WaitForFixedUpdate();
}
}
I am aware that a * operator can be used to mix/add/multiply/something two Quaternions together, and I have tried to create a Quaternion offset at the start of the coroutine using various methods (Angle, AngleAxis, FromToRotation, LookRotation) between the mainCamera and the heldObject, but every single one results in the heldObject spinning wildly out of control. The only time it doesn't spin wildly is when the offset is very near 0, which happens in some configurations on the initial grab as the heldObject.transform.rotation is 0, 0, 0 in the world by default for many of the objects I'm testing with.
I imagine the final code for rotation would look something like this:
heldObject.transform.rotation = (Quaternion.Lerp (heldObject.transform.rotation, mainCamera.transform.rotation, 0.1f) * offset);
but I cannot figure out how to create a proper offset that doesn't send my heldObjects into a tailspin.
any help would be appreciated, I'm still somewhat new this so hopefully I haven't left out a vital detail that would lead someone else to a correct answer to the problem I'm having.
Ultimately if it can't be fixed it still behaves beautifully, I'm just being nit-picky about the behavior of the script and I'd really prefer to perfect it.
Thank you
Answer by Mindmapfreak · Oct 03, 2016 at 10:39 PM
If I undestand you correctly you want something like:
//When picking up the object
offset = Quaternion.Inverse(mainCamera.transform.rotation) * heldObject.transform.rotation;
//Then
heldObject.transform.rotation = mainCamera.transform.rotation * offset;
//Or with lerp (remove the line above if you use this one and vice versa)
heldObject.transform.rotation = Quaternion.Lerp(heldObject.transform.rotation, mainCamera.transform.rotation * offset, 6.0f*Time.deltaTime); //You should probably use Time.deltaTime in your timed calculations to make your game framerate independent
I am not sure what you mean by
but still Lerp rotation from the mainCamera
and why you Lerp the rotation, shouldn't the object instantly have the correct rotation?
Both solutions work with slight variations, you helped lead me to the source of my problem! I was adding the offset to the outside of the Lerp calculation, which forced the Lerp to calculate against the offset at the start of each frame. By including the offset within the Lerp (or disregarding it entirely, per your example on line 4) the desired result is achieved.
I was just overcomplicating things when the solution was simple. thank you!
Answer by txzeenath · Oct 04, 2016 at 03:00 AM
Just an idea from a noob.
You may need calculate a transform with Quaternion.Inverse and the item's original rotation to get a proper offset. (Quaternion.Inverse(itemRot)*cameraRot). If I remember right, multiplying by the inverse will give you your relative transform.
I'm sure someone who actually has practice with this stuff could give a clean example. (My multiplication order is probably flipped too lol)
You could also Lerp the rotation based on Vector3.Distance between the target position and current position. So the closer it gets to the target position the higher the lerp "step" on rotation. Which should give it a smooth rotation that gets faster as it approaches center.
float bufferZone = 100F;
float minSpeed = 0.001F;
float maxSpeed = 0.1F;
float deltaDist = Vector3.Distance(heldObject.transform.position,mainCamera.transform.forward * distance);
float step = MathF.Clamp(bufferZone/deltaDist,minSpeed,maxSpeed);
heldObject.transform.rotation = Quaternion.Lerp (heldObject.transform.rotation, mainCamera.transform.rotation, step);