Quaternion offsets not consistent - c#

Current problem:
I'm using world space quaternion offsets to rotate a player model's hands to the player's true hands using:
private Quaternion GetOffsetFromAvatar(Quaternion a, Quaternion b)
{
Quaternion _offset = a * Quaternion.Inverse(b);
return _offset;
}
then applying it with:
public void OffsetTarget(Transform Target, Transform PlayerHand, Transform AvatarBone)
{
/// OffsetTarget() is to be used to auto align avatar hands to player hands.
Quaternion _offset = GetOffsetFromAvatar(PlayerHand.rotation,
AvatarBone.GetComponentInChildren<AvatarOffsetPoint>().transform.rotation);
Target.rotation = _offset * PlayerHand.rotation;
}
The expected result should allow players to change their model on the fly during runtime and the hands align correctly to the player's real hands, but when I test that, the hands face in completely different directions.
.

Solution
In the OffsetTarget() method; I needed to change the last line were the offset is finally applied to the avatar.
From:
...
Target.rotation = _offset * PlayerHand.rotation;
}
To:
...
Target.rotation = _offset;
}
Someone smarter than me, correct me if I'm wrong about the reasoning behind this answer:
I was assuming I was doing the Quaternion math in world space, but the PlayerHand.rotation; part of the line was actually applying the math relative to the player's hand, instead of world space by using Quaternion.identity;.
I really need to stop asking a question just to answer it a few minutes later...

Related

Is there a way to create a curve when launching a gameobject to the player?

This is what I have tried so far:
I create a raycast and if it hits an object on layer 8 (the layer in which objects need to be launched to the player), I call the SlerpToHand() function.
private void Update()
{
if(Physics.Raycast(transform.position, transform.forward * raycastLength, out hit))
{
if(hit.collider.gameObject.layer == 8)
{
// Launch object to player
SlerpToHand(hit.collider.transform);
}
}
}
Inside of SlerpToHand(), I set the object's position to Vector3.Slerp(), that vector being created from values in the hit object.
private void SlerpToHand(Transform hitObj)
{
Vector3 hitObjVector = new Vector3(hitObj.transform.position.x, hitObj.transform.position.y, hitObj.transform.position.z);
hitObj.position = Vector3.Slerp(hitObjVector, transform.position, speed);
}
But the result of this is all wrong, the object just gets teleported to the player's hands. Is Vector3.Slerp() not a good way to curve an object to the player? For context I am trying to recreate Half-Life: Alyx's grabbity gloves. There is still some work to do with the hand gestures but I am just trying to get the object curve down. Help is much appreciated, let me know if more info is needed.
See unity docs:
public static Vector3 Slerp(Vector3 a, Vector3 b, float t);
Here, t is a normalized position between two input values. It means, if t = 0, result will be exactly first value. If t = 1, result will be exactly second value. If t = 0.5, result will be the middle between two values.
So, usually, you need to call Slerp every Update, step by step increasing t from 0 to 1. For this, usually Time.deltaTime used (which equals the time between updates). For speed control, multiply your speed by Time.deltaTime.
Update()
{
if (t < 1)
{
t += Time.deltaTime * speed;
hitObj.position = Vector3.Slerp(startPosition, endPosition, t);
}
}
...and in this case, for start moving, you just need to set t = 0. Probably, you have to implement your own logic here, but this should show the idea.
In addition:
Slerp used to interpolate between vector directions, for positions use Lerp.
Consider use DOTween plugin - its free and powerful for such cases.

Setting Quaternion through inspector window

