Moving object with forward is multiplying instead of adding - c#

I need to move an object forward a specific distance from a start point in space. The start point does not have a forward vector, so I need to compute one from the start point to (0,0,0).
I can successfully compute a new start point with the correct forward vector, however when I use it to move an object it multiplies the move distance instead of adding it.
// "start" is actually a raycast hit point, but am using a Vector3 here for simplicity
float moveDistance = 2;
Vector3 start = new Vector3 (0, 0, 3);
Vector3 center = new Vector3 (0, 0, 0);
Vector3 fwd = start - center;
myObject.position = start + fwd * moveDistance;
Debug.Log (fwd); //returns: (0.0, 0.0, 0.3)
Debug.Log (myObject.position); //returns: (0.0, 0.0, 9.0)
The result is myObject.position = 0, 0, 9 // multiplying moveDistance
And the desired result is = 0, 0, 5 // adding moveDistance
The result does have a forward vector, but it's multiplying instead of adding distance. When I do the same calculation for myObject.position using the forward vector of a gameobject, it adds the distance.

Remember that vectors have both direction and magnitude. Multiplying a vector by a scalar will multiply each of its components. If fwd is (0,0,3), multiplying it by 2 will yield (0,0,6). To get a vector in the same direction with moveDistance length, you could normalize it first:
myObject.position = start + fwd.normalized * moveDistance;
(Personally, I would rather normalize the vector before assigning it to fwd, but that's up to you.)
You can always separate the direction and magnitude parts of a vector, which makes it easier to work with them separately:
Vector3 move = <some vector>
Vector3 moveDir = move.normalized; //a normalized vector has length 1
float moveDist = move.magnitude; //move = moveDir * moveDist

Try something like this:
float distanceToMove = 2;
Vector3 start = new Vector3 (0, 0, 3);
Vector3 center = new Vector3 (0, 0, 0);
Vector3 fwd = (center - start).normalized;
myObject.position = start + fwd * distanceToMove;
Debug.Log (myObject.position);

Related

Lerp point between position A,B while controlling the min and max Y-value

I have an object A that I can move freely, and and object B that also moves freely but always along the ground.
I wrote some code to make a point always interpolate at 50% distance between A,B. I modified the code so that if object A goes below listenerMinY on the Y-axis, it will move closer to point A.
This all works great. However one more thing I want to add is that if object A goes above listenerMaxY on the Y-axis, the point cannot go any higher, in other words I want to Clamp the point to a position on the Y-axis. And I do not know how to do that using this Lerp.
As you can see I am currently trying to clamp the Y-position to listenerMaxY which does limit it's movement on the Y-axis, but then it does not interpolate correctly and leaves the "line" between A,B.
if (Physics.Raycast(transform.position, Vector3.down, out hit, Mathf.Infinity, groundLayer))
{
downwardDistance = hit.distance;
}
//listenerMinY = 20, listenerMaxY = 60
float percentageDistance = downwardDistance / listenerMinY;
Vector3 interpolation = Vector3.Lerp(transform.position, centerGround.position, Mathf.Clamp(percentageDistance, 0, .5f));
point.position = new Vector3(interpolation.x, Mathf.Clamp(interpolation.y, 0, listenerMaxY), interpolation.z);
point.rotation = transform.rotation;
Full code (attach to camera or any Object A) for testing:
public class CameraRay : MonoBehaviour
{
[SerializeField] LayerMask groundLayer;
[SerializeField] Transform centerGround;
[SerializeField] Transform listener;
[SerializeField] int listenerMinY;
[SerializeField] int listenerMaxY;
private void Update()
{
RaycastHit hit;
float forwardDistance = 0;
float downwardDistance = 0;
// Control center ground pos
if (Physics.Raycast(transform.position, transform.forward, out hit, Mathf.Infinity, groundLayer))
{
forwardDistance = hit.distance;
Debug.DrawRay(transform.position, transform.forward * forwardDistance, Color.blue);
centerGround.position = hit.point;
}
// Check distance Y from ground, green = <20, red = >listener Y limit, else yellow
if (Physics.Raycast(transform.position, Vector3.down, out hit, Mathf.Infinity, groundLayer))
{
downwardDistance = hit.distance;
if (downwardDistance < listenerMinY)
Debug.DrawRay(transform.position, Vector3.down * downwardDistance, Color.green);
else if(downwardDistance < listenerMaxY)
Debug.DrawRay(transform.position, Vector3.down * downwardDistance, Color.yellow);
else
Debug.DrawRay(transform.position, Vector3.down * downwardDistance, Color.red);
}
float percentageDistance = downwardDistance / listenerMinY;
Vector3 interpolation = Vector3.Lerp(transform.position, centerGround.position, Mathf.Clamp(percentageDistance, 0, .5f));
listener.position = new Vector3(interpolation.x, Mathf.Clamp(interpolation.y, 0, listenerMaxY), interpolation.z);
listener.rotation = transform.rotation;
}
}
So if I understand you correctly you want to stay on the vector A -> B or in your specific case centerGround.position -> transform.position but whatever happens the Y component of he resulting vector should not be bigger than listenerMaxY.
So you can probably calculate backwards what factor you would need to maximum reach that Y level like e.g.
// returns the factor that would be needed to be passed into Lerp in order to result in listenerMaxY
// the result is clamped between 0 and 1
var maxFactor = Mathf.InverseLerp(transform.position.y, centerGround.position.y, listenerMaxY);
// your original factor based on the distance
var percentageDistance = downwardDistance / listenerMinY;
// Now take the minimum of the two factors
var factor = Mathf.Min(percentageDistance, maxFactor);
// and use that as the factor of Lerp
var interpolation = Vector3.Lerp(transform.position, centerGround.position, factor);
// The result is now always a position on the line between
// transform.position and centerGround.position but with y <= listenerMaxY
// (EXCEPT if both positions are already higher than that anyway of course)
listener.position = interpolation;
See Mathf.InverseLerp => basically as the name says this works exactly the other way round: Give it a start and end point and the expected value and it will return the factor that would result in that last value when passed into Lerp

How to use LookRotation to rotate an object based on hand positions?

I'm creating an interaction system for a VR game and I'm struggling with two hand interactions. I'm using the Quaternion.LookRotation() method to generate the rotation of the grabbed object based on the positions and rotations of the hands. The forward part is pretty simple:
Vector3 fwd = (primaryHand.position - secondaryHand.position).normalized;
The "up" part is what I have difficulty with. Initially I tried using the average up direction of both hands:
Vector3 avgUp = Quaternion.Slerp(primaryHand.rotation, secondaryHand.rotation, 0.5f) * Vector3.up;
There is an issue with this approach: the hand's up vector might get aligned with the fwd vector, which causes the object to flip over when it goes over it. Here is a simple illustration of the problem:
The light green arrows represent the up direction of the hands, while the dark green is the calculated direction used as an argument for the LookRotation() method.
The obvious solution seems to be to pick a different fixed vector instead of "up", one which won't be so easily aligned with the fwd vector. In the example it could be a vector aligned with the fingers. But keep in mind that there are no restrictions on initial hand rotation so no matter which vector you choose the hands can always happen to be rotated so that the vectors align. And even if you pick the an optimal vector dynamically (one that is perpendicular to fwd), it's still at best 90 degrees from aligning with fwd.
To solve this I tried restricting the direction to the values which don't cause problems but this caused another issues (I had difficulties with determining which values are ok and which should be discarded). I feel like I'm doing something wrong here, is there any better solution to this problem?
You can calculate the delta rotations of the hands and apply it to the "base up" of the object (the new up if we only take into account the change in position of hands...which will of course be orthogonal to the axis of the object). Then determine the change in angle that results in that up being rotated with each hand. Average those angles out, then apply those angles with the hand-hand axis using Quaternion.AngleAxis to the "base up" from earlier. Then you have your forward and up for Quaternion.LookRotation).
Below is an example of how you can use this, including VR hand noise simulation. To see the test, create a new scene in unity and attach this to the camera and it will build the scene on play start. There is a grip/release gui that will appear in Play view. You can adjust the hand rotation in Scene view
Vector3 leftHandPosCurrent;
Vector3 rightHandPosCurrent;
Vector3 worldAxisPrev;
Quaternion leftHandRotPrev;
Quaternion leftHandRotCurrent;
Quaternion rightHandRotPrev;
Quaternion rightHandRotCurrent;
bool isGripping;
bool firstFrameGripping;
Rigidbody grippedRB;
Transform leftHand;
Transform rightHand;
Quaternion targetRot;
/*
* On subsequent frames of gripping, calculate deltas in positions and
* rotations, average out the hand's effects, then apply them to the gripped
* object
*/
void HandleGrippedRot()
{
Vector3 worldAxisCurrent = rightHandPosCurrent - leftHandPosCurrent;
if (!firstFrameGripping)
{
Vector3 prevUp = targetRot * Vector3.up;
// we haven't moved the transform based on the hands yet, so
// find the new up would be ignoring hand rotations
Vector3 newUp = Quaternion.FromToRotation(worldAxisPrev,
worldAxisCurrent) * prevUp;
float leftHandAngle = GetDegRot(newUp, leftHandRotPrev,
leftHandRotCurrent, worldAxisCurrent);
float rightHandAngle = GetDegRot(newUp, rightHandRotPrev,
rightHandRotCurrent, worldAxisCurrent);
float avgAngle = (leftHandAngle + rightHandAngle) * 0.5f;
newUp = Quaternion.AngleAxis(avgAngle, worldAxisCurrent) * prevUp;
targetRot = Quaternion.LookRotation(worldAxisCurrent,
newUp);
}
else
{
firstFrameGripping = false;
}
leftHandRotPrev = leftHandRotCurrent;
rightHandRotPrev = rightHandRotCurrent;
worldAxisPrev = worldAxisCurrent;
}
/*
* Given the "up" of the object without taking hand rotations into account
* and the axis, determine how a hand's delta rotation affects that up
* around the axis and return the angle of that rotation
*/
float GetDegRot(Vector3 baseUp, Quaternion prevHandRot, Quaternion curHandRot,
Vector3 axis)
{
Vector3 adjUp = (curHandRot * Quaternion.Inverse(prevHandRot)) * baseUp;
adjUp = Vector3.ProjectOnPlane(adjUp, axis);
return Vector3.SignedAngle(baseUp, adjUp, axis);
}
void Update()
{
AddVRNoise(leftHand);
AddVRNoise(rightHand);
leftHandPosCurrent = leftHand.position;
rightHandPosCurrent = rightHand.position;
leftHandRotCurrent = leftHand.rotation;
rightHandRotCurrent = rightHand.rotation;
if (isGripping)
{
HandleGrippedRot();
}
}
void StartGrip()
{
if (isGripping) return;
isGripping = true;
firstFrameGripping = true;
// grippedTransform is set accordingly at some point
}
void EndGrip()
{
if (!isGripping) return;
isGripping = false;
}
/*
* example of using targetRot to move rb
*/
private void FixedUpdate()
{
if (!isGripping) return;
Quaternion delta = targetRot
* Quaternion.Inverse(grippedRB.transform.rotation);
delta.ToAngleAxis(out float angle, out Vector3 axis);
// convert to shortest angle form
if (angle > 180f)
{
axis = -axis; angle = 360f - angle;
}
grippedRB.angularVelocity = angle * 0.25f * axis;
}
/*
* just for testing purposes
*/
void Start()
{
leftHand = CreateHand(true);
leftHand.position = Vector3.left;
rightHand = CreateHand(false);
rightHand.position = Vector3.right;
CreateArrow();
}
/*
* just for testing purposes
*/
void AddVRNoise(Transform hand)
{
Quaternion noise = Random.rotation;
noise.ToAngleAxis(out float angle, out Vector3 axis);
angle = 100f * Time.deltaTime;
noise = Quaternion.AngleAxis(angle, axis);
Quaternion noisyRot = hand.rotation * noise;
hand.rotation = noisyRot;
}
/*
* just for testing purposes
*/
void OnGUI()
{
if (GUI.Button(new Rect(0, 0, 100, 50), "Grip"))
{
StartGrip();
}
if (GUI.Button(new Rect(100, 0, 100, 50), "Release"))
{
EndGrip();
}
}
/*
* just for testing purposes
*/
Transform CreateHand(bool isLeft)
{
string handName = isLeft ? "Left" : "Right";
GameObject hand = new GameObject($"{handName}hand");
GameObject palm = GameObject.CreatePrimitive(PrimitiveType.Cube);
palm.transform.localScale = new Vector3(0.5f, 0.2f, 1f);
palm.transform.SetParent(hand.transform);
GameObject thumb = GameObject.CreatePrimitive(PrimitiveType.Cube);
thumb.transform.localScale = new Vector3(0.2f, 0.1f, 0.1f);
thumb.transform.SetParent(hand.transform);
thumb.transform.localPosition = new Vector3(isLeft ? 0.32f : -0.32f,
0f, -.31f);
return hand.transform;
}
/*
* just for testing purposes
*/
void CreateArrow()
{
GameObject arrow = new GameObject();
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cube);
body.transform.localScale = new Vector3(1f, 1f, 5f);
body.transform.SetParent(arrow.transform);
GameObject head = GameObject.CreatePrimitive(PrimitiveType.Cube);
head.transform.SetParent(arrow.transform);
head.transform.localEulerAngles = Vector3.up * 45f;
head.transform.localPosition = new Vector3(0f, 0f, 2.5f);
grippedRB = arrow.AddComponent<Rigidbody>();
grippedRB.useGravity = false;
arrow.transform.position = 2f * Vector3.up;
}

