So, here is what i have:
SteamVR's hand gameobject
3D sphere.
what i want:
The sphere to move to same direction/position as the hand does, but it to move further with a multiplier. E.g. i move the VR controller and the hand moves 1 unit. I want that the sphere moves to the same direction in a same amount of time but e.g. 2 units. how do i do this?
i tried simple
sphere.transform.position = ControllerToFollow.position +2f;
but then the sphere is always offset.
position is a Vector3, which is essentially 3 floats - you can't plus a Vector3 with a float unless you overload the + operator. Otherwise what you can do is the following:
Vector3 followPos = new Vector3(ControllerToFollow.position.x + 2f,
ControllerToFollow.position.y + 2f,
ControllerToFollow.position.z + 2f);
sphere.transform.position = followPos;
If you only want it to follow on one axis, then you can do the following:
Vector3 followPos = new Vector3(ControllerToFollow.position.x + 2f, // Follow on x Axis
ControllerToFollow.position.y, // Y axis is the same
ControllerToFollow.position.z); // X Axis is the same
sphere.transform.position = followPos;
Edit: I think I understand your problem better now. Here's a better version.
if (Vector3.Distance(sphere.transform.position, ControllerToFollow.position) >= 2f)
{
// Code that makes the sphere follow the controlling
}
Just track the movement Delta of the hand and multiply it by a certain multiplier.
At the beginning of the manipulation store
private Vector3 lastControllerPosition;
...
lastControllerPosition = ControllerToFollow.position;
then in every frame compare
var delta = ControllerToFollow.position - lastHandPosition;
// Don't forget to update lastControllerPosition for the next frame
lastControllerPosition = ControllerToFollow.position;
Now in delta you have a movement of the controller since the last frame. So you can assign it to the sphere with a multiplier using Transform.Translate
sphere.transform.Translate(delta * multiplier, Space.World);
or simply using
sphere.transform.position += delta * multiplier;
Related
I would like to rotate an object to match its ground. So i cast 3 Rays (at the corners) to calculate the normal of the plane below.
Now i need to rotate the object accordingly but keep the y rotation (so in which direction it "faces") so just setting transform.up = normal does not work.
I thought i could just use the dot product between the transform directions to rotate it (so xRotation = Vector3.Dot(normal, transform.forward) for x and zRotation = Vector3.Dot(normal, transform.right) for z) this should be the angles between the normal vector and the right/forward vector. But as the result my object just faces the sky that way to the idea is completely wrong.
Do you know how i should proceed ?
Here is the solution to your problem. Although there are different methods for doing this, I personally find it best to use the Cross axis. In fact, you need Vector3.Cross instead of Vector3.Dot. This code works in such a way that by multiplying the transform.right of the player on the Ground normal vector, Since this axis calculates the perpendicular direction, you can expect it to give a forward corresponding to the ground.
public LayerMask groundLayer; // The Layer Ground
public Vector3 offset = Vector3.up;
private void Update()
{
if (Physics.Raycast(transform.position, Vector3.down, out var groundHit, 2f, groundLayer.value))
{
var cross = Vector3.Cross(transform.right, groundHit.normal);
var _lookRotation = Quaternion.LookRotation(cross, groundHit.normal);
transform.position = groundHit.point + offset; // the offset is OPTIONAL, you can adjust it manuel or remove
transform.rotation = _lookRotation;
}
}
Result:
You can see the result below. Consider that you can delete the offset code and make it compatible with the ground with mechanisms such as CharacterController or Rigidbody.
I am new in Unity3D scripting especially when it comes to specifics of Transform, Quaternions, Vector3.
Down there is a script. When I roll the cube it flips in the right direction. However when cubes' local axis doesn't match the world axis the cube's endRotation is performed on the wrong axis.
Can anybody help me to resolve this issue : "endRotation" would flip the cube on right axis regardless of the relation between LOCAL axis <=> WorldAxis.
I was trying to resolve it for a week. Of cause no success.
Further down there code and video.
if (Input.GetAxis("Horizontal") > buttonDown)
{
StartCoroutine(FlipTheCube(Vector3.right));
return
}...
.
.
.
public IEnumerator FlipTheCube(Vector3 direction)
{
startFliping = false;
float rollStartTime = 0;
float rollAngle = 90;
float halfWidth = transform.localScale.z / 2;
Vector3 pointAround = transform.position + (Vector3.down * halfWidth) + (direction * halfWidth);
Vector3 rollAxis = Vector3.Cross(Vector3.up, direction);
Quaternion rotation = transform.rotation;
Quaternion **endRotation** = rotation * Quaternion.Euler(rollAxis * rollAngle);
Vector3 endPlacement = transform.position + direction;
float oldAngle = 0;
while (rollStartTime < rollDurtnTime)
{
yield return new WaitForEndOfFrame();
rollStartTime += Time.deltaTime;
float newAngle = (rollStartTime / rollDurtnTime) * rollAngle;
float rotateThrough = newAngle - oldAngle;
oldAngle = newAngle;
transform.RotateAround(pointAround, rollAxis, rotateThrough);
}
transform.position = endPlacement;
transform.rotation = **endRotation**;
startFliping = true;
}
Here is a link to the youtube video
First part of video If cubes local axis is matching world axis the "endRotation" is flipping correctly Second part of video as soon cubes local axis is not matching world axis "endRotation" goes wrong.
Your problems lie within these lines:
Vector3 rollAxis = Vector3.Cross(Vector3.up, direction);
Quaternion rotation = transform.rotation;
Quaternion endRotation = rotation * Quaternion.Euler(rollAxis * rollAngle);
// ...
transform.RotateAround(pointAround, rollAxis, rotateThrough);
First problem is Euler vs angle/axis usage.
If you've only been rotating against Vector3.right, Vector3.forward, and their negatives you might not have even noticed this yet.
In the first part, you use rollAxis as an Euler angle representation, and in the second part as an axis.
The problem is that for a rotation, its axis is usually not the same as a Euler representation of that rotation! For example, the rotation made by rotating around (0.7, 0.0, 0.7) by 90° is completely different from an Euler rotation of (63°, 0°, 63°)!
Instead, just use rollAxis consistently as an angle/axis representation. You can get the quaternion form using Quaternion.AngleAxis.
Second problem is global vs local rotation.
This is a problem whose effects you're definitely already noticing.
In the first part, you apply rollAxis as a local rotation. This is because rollAxis is the second term of the * operator.
In the second part, RotateAround rotates around a global axis defined by rollAxis.
Keep it global or local for both. Global is simpler, and it appears that's what you're trying to do (judging from transform.position + direction), so you should have rotation as the second term of the * operator.
Altogether, the problems can be fixed by just changing the Quaternion endRotation = rotation * Quaternion.Euler(rollAxis * rollAngle); line:
Vector3 rollAxis = Vector3.Cross(Vector3.up, direction);
Quaternion rotation = transform.rotation;
Quaternion endRotation = Quaternion.AngleAxis(rollAngle, rollAxis) * rotation;
// ...
transform.RotateAround(pointAround, rollAxis, rotateThrough);
See here for more information about the quaternion * operator.
I suggest you using DoTween asset (https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676):
DORotate(Vector3 to, float duration);
Using tweeners will save you a lot of time writing your own animations.
I'm developing a simulation where player should be able to move around inside a 2D circle (referred to as sphere in my code). The players movement must be relative to the center of the circle.
My first step was to make sure the player always faces the center. I got the working fine. However when I tried to do the relative movement it doesn't give my quite the result I'm looking for.
When I move the player close to the center of circle and move sideways (which is relative to the player's facing vector), the player spins around the center but then slowly starts spiraling outwards. The outwards spiral is much more prominent near the center and takes about 8 orbits to reach the inner edge of the circle. Instead the player should be spinning around the center at a constant distance from the center. Why does the player spiral outwards?
Here is the code I use:
// center of the sphere
Vector3 center = sphereComponent.transform.position - player.transform.position;
// always rotate towards the center so that transform.up is
float angle = Vector3.Angle(center, Vector3.up);
float sign = (center.x < rigidbody.transform.position.x) ? 1.0f : -1.0f;
rigidbody.MoveRotation(angle * sign);
// use the input vector to calculate a vector relative to the objects right and up vectors
Vector2 relativeInputVector =
(rigidbody.transform.right * player.InputVector.x) +
(rigidbody.transform.up * player.InputVector.y);
// below is same as doing: rigidbody += relativeInputVector.normalized * 20 * Time.deltaTime;
rigidbody.MovePosition(rigidbody.position + (relativeInputVector.normalized * 20 * Time.deltaTime));
So I've tried a few things already:
I thought it was maybe a rounding issue. So I rounded the relativeInputVector's X and Y to the 2nd decimal place. Didn't help.
I normalized the relativeInputVector vector. Didn't seem to do much...
I also thought maybe I should move and then rotate instead of rotate then move. Didn't work.
Now I'm thinking the issue is somewhere in the math (probably where I define relativeInputVector) but I can't find simular use cases regarding this so that I can compare and troubleshoot.
(this is a rather saturated topic when it comes to the keywords I'm search with)
Your intuition would make sense if you were moving to the side then adjusting the direction of your forward vector simultaneously and continuously, but it's being done alternating and discretely.
Consider what happens if Time.deltaTime was absolutely enormous for one frame. You would sidestep a huge amount, maybe even going off the screen in one direction, and then you would adjust your angle to face the center of the circle. That's an exaggerated example but its exactly what's happenening on a small scale.
Here's a diagram showing why your code spirals out:
The way you're doing it, The angle between the circle's radius to the player's position at the beginning of the frame (A in the diagram) and the direction the rigidbody moves (1->2 in the diagram) is a right angle. At position 1, the radius A might be the correct distance, but the hypotenuse of a right triangle is always longer than each leg, so the new radius at position 2 (B) must be larger, and likewise, C must be larger than B.
The result of that is a spiral motion as you continue to accumulate length to your radius by switching from legs to hypotenuses of these right triangles.
Basically, in order for your code to work, you would need to be making infinitely small triangles--Time.deltaTime would need to be infinitely small--as a right triangle with one infinitely small leg is just a line, its other leg and its hypotenuse are the same length.
Of course if Time.deltaTime were infinitely small, the player would never move. ;) So, a different approach is needed:
Instead, we can calculate the player's angular velocity and then move the player according to that.
So, dirst determine the player's new distance from the center first, then how many degrees the player would travel around the circle at that radius:
Vector3 sphereCenterPoint = sphereComponent.transform.position
Vector3 playerToCenter = sphereCenterPoint - player.transform.position;
float playerVerticalSpeed = 20f * player.InputVector.normalized.y;
newVerticalPosition = rigidbody.position + playerToCenter.normalized
* playerVerticalSpeed * Time.deltaTime;
playerToCenter = sphereComponent.transform.position - newVerticalPosition;
float circumferenceOfPlayerPath = 2f * playerToCenter.magnitude * Mathf.PI;
float playerHorizontalSpeed = 20f * player.InputVector.normalized.x;
float degreesTraveled = ( playerHorizontalSpeed * Time.deltaTime / circumferenceOfPlayerPath ) * 360f;
Then, rotate the player's new vertical position around the center point and set the player's rotation and position accordingly. You can use Quaternion.LookRotation to determine the rotation needed to make the rigidbody point forward/up in desired directions:
// rotates newVerticalPosition around sphereCenterPoint by degreesTraveled around z axis
Vector3 newPosition = Quaternion.Euler(0f,0f, degreesTraveled)
* (newVerticalPosition - sphereCenterPoint ) + sphereCenterPoint;
rigidbody.MovePosition(newPosition);
rigidbody.MoveRotation(
Quaternion.LookRotation(Vector3.forward, sphereCenterPoint - newPosition));
To remove a few calculations, you can include the part where you divide by 2 pi and multiply by 360f into the 20f factor:
Vector3 sphereCenterPoint = sphereComponent.transform.position
Vector3 playerToCenter = sphereCenterPoint - player.transform.position;
float playerVerticalSpeed = 20f * player.InputVector.normalized.y;
newVerticalPosition = rigidbody.position + playerToCenter.normalized
* playerVerticalSpeed * Time.deltaTime;
playerToCenter = sphereComponent.transform.position - newVerticalPosition;
float playerHorizontalSpeed = 1146f * player.InputVector.normalized.x;
float degreesTraveled = playerHorizontalSpeed * Time.deltaTime / playerToCenter.magnitude;
// rotates newVerticalPosition around sphereCenterPoint by degreesTraveled around z axis
Vector3 newPosition = Quaternion.Euler(0f,0f, degreesTraveled)
* (newVerticalPosition - sphereCenterPoint ) + sphereCenterPoint;
rigidbody.MovePosition(newPosition);
rigidbody.MoveRotation(
Quaternion.LookRotation(Vector3.forward, sphereCenterPoint - newPosition));
I have a player position, a pointer indicating the players view direction, a distance and a horizontal and vertical angle. I want to calculate a target position:
that is distance away from the players position
that, from the players view direction, is horizontal angle to
the right and vertical angle up
It's about positioning a Hololens-Application UI in a sphere around the player. The UI should i.e. be 40 degrees to the leftand 20 degrees up from the players view direction.
Edit: Added image to clarify. Given is the Player Pos (pX|pY|pZ), the radius (= length of the black bold line) and both angles in degree.
I'm looking for how to calculate the UI Center position (x?|y?|z?).
You can use Quaternion.Euler to create a rotation based on angles in world space and then get the desired result by multiplying it with a known position.
So by using your example you could find the position like this:
float radius, x_rot, y_rot;
Vector3 forwardDirection, playerPos;
Vector3 forwardPosition = playerPos + (forwardDirection * radius);
Vector3 targetPosition = Quaternion.Euler(x_rot, y_rot, 0) * forwardPosition;
Try check out the docs on Quaternion and Quaternion.AngleAxis for more handy rotation stuff.
Answer by a mathematician:
To calculate the spherical position with the given information (distance between objects, x angle, y angle) you use trigonometry:
float x = distance * Mathf.Cos(yAngle) * Mathf.Sin(xAngle);
float z = distance * Mathf.Cos(yAngle) * Mathf.Cos(xAngle);
float y = distance * Mathf.Sin(yAngle);
ui.transform.position = player.transform.position + new Vector3(x,y,z);
// Set UI in front of player with the same orientation as the player
ui.transform.position = player.transform.position + player.transform.forward * desiredDistance;
ui.transform.rotation = player.transform.rotation;
// turn it to the left on the players up vector around the the player
ui.transform.RotateAround(player.transform.position, player.transform.up, -40);
// Turn it up on the UI's right vector around the player
ui.transform.RotateAround(player.transform.position, ui.transform.right, 20);
assuming you also want the UI to face the player, otherwise you have to set another rotation after this.
No need to calculate it yourself, the Unity API already does it for you (
see Rotate around)
If i am understanding you correctly you want to create a UI that hovers above a point. I recently did a similar thing in my game. and this is how i did it.
if (Input.GetMouseButtonDown(0)) // use the ray cast to get a vector3 of the location your ui
// you could also do this manualy of have the computer do it the main thing is to
// get the location in the world where you want your ui to be and the
// WorldTOScreenPoint() will do the rest
{
RaycastHit hit;
Vector3 pos;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
pos = hit.point;
pos.y += yOffset; // use the offset if you want to have it hover above the point
ui.transform.position = cam.WorldToScreenPoint(pos); // use your main cammera here
// then either make your ui vissible or instanciati it here and make sure if you instanciate it
// that you make it a child of your cnavas
}
}
I hope this solves you problem. If i am not understanding what you are trying to do let me know and i will try to help.
Note: if you want to make the ui look farther away when you move away from the point scale the ui down as you move farther away, and scale it up when you get closer.
The diagram in the question is somewhat confusing:
The axes are in the orientation of a right-handed coordinate system, but Unity uses a left-handed coordinate system.
In terms of Euler angles, the part of the image labeled "x Angle" is actually the Y angle (rotation around Y axis), and the part of the image labeled "y Angle" is actually the X angle (around X axis).
The two angles listed use a different sign convention. The Y angle (labeled "x Angle") is following the right-hand rule, while the other angle is not.
Jonas Zimmer has a great answer that follows the conventions in the image, but I'll try to do something a bit less confusing and follows more standard math conventions.
Here is some code for Unity written in C#, in YX rotation order, treating zero angle as forward (+Z), and follows Unity's conventions of a left-handed, Y-is-up, Z-is-forward coordinate system. Increasing Y angle rotates to the right, and increasing X angle rotates down.
public static Vector3 Vector3FromAngleYX(float y, float x)
{
float cosx = Mathf.Cos(x);
return new Vector3(cosx * Mathf.Sin(y), -Mathf.Sin(x), cosx * Mathf.Cos(y));
}
Also, I found this question looking to implement a Godot version, so here is a version for Godot Engine written in GDScript, in YX rotation order, treating zero angle as forward (-Z), and follows Godot's conventions of a right-handed, Y-is-up, Z-is-back coordinate system. Increasing Y angle rotates to the left, and increasing X angle rotates up.
func vector3_from_angle_yx(y, x):
var neg_cosx = -cos(x)
return Vector3(neg_cosx * sin(y), sin(x), neg_cosx * cos(y))
I'm looking to place an object at a specific position relative to another :
This new object has to be placed in the pink zone, and I only know the minimum and max distance of placement, an angle relative to my first object forward direction (maxAngle in degrees), and the position of this first object.
I already know how to check if an object is placed in the pink zone, but not set its position in this zone. So I took the code to check an object in the cone, but I can't get how to transform it to set the position in the cone.
float distance = Random.Range(minDistance, maxDistance);
float angle = maxAngle *= Mathf.Deg2Rad;
float coneRadius = distance * Mathf.Tan(angle);
Vector3 vect = firstObject.transform.position - targetObject.transform.position;
targetObject.transform.position = new Vector3(angle, 0, firstObject.transform.position.z + distance);
If you can give me clues, it'll be very cool.
The trick is to move the local position and then straighten...
This is indeed a basic technique in Unity or any transform-based scene engine.
Create the new object, "newb".
(1) Position the object exactly at the "+" in your image.
(2) Choose your angle
angle = Random.Range(-maxAngle, maxAngle);
(3) Twist newb by that much:
newb.transform.eulerAngles = new Vector3( 0f, 0f, angle);
(4) Choose your distance:
distance = Random.Range(minDistance,maxDistance);
(5) Then offset the LOCAL position of newb by that much:
newb.transform.Translate(0f, 0f, distance, Space.Self);
And then the trick:
Note that "newb" will be "twisted", so make it sit straight:
newb.transform.eulerAngles = Vector3.zero;