I was looking for a way to set the Quaternions (x, y, z, w) through the inspector window. We get all these variables when we click on "Debug" mode in Unity. Through Unity docs, I got to know that these values are between 0-1. So how do we set for angles such as 90,-90,180,-180,270,.... MAIN THING here is that I want to set the target rotations in the script of this game object so that the gameObject moves from initial rotation to target rotation.
For example in "Normal" window, if I set the target rotation of x as 180 (shown as -5.008956e-06 in the inspector window), the gameObject moves from 0 to -180, instead of +180. That is the reason I moved to "Debug" window thinking it helps here to set it. But the values here range between 0-1, so does anyone have an idea of how to calculate this?
Moreover, for rotation I am using this one line:
transform.localRotation = Quaternion.Slerp(transform.localRotation, targetRotation, Time.deltaTime * RotationSpeed);
It sounds like you want to be able to adjust it via a Vector3 just how Unity does it in the Transform Inspector. You could do something like
public Vector3 targetRotationV3;
private Quaternion targetRotation;
private void Start()
{
targetRotation = Quaternion.Euler(targetRotationV3);
}
or if you need to be more flexible
private void Update()
{
targetRotation = Quaternion.Euler(targetRotationV3);
...
}
Then for my comment what I mean is that Slerp interpolates a value between the first and the second argument using the factor between 0 and 1.
Since you every frame use a new value as start point, namely the current rotation, this will get slower and slower to the end and depending on your given speed never reach the target rotation.
It makes little sense to use Time.deltaTime here which just divides your speed by about 60 (for 60 FPS). Usually you rather want a constant interpolation factor between 0 and 1. If the frame-rate goes up it might even rotate back since in this case the Time.deltaTime would get smaller!
So you either rather want a constant interpolation factor
[Range(0f,1f)] private float slerpFactor = 0.5f;
private void Update()
{
targetRotation = Quaternion.Euler(targetRotationV3);
transform.localRotation = Quaternion.Slerp(transform.localRotation, targetRotation, slerpFactor);
}
or if you want to rotate with a constant speed instead use Quaternion.RotateTowards
private void Update()
{
targetRotation = Quaternion.Euler(targetRotationV3);
transform.localRotation = Quaternion.RotateTowards(transform.localRotation, targetRotation, Time.deltaTime * RotationSpeed);
}
where your RotationSpeed is now in ° / second
As I said in my comment, don't set Quaternion directly ever, unless you are really confident in your understanding of them, as pointed out in the unity docs (emphasis mine).
They are based on complex numbers and are not easy to understand intuitively. You almost never access or modify individual Quaternion components (x,y,z,w); most often you would just take existing rotations (e.g. from the Transform) and use them to construct new rotations (e.g. to smoothly interpolate between two rotations). The Quaternion functions that you use 99% of the time are: Quaternion.LookRotation, Quaternion.Angle, Quaternion.Euler, Quaternion.Slerp, Quaternion.FromToRotation, and Quaternion.identity. (The other functions are only for exotic uses.)
Rather what you want to do is set the initial and target rotations as Vector3 (Eulerangles) from the inspector and use the build in Quaternion.Euler(); method to let Unity figure out the transformation from Eulerangles to Quaternions.
This would look something like this (Note that I am doing this in an update for the example, and using a float time that I update from the inspector to change the rotation, this is just done for ease of example and not the best way to do implement the t parameter of Quaternion.Slerp):
public Vector3 initialrotation;
public Vector3 targetRotation;
public float time;
void Update()
{
// Let Unity figure out what the appropriate Quaternions are for the given Eulerangles
// Note that this can better be done in Start if initialRotation and targetRotation never change. Just put it here for simplicity
var initialQuaternion = Quaternion.Euler(initialrotation);
var targetQuaternion = Quaternion.Euler(targetRotation);
var slerp = Quaternion.Slerp(initialQuaternion, targetQuaternion, time);
transform.rotation = slerp;
}

How to make camera relative movement