Issue When Moving Object to Specific Position Unity 3D

I have an object that I want to move slowly & smoothly to a specific position when clicking, so I used this code:
currpos = transform.position;
Vector3 NewPos = new Vector3(- 10, currpos.y, currpos.z + 2);
Rigidbody.MovePosition(Vector3.Lerp(transform.position, NewPos, Time.deltaTime* MoveSpeed)) ;
The problem I have is when the MoveSpeed is low the object move a little bit and didn't reach the specific position, and when increasing the MoveSpeed he reach the specific position but quickly! do you have any suggestion?
public static Vector3 Lerp(Vector3 a, Vector3 b, float t);
Interpolates between the vectors a and b by the interpolant t. The
parameter t is clamped to the range [0, 1]. This is most commonly used
to find a point some fraction of the way along a line between two
endpoints.
As the Doc says, it is some fraction instead of distance. The logic isn't right.
So the code could be revised as follows:
currpos = transform.position;
Vector3 NewPos = new Vector3(- 10, currpos.y, currpos.z + 2);
Vector3 Dir= (NewPos-transform.position).normalized;
Vector3 velocity= Dir*MoveSpeed;
Rigidbody.MovePosition(transform.position+velocity);
If you insist using Lerp:
currpos = transform.position;
Vector3 NewPos = new Vector3(- 10, currpos.y, currpos.z + 2);
float dist= (NewPos-transform.position).magnitude;
if(dist!=0)
{
Rigidbody.MovePosition(Vector3.Lerp(transform.position, NewPos, Time.deltaTime* MoveSpeed)) ;
}
else
{
//do nothing new postion arrived
}
According to Unity3D documentation on Rigidbody.MovePosition you need to enable Rigidbody interpolation for smooth transition between the two positions. This should also be done in the FixedUpdate like this:
void FixedUpdate()
{
rb.MovePosition(transform.position + transform.forward * Time.deltaTime);
}
The Rigidbody.MovePosition takes just one parameter which is the destination Vector3. You should just specify destination point without the Lerping so try this instead:
Rigidbody.MovePosition(NewPos + Time.deltaTime * MoveSpeed);
And do it in the FixedUpdate. Also:
If the rigidbody has isKinematic set false then it works differently. It works like transform.position=newPosition and teleports the object (rather than a smooth transition).
Lerp Function gives a point between 2 Vectors everytime you call it, so just like the turtle problem, it will never reach to destionation. It will get slower and slower each time.
You can eliminate this behaviour with fixing travel speed to a constant but it will not look as good as slowing gradually as approach.
Also you can eliminate this behaviour with stopping the object after it gets too close to object, (that is how i use it usually)
Also note that, if you want to animate someting with code and you want it to be smooth you have to calculate it on "Update" Function.
So the code can be something like
void GoToTarget(){
var currpos = transform.position;
NewPos = new Vector3(- 10, currpos.y, currpos.z + 2);
traveling = true;
}
void Update () {
if (traveling){
rigidbody.MovePosition(Vector3.Lerp(transform.position, NewPos, Time.deltaTime* MoveSpeed));
if((transform.position-NewPos).magnitude < 0.1f){
//Current position is too close to Target, teleport to target and stop
transform.position = NewPos;
traveling = false;
}
}
}
Rather than lerping, why not just move towards it?
currpos = transform.position;
Vector3 NewPos = new Vector3(- 10, currpos.y, currpos.z + 2);
var position = Vector3.MoveTowards(transform.position, NewPos, Time.deltaTime* MoveSpeed);
Rigidbody.MovePosition(position);

