I have an object in my game that has a few meshes and when I try to rotate either of the meshes either way, it only rotates it around world axis, and not its local axis. I have a rotation = Matrix.Identity in a class constructor. Every mesh has this class attached to it. Then this class also contains methods:
...
public Matrix Transform{ get; set; }
public void Rotate(Vector3 newRot)
{
rotation = Matrix.Identity;
rotation *= Matrix.CreateFromAxisAngle(rotation.Up, MathHelper.ToRadians(newRot.X));
rotation *= Matrix.CreateFromAxisAngle(rotation.Right, MathHelper.ToRadians(newRot.Y));
rotation *= Matrix.CreateFromAxisAngle(rotation.Forward, MathHelper.ToRadians(newRot.Z));
CreateMatrix();
}
private void CreateMatrix()
{
Transform = Matrix.CreateScale(scale) * rotation * Matrix.CreateTranslation(Position);
}
...
And now the Draw() method:
foreach (MeshProperties mesh in model.meshes)
{
foreach (BasicEffect effect in mesh.Mesh.Effects)//Where Mesh is a ModelMesh that this class contains information about
{
effect.View = cam.view;
effect.Projection = cam.projection;
effect.World = mesh.Transform;
effect.EnableDefaultLighting();
}
mesh.Mesh.Draw();
}
EDIT:
I am afraid either I screwed somewhere up, or your tehnique does not work, this is what I did. Whenever I move the whole object(Parent), I set its Vector3 Position; to that new value. I also set every MeshProperties Vector3 Position; to that value. And then inside CreateMatrix() of MeshProperties I did like so:
...
Transform = RotationMatrix * Matrix.CreateScale(x, y, z) * RotationMatrix * Matrix.CreateTranslation(Position) * Matrix.CreateTranslation(Parent.Position);
...
Where:
public void Rotate(Vector3 newRot)
{
Rotation = newRot;
RotationMatrix = Matrix.CreateFromAxisAngle(Transform.Up, MathHelper.ToRadians(Rotation.X)) *
Matrix.CreateFromAxisAngle(Transform.Forward, MathHelper.ToRadians(Rotation.Z)) *
Matrix.CreateFromAxisAngle(Transform.Right, MathHelper.ToRadians(Rotation.Y));
}
And Rotation is Vector3.
RotationMatrix and Transform are both set to Matrix.Identity in the constructor.
The problem is if I try to rotate around for example Y axis, he should rotate in a circle while "standing still". But he moves around while rotating.
I'm not entirely certain this is what you want. I'm assuming here you have an object, with some meshes and positions offset from the position and orientation of the main object position and you want to rotate the child object around its local axis relative to the parent.
Matrix.CreateTranslation(-Parent.Position) * //Move mesh back...
Matric.CreateTranslation(-Mesh.PositionOffset) * //...to object space
Matrix.CreateFromAxisAngle(Mesh.LocalAxis, AngleToRotateBy) * //Now rotate around your axis
Matrix.CreateTranslation(Mesh.PositionOffset) * //Move the mesh...
Matrix.CreateTranslation(Parent.Position); //...back to world space
Of course you usually store a transform matrix which transforms a mesh from object space to world space in one step, and you'd also store the inverse. You also store the mesh in object coordinates all the time and only move it into world coordinate for rendering. This would simplify things a little:
Matrix.CreateFromAxisAngle(Mesh.LocalAxis, AngleToRotateBy) * //We're already in object space, so just rotate
ObjectToWorldTransform *
Matrix.CreateTranslation(Parent.Position);
I think you could simply set Mesh.Transform in your example to this and be all set.
I hope this is what you were looking for!
The problem was that, when I was exporting model as .FBX the pivot point wasnt in model centre. Thus making the model move while rotating.
Related
I am making a game where the player-object is running in a circle around origin. I want the player to be able to make the circle bigger or smaller. I have the distance between the new coordinates and origin and I have two old coordinates: current player coordinates and the origin. How do I get new coordinates aligned with the old coordinates and the origin using line lenght?
I am using unity and c#.
Can we see what you already have? This makes the process easier.
Maybe Transform.Rotate Could help you?
You only have to update the position of the player to "make the circle bigger". For example:
public class Example : MonoBehaviour
{
private Vector3 target = new Vector3(5.0f, 0.0f, 0.0f);
void Update()
{
// Spin the object around the world origin around the Yaxis 20 degrees/second.
transform.RotateAround(target, Vector3.up, 30 * Time.deltaTime);
}
Public void IncreaseDistance(float amount){
//you need a vector in de direction you want to move to. You get this by
// calculating the distance vector from the target to this.gameObject. Then normalize it
Vector3 direction = (transform.position - target).normalize; //to - from;
transform.position += direction * amount
}
}
I have a first person rigidbody capsule that rotates so that he will always be upright against the gravity direction. I want to rotate my player to the side so that the player camera will not rotate vertically.
My code is,
void Update() {
FixOrientation();
}
void FixOrientation()
{
if (trans.up != -GetGravityDirection())
{
Quaternion targetRotation = Quaternion.FromToRotation(trans.up, -GetGravityDirection()) * trans.localRotation;
trans.localRotation = Quaternion.RotateTowards(trans.localRotation, targetRotation, 5f);
}
}
The result is,
In the image above, I changed the gravity direction to point to the ceiling.
This code only rotates the player at the global x-axis no matter where he is facing which means when i'm facing global forward or backward, the player will rotate vertically the camera. What I want is for it to rotate on the side(local z axis).
Unity already has a method for exactly that: Transform.Rotate has an overload taking an angle and a rotation axis.
It might look like
// rotation speed in degrees per second
public float RotationSpeed;
void Update()
{
FixOrientation();
}
void FixOrientation()
{
if (transform.up != -GetGravityDirection())
{
// Get the current angle between the up axis and your negative gravity vector
var difference = Vector3.Angle(transform.up, -GetGravityDirection());
// This simply assures you don't overshoot and rotate more than required
// to avoid a back-forward loop
// also use Time.deltaTime for a frame-independent rotation speed
var maxStep = Mathf.Min(difference, RotationSpeed * Time.deltaTime);
// you only want ot rotate around local Z axis
// Space.Self makes sure you use the local axis
transform.Rotate(0, 0, maxStep, Space.Self);
}
}
A Sidenote:
Just in general be careful with the direct comparison of two Vectors
trans.up != -GetGravityDirection()
uses an approximation of 0.00001. In your case that should be fine anyway but for comparing you should rather use
Vector3.Angle(vector1, vector2) > threshold
to define a wider or stronger threshold
I am currently implementing a radio in Unity for Oculus. Player can twist the knob to change volume. However, either eulerangle or rotation cannot return a unique value while turning in 360 degrees.
And below code has the same problem with eulerangle.
void Start ()
{
oldRight = Vector3.right;
angle = 0.0f;
}
void Update ()
{
Vector3 right = transform.right;
float tempAngle = Vector3.SignedAngle(oldRight,right,transform.forward);
if (right.y < oldRight.y)
{
angle += tempAngle;
}
else
{
angle -= tempAngle;
}
oldRight = right;
}
transform.rotation
If you want XYZ angles (and not a quaternion),
transform.rotation.eulerAngles
First of all, we don't know what's the relation between the radio game object and the knob game object, and that matters.
I'll make an example in order to show how you can get the rotation of the knob regardless of that relation.
In the image below, the radio is the cube, with its transform reset all to 0.
This is the cylinder which acts as the knob:
The important part here is that I rotated and placed the cylinder such that the X axis of the transform is parallel to the X axis of the cube.
And here is the script that rotates the cylinder and reports the angle of such rotation:
using UnityEngine;
public class RotateKnob : MonoBehaviour {
public float m_Speed;
void Update() {
if (Input.GetKey(KeyCode.RightArrow)) {
transform.Rotate(Vector3.up, Time.deltaTime * m_Speed, Space.Self);
}
if (Input.GetKey(KeyCode.LeftArrow)) {
transform.Rotate(Vector3.down, Time.deltaTime * m_Speed, Space.Self);
}
var angle = Vector3.SignedAngle(transform.right, transform.parent.right, transform.up);
Debug.Log(angle);
}
}
Notice how the rotation is done relative to the Y axis and Space.Self.
The angle is taken relative to the Y axis of the cylinder (which is the axis around which we rotate the cylinder), thus transform.up as the SignedAngle third parameter, and ofc we measure the angle between the transform.right and the transform.parent.right (the transform.right of the cube that childs the cylinders).
With this code you don't have to worry about the transform of the cube in the world, the result will always be relative to the cube when rotating the cylinder.
Please do remember that the angle will be reported in the range [-180,180] and not [0,360].
How to make a plane to "look" at the camera by rotating on only on one axis?
For example, I have a plane with a texture of smoke coming from the pipe. If I walk around the pipe the plane should always be facing the camera, rotating along the y axis. But the direction of the smoke should not change, therefore, along with the x and z axes, the plane should not rotate.
Here is a code example which helps to rotate the plane on all axes:
void Update()
{
transform.LookAt(Camera.main.transform.position, -Vector3.up);
}
How to make it rotate only on one axis?
One approach to this is to store the object's original rotation in a Vector3 using transform.eulerAngles. You can then create another Vector3 to store the object's rotation after the LookAt function has completed. You can then set the object's rotation to a new Vector3 using only the y value from the second variable and using the original x and y values. It would look something like this:
void Update()
{
Vector3 originalRotation = transform.eulerAngles;
transform.LookAt(Camera.main.transform.position, -Vector3.up);
Vector3 newRotation = transform.eulerAngles;
transform.eulerAngles = new Vector3(originalRotation.x, newRotation.y, originalRotation.z);
}
I want to restrict player movement in the sphere, the schematic diagram show as below. If player movement is out of range, then restrict player to sphere max radius range.
How can I write C# code to implement it, like this?
These are my current steps:
Create 3D sphere
Create C# code append to sphere object
My code so far:
public Transform player;
void update(){
Vector3 pos = player.position;
}
I don't know how you calculate your player`s position but before assigning the new position to the player you should check and see if the move is eligible by
checking the new position distance form the center of the sphere
//so calculate your player`s position
//before moving it then assign it to a variable named NewPosition
//then we check and see if we can make this move then we make it
//this way you don't have to make your player suddenly stop or move it
//back to the bounds manually
if( Vector3.Distance(sphereGameObject.transform.position, NewPosition)< radius)
{
//player is in bounds and clear to move
SetThePlayerNewPosition();
}
What #Milad suggested is right but also include the fact you won't be able to "slide" on the sphere border if your movement vector even slightly goes outside the sphere :
(sorry for the crappy graphic skills...)
What you can do if you want to be able to "slide" on the sphere interior surface is get the angle formed between the player position and the X vector and then apply this angle with the :
public Transform player;
public float sphereRadius;
void LateUpdate()
{
Vector3 pos = player.position;
float angle = Mathf.Atan2(pos.y, pos.x);
float distance = Mathf.Clamp(pos.magnitude, 0.0f, sphereRadius);
pos.x = Mathf.Cos(angle) * distance;
pos.y = Mathf.Sin(angle) * distance;
player.position = pos;
}
Just make sure using this won't counter effect your player movement script (that's why I put it in LateUpdate() in my example).