I'm learning unity and c#, and want to make my movement to be camera relative movement instead of world relative movement. How do I do that?
I'm learning unity and c#, my unity version is 2018.3.12f1. I would be happy for help.
just to let know, instead of moving the cam I'm rotating the player.
void Update()
{
float AxisY = Player.transform.eulerAngles.y;
/* Movement starts here */
Vector3 Movement = new Vector3 (Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { //running code
Player.transform.position += Movement * running_speed * Time.deltaTime;
} else {
Player.transform.position += Movement * speed * Time.deltaTime;
}
/*Movement ends here */
/* Rotation controller starts here */
Quaternion target = Quaternion.Euler(Player.transform.eulerAngles.x, Player.transform.eulerAngles.y, Player.transform.eulerAngles.z);
/*if (Player.transform.eulerAngles.x != 0 || Player.transform.eulerAngles.z != 0 || Player.transform.eulerAngles.y != 0) {
Player.transform.rotation = Quaternion.Euler(0,0,0);
}*/
if (Input.GetKey(KeyCode.E))
{
Debug.Log("E got pressed");
//float AxisYPositive = Player.transform.eulerAngles.y;
AxisY = AxisY+1;
Player.transform.rotation = Quaternion.Euler(0, AxisY, 0);
} else if (Input.GetKey(KeyCode.Q))
{
Debug.Log("Q got pressed");
//float AxisYNegetive = Player.transform.eulerAngles.y;
AxisY=AxisY-1;
Player.transform.rotation = Quaternion.Euler(0, AxisY, 0);
}
}
}
The player's movement is world relative, how to make the movement camera relative?
If you want to make the movements relative to the gameObject, call the method Transform.Rotate() on the transform of the gameObject you want to rotate rather than modifying its Quaternion directly. Just make sure the final argument is set to Space.Self.
if (Input.GetKey(KeyCode.E))
{
Debug.Log("E got pressed");
//float AxisYPositive = Player.transform.eulerAngles.y;
AxisY = AxisY+1;
Player.transform.Rotate(Quaternion.Euler(0, AxisY, 0), Space.Self);
}
In general you don't want to directly mess with objects transform.rotation, at least not unless you at least somewhat understand quaternions (I don't!).
I can see a few issues with your code, but the common thread seems to be that you don't really understand how transforms work. Specifically, you might want to look into World/Local space.
The usual way to control a player goes roughly like this:
void DoMovement(Transform player)
{
//If you move first your controls might feel 'drifty', especially at low FPS.
Turn(player);
Move(player);
}
void Turn(Transform player)
{
float yaw = Input.GetAxis("Yaw") * time.deltaTime; //Aka turn left/right
player.Rotate(0, yaw, 0, Space.Self);
// Space.Self is the default value, but I put it here for clarity.
//That means the player will rotate relative to themselves,
//...instead of relative to the world-axis, like in your code.
}
You didn't ask about movement, but as-is your character will always move relative to the world. The below should make it move relative to the camera.
Transform _cameraTransform; //Assumes this is set druing Start()
void Move(Transform player)
{
var forwardMove = _cameraTransform.Forward; //Get whatever direction is 'forward' for camera
forwardMove.Y = 0; //Don't want movement up and down.
forwardMove = forwardMove.normalized; //Normalize sets the 'power' of the vector to 1.
//If you set Y to 0 and don't normalize you'll go slower when camera looks down
//...than when camera is flat along the plane
player.position += forwardMove * Input.GetAxis("Vertical") * time.deltaTime;
//Here you could do the same for strafe/side to side movement.
//Would be same as above, but using the transform.right and Horizontal axis
}
Now, I'm making some assumptions here since you haven't specified what kind of game it is and what kind of controls you want. I'm assuming you have a character running around on a mostly flat plane (no aircraft/spaceship controls), and that the camera is attached to the player. This might not not actually be the case.
In any case I advice you to check out the tutorials, especially the Roll-a-Ball tutorial which I have found is good for beginners to get a grasp on basic players controls that are not just world-relative. The other tutorials, too, are pretty good if you think they're interesting.
Aside from the official Unity tuts a ton of decent to amazing tutorials out there, including video tutorials, so for something like this you could just search for <game type> tutorial and pick whatever seems good to you. While getting started I advice you to avoid the shortest videos, as you will likely benefit greatly from explanation that only fits in longer videos. Of course, that doesn't mean you should pick the longest videos either.
In case someone needs to move an object and don't care about colliders, you can use transform.Translate and assign to his second parameter relativeTo your camera (or any transform) to automatically calculate the translation relative to the object assigned.

Detect initial orientation for Gyroscope Camera, but heading only

So I'm using a script I found on here (How to enable gyroscope camera at current device orientation) BUT I only want this initial device orientation to affect the heading.
Quaternion offset;
void Awake()
{
Input.gyro.enabled = true;
}
void Start()
{
//Subtract Quaternion
offset = transform.rotation * Quaternion.Inverse(GyroToUnity(Input.gyro.attitude));
}
void Update()
{
GyroModifyCamera();
}
void GyroModifyCamera()
{
//Apply offset
transform.rotation = offset * GyroToUnity(Input.gyro.attitude);
}
private static Quaternion GyroToUnity(Quaternion q)
{
return new Quaternion(q.x, q.y, -q.z, -q.w);
}
For the backstory, I have a few interior renderings, with a gyro control to look around the space. I don't want to affect the entire world orientation, I just want them to be facing in the "correct" direction, in terms of the best view of the space. (If not, it just depends on how the device is oriented and its common to jump into a space looking at a random, uninteresting corner of the room.)
With the script above, it resets the camera on all three axes (and therefore the entire world orientation), so if the user is holding the tablet at an unusual angle, then things get really weird.
Here's a quick image to assist the explanation:
I always want the initial view to be aligned with the green arrow, not something random like the red arrow. But just the heading, it's OK to be looking at the floor or ceiling as long as the heading is right in that sweet spot of the room.
Thanks!