OpenGL Unprojecting mouse cursor to find relative position to a certain object?

I have a player object that's moving around the screen. The camera is a fixed camera, looking down on the player (like in Diablo).
Now I want the player object to rotate towards the mouse cursor. The player is not always on the center of the screen (for this case I already have a solution).
In order to do this, I think I need to project the mouse cursor to the same height (y-axis) that my player is on (y-axis is "up" in my game) and then check compare player position with cursor position on the same height in world space.
So far, my unprojecting method looks like this:
private bool Unproject(float winX, float winY, float winZ, out Vector3 position)
{
position = Vector3.Zero;
Matrix4 transformMatrix = Matrix4.Invert(World.CurrentWindow.GetViewMatrix() * World.CurrentWindow.GetProjectionMatrix());
Vector4 inVector = new Vector4(
(winX - World.CurrentWindow.X) / World.CurrentWindow.Width * 2f - 1f,
(winY - World.CurrentWindow.Y) / World.CurrentWindow.Height * 2f - 1f,
2f * winZ - 1f,
1f
);
Matrix4 inMatrix = new Matrix4(inVector.X, 0, 0, 0, inVector.Y, 0, 0, 0, inVector.Z, 0, 0, 0, inVector.W, 0, 0, 0);
Matrix4 resultMtx = transformMatrix * inMatrix;
float[] resultVector = new float[] { resultMtx[0, 0], resultMtx[1, 0], resultMtx[2, 0], resultMtx[3, 0] };
if (resultVector[3] == 0)
{
return false;
}
resultVector[3] = 1f / resultVector[3];
position = new Vector3(resultVector[0] * resultVector[3], resultVector[1] * resultVector[3], resultVector[2] * resultVector[3]);
return true;
}
Now I unproject the mouse cursor once for the near plane (winZ = 0) and the far plane (winZ = 1).
protected Vector3 GetMouseRay(MouseState s)
{
Vector3 mouseposNear = new Vector3();
Vector3 mouseposFar = new Vector3();
bool near = Unproject(s.X, s.Y, 0f, out mouseposNear);
bool far = Unproject(s.X, s.Y, 1f, out mouseposFar);
Vector3 finalRay = mouseposFar - mouseposNear;
return finalRay;
}
My problem is:
How do I know if the values are correct. The values in the "finalRay" Vector are quite small - always. I would have thought that i would get much bigger z-values because my near plane (perspective projection) is 0.5f and my far plane is 1000f.
And how can I find out if the mouse cursor is left/right (-x, +x) or behind/in front of (-z, +z) the player? (I know the player's position)
Where is my error?
And how can I find out if the mouse cursor is left/right (-x, +x) or behind/in front of (-z, +z) the player?
Do it in the opposite direction. Project the postion of the player to the screen. So you can easily compare the position of the player with the mouse position:
// positon of the player in world coordinates
Vector3 p_ws ....;
// positon of the player in view space
Vector4 p_view = World.CurrentWindow.GetViewMatrix() * Vector4(p_ws.X, p_ws.Y, p_ws.Z, 1.0);
// postion of the player in clip space (Homogeneous coordinates)
Vector4 p_clip = World.CurrentWindow.GetProjectionMatrix() * p_view;
// positon of the player in normailzed device coordinates (perspective divide)
Vec3 posNDC = new Vector3(p_clip.X / p_clip.W, p_clip.Y / p_clip.W, p_clip.Z / p_clip.W);
// screen position in pixel
float p_screenX = (posNDC.X * 0.5 + 0.5) * World.CurrentWindow.Width;
float p_screenY = (posNDC.Y * 0.5 + 0.5) * World.CurrentWindow.Height;

Rotating Vector3D

I´m trying to rotate a Vector3D like this:
Vector3D i = new Vector3D(1, 1, 0);
i.Normalize();
Matrix3D m = Matrix3D.Identity;
Quaternion rot = GetShortestRotationBetweenVectors(i, new Vector3D(1, 0, 0));
m.Rotate(rot);
Vector3D j = new Vector3D(0, 1, 0);
Vector3D jRotated = m.Transform(j);
// j should be equal to i
public static Quaternion GetShortestRotationBetweenVectors(Vector3D vector1, Vector3D vector2)
{
vector1.Normalize();
vector2.Normalize();
float angle = (float)Math.Acos(Vector3D.DotProduct(vector1, vector2));
Vector3D axis = Vector3D.CrossProduct(vector2, vector1);
// Check to see if the angle is very small, in which case, the cross product becomes unstable,
// so set the axis to a default. It doesn't matter much what this axis is, as the rotation angle
// will be near zero anyway.
if (angle < 0.001f)
{
axis = new Vector3D(0.0f, 0.0f, 1.0f);
}
if (axis.Length < .001f)
{
return Quaternion.Identity;
}
axis.Normalize();
Quaternion rot = new Quaternion(axis, angle);
return rot;
}
I get the rotation matrix to pass from i = (0.77 ,0.77, 0) to (1,0,0) => 45º. So (0, 1, 0) rotates 45º, and the result should be (0.77, 0.77, 0). But the result is almost the same original vector (0,1,0), so no transform has been done.
How can I rotate vectors? I have a vector (x, y, z), and this vector should rotate to (1, 0, 0). For this operation, suppose we have to rotate 30º. So how do I rotate all the vectors I have this 30º?
At last I found the problem.
float angle = (float)Math.Acos(Vector3D.DotProduct(vector1, vector2));
gives the angle in radians.
Quaternion rot = new Quaternion(axis, angle);
expect the angle in degrees
So the solution is easy:
float angle = (float)(Math.Acos(Vector3D.DotProduct(vector1, vector2)) * (180 / Math.PI));
This means there is a bug in Avateering-XNA (Microsoft Official Software).

Categories