I'm making a simple solar system simulation using OpenTK for OpenGL and monodevelop for a C# ide. It's been going well, and I had planets orbiting the sun, lit properly. The problem started when I wanted to tilt the axis of planets; wherever I put the new rotation in my method it doesn't visibly rotate. Once it made the lit faces of my spheres rotate around it somehow. If I put the rotation before or after the rotation that makes the planet rotate around its axis, the top of the planet gets lit strangely, as if the normals are broken.
public void Display (float time)
{
GL.Rotate (axialTilt, tiltAxis); // This rotation causes the issues, no matter
// where I put it
GL.PushMatrix (); // This push/pop was added later, but it
// doesn't help
rotationalPosition += rotationSpeed * time;
GL.Rotate (rotationalPosition, Vector3.UnitZ);
planet.Draw ();
GL.PopMatrix ();
if (ring != null) {
ring.Draw ();
}
if (moons != null) {
foreach (Orbit o in moons) {
o.Draw (time);
}
}
}
This is called by:
public void Draw (float time)
{
GL.PushMatrix ();
GL.Rotate (angle, orientation); // This will allow an angled axis for
// moons and other planetary orbits.
orbitPosition += orbitSpeed * time;
GL.Rotate (orbitPosition, Vector3.UnitZ);
GL.Translate (orbitDistance, 0, 0);
GL.Rotate (-orbitPosition, Vector3.UnitZ); // Rotate backward, so rotating around
// the orbit axis doesn't rotate the
// planet.
orbitingBody.Display (time);
GL.PopMatrix ();
}
It's therefore rather strange. Nothing here should be causing issues with normals, and the tilt seems to be in the right place.
It looks like you are tilting the planet around the sun first, then rotating it. This is making the light come from a different direction than you expect.
I'm not sure what planet.Draw() does, but this is what your code should be doing conceptually.
Start with an identity matrix
(if you don't start with an identity matrix, you are rotating around the center of the scene, instead of the planet's axis)
Rotate the planet around its tilt axis.
Add the planets current position relative to the sun to the transformation so that the sun is now at the origin of the scene. (GL.translate)
Rotate the planet around the sun.
Related
I've spent my whole day trying to figure out a problem I'm having with my school project, so I'm off to my last resort: stackoverflow!
My problem:
I'm trying to rotate a character relative to a Camera you can rotate around the character.
input: xbox controller
relevant information:
The camera rotates horizontally around the character, using the right joystick
The character movement happens with the left joystick
The character already moves relative to the Camera, that's working as expected. I'm trying to rotate the character, which happens in a seperate method.
When the left joystick is pulled downwards, the character should always be facing (and moving towards) the camera.
When the left joystick is pulled upwards, the character should always be facing the opposite of (and moving away from) the camera.
I'm leaving a lot of code out, just to keep it readable for you guys. If you need something, just ask and I'll provide.
What I have so far: https://imgur.com/TERUXV6
Why it's wrong: The character rotation is perfect. However, I'm cheating here. The camera rotates according to the world coordinates. As soon as I rotate the camera, this is obvious.
The following script is attached to the Character GameObject.
public class CharacterBehaviour : MonoBehaviour
{
public GameObject HumanoidModel;
[SerializeField]private Transform _mainCameraTransform;
private void Update()
{
ApplyMovement();
RotateCharacter();
}
private void ApplyMovement()
{
//get input movement vector
Vector3 inputMovement = new Vector3(_inputMoveCharacterXAxis, 0, _inputMoveCharacterZAxis);
//make sure camera forward is player movement forward
Vector3 mainCameraForwardXz = Vector3.Scale(_mainCameraTransform.forward, new Vector3(1, 0, 1)); //multiplied by (1, 0, 1) to remove Y component
Vector3 mainCameraRightXz = Vector3.Scale(_mainCameraTransform.right, new Vector3(1, 0, 1)); //multiplied by (1, 0, 1) to remove Y component
Vector3 movementInCameraForwardDirection = mainCameraForwardXz * inputMovement.z;
Vector3 movementInCameraRightDirection = mainCameraRightXz * inputMovement.x;
Vector3 movementForward = movementInCameraForwardDirection + movementInCameraRightDirection;
_velocity = movementForward * MaximumSpeed;
}
private void RotateCharacter()
{
Vector3 inputDirection = new Vector3(_inputMoveCharacterXAxis, 0, _inputMoveCharacterZAxis);
HumanoidModel.transform.LookAt(HumanoidModel.transform.position +
HumanoidModel.transform.forward + inputDirection);
}
The following script is attached to the Main Camera GameObject
public class CameraBehaviour : MonoBehaviour
{
[SerializeField] private Transform _characterTransform;
[SerializeField] private Transform _mainCameraTransform;
private void Update ()
{
RotateCamera();
}
// Rotate camera horizontally
private void RotateCamera()
{
_mainCameraTransform.RotateAround(_characterTransform.position, Vector3.up, _inputRotateCameraHorizontal);
}
}
The source of the problem is in the RotateCharacter() function. I know I need to get some calculations in there to make the character rotation relative to the camera rotation, I just can't figure out what that calculation is, and why.
Thanks in advance!
Thrindil
so heres what you need...
camDefault, a Vector3 for the cameras initial position behind the char.
camCur, a Vector3 for the cameras current position(to track where it is in orbit around the character)
you need to set camDefault in Awake() to the its current position at that time, IE camDefault = cam.transform.position);
then in a fixed update,
camCur= cam.transform.position;
then,
if(Input//your horizontal axis here//==0){
if(camCur!=camDefault){
//translate camera to cam default
cam.tranform.translate(camDefault);
cam.lookat(player.transform.forward);
}
}
keep in mind that some of this is pseudocode, just a general direction. but the unity methods are there. if properly implemented this script will allow you to rotate around your char with right stick, than the camera will slide back behind you when you let go.
I believe, for your sanity, it would be easier to make the camera a direct child of ybot, so it rotates with it and you dont need to do it maually the camera will always stay behind the player. but thats just my opinion. as it sits now, the camera, and the model are children of player, if you want the camera to turn with the player, just make it a child of that model.
in this case, you could store the initial rotation, and current rotation as above, and us your stick to look left and right and then snap back to forward when you let the stick go.
I'm working on a prototype of a tower defense game and I've encountered a problem with the rotation of a turret. I made it so that every turret must have a rotator part which rotates horizontally and holds the main turret body with the cannon which rotates vertically. I came up with a simple script to this but it only seems to work for the rotator and not for the cannon, at least not the way it should.
Here is the code from the script:
void Update () {
if (target != null) {
Vector3 tempRotatorRotation = rotator.transform.localEulerAngles;
rotator.transform.LookAt (target.transform);
rotator.transform.localEulerAngles = new Vector3 (tempRotatorRotation.x, rotator.transform.localEulerAngles.y, tempRotatorRotation.z);
Vector3 tempCannonRotation = cannon.transform.localEulerAngles;
cannon.transform.LookAt (target.transform);
cannon.transform.localEulerAngles = new Vector3 (cannon.transform.localEulerAngles.x, tempCannonRotation.y, tempCannonRotation.z);
}
}
And here is an image of how this turns out. The rotator is rotated perfectly, but as you can see the cannon is looking down for some reason.
(Blue is the pedestal which doesn't move. Green is rotator. Red is turret body. Light blue is cannon)
The origin of the cannon 3D model is set almost at the start of it.
Here is the screenshot of the canon selected showing it's axis and transform data
forward to unity is the blue line, which in your diagram is facing up. try this
crate empty, attach to turret so it rotates, make sure blue line(z axis) is facing your forward direction, you can do this manualy by rotating. then place your barrel as a child of that object, and point that object at target.
ive had to do this several times with blender models, since blender uses the z axis as its vertical axis not its depth axis like unity.
-turret_test
-turret_test_pedestal
-turret_test_rotater
-turret_test_turret
-AIM(new empty, orient the proper direction then add child)
-turret_test_cannon
Your cannon's barrel points in its forward direction, so all you need to use is cannon.transform.LookAt (target.transform, cannon.transform.up);
void Update () {
if (target != null) {
/* rotator code here */
// Remember which way the top of the cannon faces.
Vector3 cannonUpDirection = cannon.transform.up;
// point the cannon directly at the target
cannon.transform.LookAt (target.transform, cannonUpDirection);
}
}
If the rotator isn't pointing at/above/below the target, then you have to figure out how much to rotate the canon upwards/downwards from the horizontal, then point it in the same direction as the rotator and then do that:
void Update () {
if (target != null) {
/* rotator code here */
// Remember which way the top of the cannon faces.
Vector3 cannonUpDirection = cannon.transform.up;
// point the cannon directly at the target
cannon.transform.LookAt (target.transform, cannonUpDirection);
// Find global direction for looking straight ahead above/below the target
Vector3 sameYDirection = Vector3.Scale(
cannon.transform.forward,
new Vector3(1f,0f,1f)
);
// Convert that to local
Vector3 sameYDirectionLocal = cannon.transform
.InverseTransformDirection(sameYDirection);
// get rotation for moving from looking ahead to looking direct
Quaternion lookTowards = Quaternion.FromToRotation(
sameYDirectionLocal,
Vector3.forward
);
// lookTowards is a locally-vertical rotation from horizontal to the target,
// given some top side of the cannon.
// Clamp lookTowards here if you have a max rotation speed.
// Face cannon in same direction of rotator;
cannon.transform.rotation = Quaternion.LookRotation(
rotator.forward, cannonUpDirection);
// Use lookTowards to look down from that position.
cannon.transform.rotation *= lookTowards;
}
}
Turns out Unity is broken and when I re-imported the model all the axis have changed and instead of the canon turning in the X axis up and down now it was in the Y axis while everything else rotates horizontally in Y.
Also in the calculation I had to add + 90 to both equations. This will make no sense now since it shows both are changing in Y axis but one is rotating horizontally and the other vertically.
if (target != null) {
Vector3 tempRotatorRotation = rotator.transform.localEulerAngles;
rotator.transform.LookAt (target.transform);
rotator.transform.localEulerAngles = new Vector3 (tempRotatorRotation.x, rotator.transform.localEulerAngles.y + 90, tempRotatorRotation.z);
Vector3 tempCanonRotation = canon.transform.localEulerAngles;
canon.transform.LookAt (target.transform);
canon.transform.localEulerAngles = new Vector3 (tempCanonRotation.x, canon.transform.localEulerAngles.y + 90, tempCanonRotation.z);
}
I'm having some problems with this so i thought i'd ask here.
I wrote a code that instantiates a health bar (a simple slider) and an enemy. It then sets that enemy (who just spawned) as the target of that health bar and the healthbar updates its position to always stay above the enemys head.
This is the script for the health bar
(canvasRect is the rect transform of the canvas, myRect is the health bars rect transform):
private void Update() {
if(target != null) {
Vector2 targetPosition = Camera.main.WorldToViewportPoint(target.position);
Vector2 screenPosition = new Vector2(((targetPosition.x * canvasRect.sizeDelta.x) - (canvasRect.sizeDelta.x * 0.5f)),
((targetPosition.y * canvasRect.sizeDelta.y) - (canvasRect.sizeDelta.y * 0.5f)));
screenPosition += new Vector2(0f, 15f);
myRect.anchoredPosition = screenPosition;
}
}
The script works great, but there is one problem: as you can see, i'm adding an offset in the y coordinate. This is to place the health bar above the enemy, not inside of it.
The problem comes when i move the camera however. If i zoom in, it rotates a bit (like an RTS camera) which means that the offset is wrong. The further i zoom in, the lower the health bar goes (as the offset of 15f is not enough anymore). And then when i move the camera closer to the enemy, it again pushes the health bar into him.
Is there a way to get the "border" of my enemy in screensize so i can always know what offset to have? Or is there another solution?
Thanks in advance!
The solution is simple. Don't add the offset to the screen position. Instead, add it to the enemy world position (target.position + offset).
In addition to that you can get the bounds of the mesh by using MeshRenderer.bounds.
I need to have a game object point north AND I want to combine this with gyro.attitude input. I have tried, unsuccessfully, to do this in one step. That is, I couldn't make any gyro script, which I found on the net, work with the additional requirement of always pointing north. Trust me, I have tried every script I could find on the subject. I deduced that it's impossible and probably was stupid to think it could be done; at least not this way (i.e. all-in-one). I guess you could say I surmised that you can't do two things at once. Then I thought possibly I could get the same effect by breaking-up the duties. That is, a game object that always points north via the Y axis. Great, got that done like this:
_parentDummyRotationObject.transform.rotation = Quaternion.Slerp(_parentDummyRotationObject.transform.rotation, Quaternion.Euler(0, 360 - Input.compass.trueHeading, 0), Time.deltaTime * 5f);
And with the game object pointing north on the Y, I wanted to add the second game-object, a camera in this case, with rotation using gyro input on the X and Z axis. The reason I have to eliminate the Y axes on the camera is because I get double rotation. With two things rotating at once (i.e. camera and game-object), a 180 degree rotation yielded 360 in the scene. Remember I need the game object to always point north (IRL) based on the device compass. If my device is pointing towards the East, then my game-object would be rotated 90 degrees in the unity scene as it points (rotation) towards the north.
I have read a lot about gyro camera controllers and one thing I see mentioned a lot is you shouldn't try to do this (limit it) on just 1 or 2 axis, when using Quaternions it's impossible when you don't know what you're doing, which I clearly do not.
I have tried all 3 solutions from this solved question: Unity - Gyroscope - Rotation Around One Axis Only and each has failed to rotate my camera on 1 axis to satisfy my rotational needs. Figured I'd try getting 1 axis working before muddying the waters with the 2nd axis. BTW, my requirements are simply that the camera should only rotate on 1 axis (in any orientation) based on the X axis of my device. If I could solve for X, then I thought it'd be great to get Z gyro input to control the camera as well. So far I cannot get the camera controlled on just 1 axis (X). Anyway, here are my findings...
The first solution, which used Input.gyro.rotationRateUnbiased, was totally inaccurate. That is, if I rotated my device around a few times and then put my phone/device down on my desk, the camera would be in a different rotation/location each time. There was no consistency. Here's my code for the first attempt/solution:
<code>
private void Update()
{
Vector3 previousEulerAngles = transform.eulerAngles;
Vector3 gyroInput = Input.gyro.rotationRateUnbiased;
Vector3 targetEulerAngles = previousEulerAngles + gyroInput * Time.deltaTime * Mathf.Rad2Deg;
targetEulerAngles.y = 0.0f;
targetEulerAngles.z = 0.0f;
transform.eulerAngles = targetEulerAngles;
}
</code>
The second solution was very consistent in that I could rotate my device around and then put it down on the desk and the unity camera always ended up in the same location/rotation/state so-to-speak. The problem I had was the camera would rotate on the one axis (X in this case), but it did so when I rotated my device on either the y or x axis. Either type of rotation/movement of my phone caused the unity camera to move on the X. I don't understand why the y rotation of my phone caused the camera to rotate on X. Here is my code for solution #2:
private void Start()
{
Input.gyro.enabled = true;
startEulerAngles = transform.eulerAngles;
startGyroAttitudeToEuler = Input.gyro.attitude.eulerAngles;
}
private void Update()
{
Vector3 deltaEulerAngles = Input.gyro.attitude.eulerAngles - startGyroAttitudeToEuler;
deltaEulerAngles.y = 0.0f;
deltaEulerAngles.z = 0.0f;
transform.eulerAngles = startEulerAngles - deltaEulerAngles;
}
The 3rd solution: I wasn't sure how to complete this last solution, so it never really worked. With the 2 axis zeroed-out, the camera just flipped from facing left to right and back, or top to bottom and back; depending on which axis were commented out. If none of the axis were commented-out (like the original solution) the camera would gyro around on all axis. Here's my code for attempt #3:
private void Start()
{
_upVec = Vector3.zero;
Input.gyro.enabled = true;
startEulerAngles = transform.eulerAngles;
}
private void Update()
{
Vector3 gyroEuler = Input.gyro.attitude.eulerAngles;
phoneDummy.transform.eulerAngles = new Vector3(-1.0f * gyroEuler.x, -1.0f * gyroEuler.y, gyroEuler.z);
_upVec = phoneDummy.transform.InverseTransformDirection(-1f * Vector3.forward);
_upVec.z = 0;
// _upVec.x = 0;
_upVec.y = 0;
transform.LookAt(_upVec);
// transform.eulerAngles = _upVec;
}
Originally I thought it was my skills, but after spending a month on this I'm beginning to think that this is impossible to do. But that just can't be. I know it's a lot to absorb, but it's such a simple concept.
Any ideas?
EDIT: Thought I'd add my hierarchy:
CameraRotator (parent with script) -> MainCamera (child)
CompassRotator (parent) -> Compass (child with script which rotates parent)
I'd do this in the following way:
Camara with default 0, 0, 0 rotation
Screenshot
Object placed at the center of the default position of the camera.
Script for the Camera:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
Camera m_MainCamera;
// Start is called before the first frame update
void Start()
{
// Disable the sleep timeout during gameplay.
// You can re-enable the timeout when menu screens are displayed as necessary.
Screen.sleepTimeout = SleepTimeout.NeverSleep;
// Enable the gyroscope.
if (SystemInfo.supportsGyroscope)
{
Input.gyro.enabled = true;
}
m_MainCamera = Camera.main;
m_MainCamera.enabled = true;
}
// Update is called once per frame
void Update()
{
if (m_MainCamera.enabled)
{
// First - Grab the Gyro's orientation.
Quaternion tAttitude = Input.gyro.attitude;
// The Device uses a 'left-hand' orientation, we need to transform it to 'right-hand'
Quaternion tGyro = new Quaternion(tAttitude.x, tAttitude.y, -tAttitude.z, -tAttitude.w);
// the gyro attitude is tilted towards the floor and upside-down reletive to what we want in unity.
// First Rotate the orientation up 90deg on the X Axis, then 180Deg on the Z to flip it right-side up.
Quaternion tRotation = Quaternion.Euler(-90f, 0, 0) * tGyro;
tRotation = Quaternion.Euler(0, 0, 180f) * tRotation;
// You can now apply this rotation to any unity camera!
m_MainCamera.transform.localRotation = tRotation;
}
}
}
With this script my Object always face SOUTH no matter what.
If you want the object to face NORTH you just have to turn the view 180ยบ on the Y axis as a last rotation:
Quaternion tRotation = Quaternion.Euler(-90f, 0, 0) * tGyro;
tRotation = Quaternion.Euler(0, 0, 180f) * tRotation;
//Face NORTH:
tRotation = Quaternion.Euler(0,180f, 0) * tRotation;
Hope this might help ;)
I have written this code for rotating and moving the camera.
Sadly, I am not very experianced with matrices and 3D programming, since I started only a few days ago:
plLookAt = new Vector3(plPos.X, plPos.Y, plPos.Z - 20);
if (kb.IsKeyDown(Keys.W))
{
plPos.Z++;
}
if (kb.IsKeyDown(Keys.A))
{
plPos.X++;
}
if (kb.IsKeyDown(Keys.S))
{
plPos.Z--;
}
if (kb.IsKeyDown(Keys.D))
{
plPos.X--;
}
view = Matrix.CreateLookAt(new Vector3(0, 0, 0), plLookAt, Vector3.UnitY);
view = view * Matrix.CreateRotationY(MathHelper.ToRadians(rotations.Y)) * Matrix.CreateRotationX(MathHelper.ToRadians(rotations.X));
view = view *Matrix.CreateTranslation(plPos);
if (PrMS.X < ms.X)
{
rotations.Y++;
}
else if (PrMS.X > ms.X)
{
rotations.Y--;
}
if (PrMS.Y < ms.Y)
{
rotations.X++;
}
else if (PrMS.Y > ms.Y)
{
rotations.X--;
}
plPos is the Player (camera) position
view is the view Matrix
rotations is where the rotation of the camera is saved (Vector3)
PrMS is the MouseState of the previous frame.
This code doesn't work and I think it is because of the order, which the multiplications are in, but I'm not sure. What is the best way of rotating the camera, so that it actually works and I can go in the direction the camera is facing.
Thank You in advance!
Your problem is not in the order of the matrix multiplication, it is that your rotations need to be around camera local axes and your are performing them around world axes.
I think that what you are expecting is that applying .CreateRotationX(rotations.X) and .CreateRotationY(rotationsY) will cause the camera to change pitch and yaw about it's own local axis. But these rotations always cause a rotation about the world axis. If your camera's local X axis just happens to be aligned with the world X axis and you performed a .CreateRotationX(), then it would work as expected. But in your case, you are rotating the camera about the Y axis first and this is throwing the camera's local X axis out of alignment with the world X axis so the next rotation (the X) is not going to go as expected. Even if you did the X first and the Y second, the X would throw the Y out of whack. Although the order of matrix multiplication does matter in general, in your particular case, it is the axis of rotation that is the problem.
It appears you are making a camera that is located at your player's position but can look left/right, up/down by mouse control. Here is a class that offers a different way to approach that criteria:
class Camera
{
public Matrix View { get; set; }
public Matrix Projection { get; set; }
Vector2 centerOfScreen;// the current camera's lookAt in screen space (since the mouse coords are also in screen space)
Vector3 lookAt;
float cameraRotationSpeed;
public Camera()
{
//initialize Projection matrix here
//initialize view matrix here
//initialize centerOfScreen here like this: new Vector2(screenWidth/2, screenHeihgt/2);
cameraRotationSpeed = 0.01f;//set to taste
}
public void Update(Vector3 playerPosition, MouseState currentMouse)
{
Matrix cameraWorld = Matrix.Invert(View);
Vector2 changeThisFrame = new Vector2(currentMouse.X, currentMouse.Y) - centerOfScreen;
Vector3 axis = (cameraWorld.Right * changeThisFrame.X) + (cameraWorld.Up * changeThisFrame.Y);//builds a rotation axis based on camera's local axis, not world axis
float angle = axis.Length() * cameraRotationSpeed;
axis.Normalize();
//rotate the lookAt around the camera's position
lookAt = Vector3.Transform(lookAt - playerPosition, Matrix.CreateFromAxisAngle(axis, angle)) + playerPosition;//this line does the typical "translate to origin, rotate, then translate back" routine
View = Matrix.CreateLookAt(playerPosition, lookAt, Vector3.Up);// your new view matrix that is rotated per the mouse input
}
}