Calculating/Predicting a way

I'm just starting with physics, so I'm not always sure about what I'm doing. It's a 2D project but I'm using 3D physical objects like SphereCollider etc..
What I have:
Objects floating in space and affecting each other through gravity:
protected virtual IEnumerator OnTriggerStay(Collider other) {
yield return new WaitForFixedUpdate();
if(other.attachedRigidbody) {
Vector3 offsetVector = this.transform.position - other.transform.position;
float distance = offsetVector.magnitude;
float gravityForce = (other.rigidbody.mass * mass) / Mathf.Pow(distance, 2);
// Clamp gravity.
if(gravityForce > 1.0F) {
gravityForce = 1.0F;
}
other.attachedRigidbody.constantForce.force = offsetVector.normalized * gravityForce;
}
}
There are controllable objects on which the player can click and drag a line away from the object in order to give it a force (shoot) in the opposite direction.
What I want to achieve:
The player should see a rough prediction of the way while aiming. That means that the way-prediction needs to take in account the current velocity, the force which would be applied when the player release the mouse button and the gravity of the surrounding objects.
What I have tried so far:
For testing purposes I just save the computed/predicted positions in an array and draw those positions in OnDrawGizmos().
I wrote a method which returns the gravity influence for a certain position called computeGravityForPosition(Vector3 position).
And thats how I try to calculate the positions:
private void drawWayPrediction() {
Vector3 pos = this.transform.position;
// The offsetVector for the shooting action.
Vector3 forceVector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition);
forceVector.z = 0.0F;
// The predicted momentum scaled up to increase the strength.
Vector3 force = (forceVector.normalized * forceVector.magnitude);
// 1. I guess that this is wrong, but don't know how to do it properly.
momentum = this.rigidbody.velocity + force;
for(int i = 0; i < predictionPoints.Length; i++) {
float t = i * Time.fixedDeltaTime;
momentum += computeGravityForPosition(pos);
pos += momentum * t * t;
predictionPoints[i] = pos;
}
}
At the beginning, when the objects just slowly approaching each other it looks okay. After the first shot, the prediction is completely wrong. I guess it is because of 1. in the code. Just adding the force to the velocity is probably horrible wrong.
Thank you very much for your time.
EDIT:
I removed seemingly unnessecary parts.
I still think that the main problem lays in 1. in the code. I just don't know how to mix up the current movement of the object (from which I only have the current velocity as far as I know the physics engine of unity) with the new created force:
Vector3 forceVector = pos - Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 force = (forceVector.normalized * forceVector.magnitude);
So if you are using a new version of unity probably above 2018, you can use the nice method
Physics.Simulate(dt); // delta time, dt, is the amount of time to simulate.
https://docs.unity3d.com/ScriptReference/Physics.Simulate.html
https://docs.unity3d.com/2018.3/Documentation/ScriptReference/PhysicsScene.Simulate.html
By using this function you can manually advance the simulation.
This method should be applied to a different physics scene.
Therefore I suggest that when you click you will simulate a few physics steps (the more you will simulate the more accurate indication the player will get),
with every step you store the position of the object and when you are done simulating draw a line between all the points.
In my opinion, it should run quite fast if done correctly.
The code should look something like this:
public PhysicsScene physicsScene;
GameObject actualBall;
GameObject simulatedBall;
OnClick() {
simulatedBall.SetPosition(actualBall.transform.position);
if (!physicsScene.IsValid())
return; // do nothing if the physics Scene is not valid.
for (int i=0; i < 10; i++) {
physicsScene.Simulate(Time.fixedDeltaTime);
// store the position.
myPoints.append(simulatedBall.rb.position);
}
// draw a line from the stored points.
}
In addition there is this video that I hope will help, good luck
https://www.youtube.com/watch?v=GLu1T5Y2SSc
I hope I answered your question and if not tell me :)
Disclaimer : Unfortunately I suck at math so can't provide any code for the calculations.
Now that the legal stuff is out of the way :)
In my opinion you are looking at this all wrong. What you need is to calculate the curve (path of the objects trajectory) and then simply plot the curve in OnDrawGizmos with a line renderer.
You don't need to simulate the behaviour of the object. Not only is this a LOT faster but it's also simpler in terms of TimeScale shenanigans. By changing the TimeScale you are also affecting the TimeScale of your trajectory simulation which will most likely look and feel weird.
By doing a basic trajectory calculation you will not have this issue.
PS: This link might help.

Categories