Opentk Camera rotation destroys scene objects rotations - c#

I fight with that for 2 days and searching internet but i don't have any answer for my problem. In few words i want to have orbiting camera on screen and objects which I want also rotate independently from my camera. I implement Arcball rotation controller and I use it to rotate all object in the scene (maybe this cause problem). It looks ok when i have camera in starting point but when i start moving it and I want to rotate objects on the scene its looks like all object rotate like im in the camera starting point. Maybe here someone can help :D
My Arcball class:
public class ArcBallGL{
private bool isBeingDragged;
private Vector3 downPoint;
private Vector3 currentPoint;
public Vector3 BallCenter { get; set; }
private Quaternion down;
public Quaternion Now { get; set; }
private float width, height;
public ArcBallGL()
{
Reset();
width = height = 0.0f;
}
public void Reset()
{
isBeingDragged = false;
downPoint = Vector3.Zero;
currentPoint = Vector3.Zero;
down = Quaternion.Identity;
Now = Quaternion.Identity;
}
public void SetWindow(float width, float height)
{
this.width = width;
this.height = height;
}
public void OnBeginDrag(float x, float y)
{
isBeingDragged = true;
down = Now;
downPoint = ScreenToVector(x, y);
}
public void OnMove(float x, float y)
{
if (isBeingDragged == true)
{
currentPoint = ScreenToVector(x, y);
Now = down * QuatFromBallPoints(downPoint, currentPoint);
}
}
public void OnStopDrag()
{
isBeingDragged = false;
}
public Matrix4 GetRotationMatrix()
{
return Matrix4.CreateFromQuaternion(Now);
}
public Matrix4 GetRotationMatrixFormViewPort(Matrix4 camera)
{
var viewMatrix = Matrix4.CreateFromQuaternion(Now).Inverted();
viewMatrix.Normalize();
return viewMatrix * Matrix4.CreateFromQuaternion( camera.ExtractRotation()).Inverted();
}
private Vector3 ScreenToVector(float screentPointX, float screenPointY)
{
float x = (screentPointX - width * 0.5f) / (width * 0.5f);
float y = (screenPointY - height * 0.5f) / height;
float z = 0.0f;
float mag = x * x + y * y;
if (mag > 1.0f)
{
float scale = 1.0f / (float)Math.Sqrt(mag);
x *= scale;
y *= scale;
}
else
{
z = (float)Math.Sqrt(1.0f - mag);
}
return new Vector3(-x, y, -z);
}
private Quaternion QuatFromBallPoints(Vector3 from, Vector3 to)
{
float dot = Vector3.Dot(from, to);
Vector3 part = Vector3.Cross(from, to);
return new Quaternion(part.X, part.Y, part.Z, dot);
}
}
My Camera class:
public class Camera{
public Vector3 Position = Vector3.Zero;
private Vector3 Up = Vector3.UnitY;
public float MoveSpeed = 0.2f;
public float RotationSpeed = 0.01f;
public Quaternion RotQuar { get; set; }
public Camera(Vector3 startPos = new Vector3())
{
Position = startPos;
RotQuar = Quaternion.Identity;
}
public Matrix4 GetViewMatrix(Vector3 target)
{
//RotQuar.Conjugate();
var rotMatrix = Matrix4.CreateFromQuaternion(RotQuar);
var camPosition = Vector3.Transform(Position, rotMatrix);
var camDir = target - camPosition;
camDir.Normalize();
var camRight = Vector3.Cross(camDir, Vector3.UnitY);
camRight.Normalize();
var camUp = Vector3.Cross(camRight, camDir);
camUp.Normalize();
var cameraMat = Matrix4.LookAt(camPosition, target , camUp) ;
return cameraMat;
}
}
My mouse move methood
private void MouseMoved(object sender, MouseMoveEventArgs e)
{
if (e.Mouse.IsButtonDown(MouseButton.Left) && RIsDown)
{
foreach (Shape s in _shapes)
{
if (s.IsSelected)
{
Rotation.OnMove(e.Position.X, e.Position.Y);
s.RoatationQuat = Rotation.Now;
s.RotationMatrix = Rotation.GetRotationMatrixFormViewPort(_camera.GetViewMatrix(Vector3.Zero));
}
}
}
if (e.Mouse.IsButtonDown(MouseButton.Left) && CIsDown)
{
Rotation.OnMove(e.Position.X, e.Position.Y);
_camera.RotQuar = Rotation.Now;
}
}
I use index buffer to draw my objects on the scene. I think the problem lies in calculating the camera coords to objects coords but i don't know where i must calculate it. Thanks for help

Related

How to face the Turret Gun of my Tank to the direction of the Main Camera in Unity

Hello I want to Make the gun of my turret face the direction my camera is facing at.
I don't really know Complex Maths so can you keep it simple. Thanks
My code is this:
gun.localEulerAngles = new Vector3(tankCamera.eulerAngles.x - transform.eulerAngles.x, 0);
Here is the Complete Class:
public class TurretController : MonoBehaviour
{
public Transform turret;
public Transform gun;
public Transform tankCamera;
[Header("Values")]
public float lowestX = 23;
public float highestX = -9;
public float turnSpeed = 10f;
public float additionalX = 90f;
public float maxRadians = 90f;
public bool gunLocal = true;
public bool turretRotation = false;
public LayerMask layerMaskOfGun;
[Header("Read-Only")] [SerializeField] private Vector3 angle;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (turretRotation)
{
turret.localRotation = Quaternion.RotateTowards(turret.localRotation,
new Quaternion(0, tankCamera.rotation.y, 0, turret.localRotation.w),
Time.deltaTime * turnSpeed);
}
else
{
angle = new Vector3(0, (tankCamera.eulerAngles.y - transform.eulerAngles.y));
//turret.localEulerAngles = Vector3.Lerp(turret.localEulerAngles, angle, Time.deltaTime / turnSpeed);
turret.localEulerAngles = angle;
}
//if (gun.localRotation.x <= lowestX || gun.localRotation.x >= highestX)
{
if (gunLocal)
{
gun.localEulerAngles = new Vector3(tankCamera.eulerAngles.x - transform.eulerAngles.x, 0);
//gun.localRotation = new Quaternion(tankCamera.rotation.x, 0f, 0f, gun.rotation.w);
}
else
{
Camera camera;
//RaycastHit hit;
//if (Physics.Raycast(tankCamera.position, tankCamera.forward, out hit, -layerMaskOfGun))
{
//gun.LookAt(hit.point);
gun.LookAt(tankCamera.forward);
}
}
}
}
}

How to change material on a gameobject by rotation?

I want to change between 2 materials, depending on the platforms (gameobject) rotation.
Here is what I've done so far:
public class PlatformSpawner : MonoBehaviour
{
public GameObject platform;
public Material[] platformMaterial;
Material currentMaterial;
Renderer _renderer;
}
void Start()
{
_renderer = this.GetComponent<Renderer>();
}
I also wrote this, but I don't want to change materials by buttons:
public void LeftTurn()
{
_renderer.material = platformMaterial[0];
currentMaterial = _renderer.material;
}
public void RightTurn()
{
_renderer.material = platformMaterial[1];
currentMaterial = _renderer.material;
}
}
And this is where the platform rotates randomly 90 degrees to the left or to the right:
public struct SpawnPoint
{
public Vector3 position;
public Quaternion orientation;
public void Step(float distance)
{
if (Random.value < 0.5)
{
position.x += distance;
orientation = Quaternion.Euler(0, 90, 0); //change to one of the materials
}
else
{
position.z += distance;
orientation = Quaternion.Euler(0, 0, 0); //change to the other of the materials.
//This is where I want to material to switch.
//When the objects position changes, the material changes too.
}
}
}
There is a picture of the gameplay. I want to change the material of all the corner platforms to have a nice curve line view.
Can anyone help me what and how to do in this case? I am a bit lost there.
Every help is highly appreciated!
EDIT: new code looks like that. The only issue is that Unity gives me 15 errors (see on the picture below), even if Visual Studio says no issue has been found. The errors refer to the switch.
public class PlatformSpawner : MonoBehaviour
{
public GameObject platform;
public Transform lastPlatform;
SpawnPoint _spawn;
bool stop;
public Material straightMaterial;
public Material turnLeftMaterial;
public Material turnRightMaterial;
public Renderer roadPrefab;
[System.Serializable]
public struct SpawnPoint
{
public Vector3 position;
public Quaternion orientation;
public RoadType type;
public enum RoadType
{
Straight,
LeftTurn,
RightTurn
}
private enum Direction
{
Z,
X,
}
private Direction lastDirection;
public void Step(float distance)
{
type = RoadType.Straight;
if (Random.value < 0.5f)
{
position.x += distance;
orientation = Quaternion.Euler(0, 90, 0);
if (lastDirection == Direction.Z)
{
type = RoadType.RightTurn;
}
}
else
{
position.z += distance;
orientation = Quaternion.Euler(0, 0, 0);
if (lastDirection == Direction.X)
{
type = RoadType.LeftTurn;
}
lastDirection = Direction.Z;
}
}
}
void Start()
{
_spawn.position = lastPlatform.position;
_spawn.orientation = transform.rotation;
StartCoroutine(SpawnPlatforms());
}
IEnumerator SpawnPlatforms()
{
while (!stop)
{
var _spawn = new SpawnPoint();
for (var i = 0; i < 20; i++)
{
var newPlatform = Instantiate(roadPrefab, _spawn.position, _spawn.orientation);
_spawn.Step(1.5f);
var roadMaterial = _spawn.type switch
{
SpawnPoint.RoadType.LeftTurn => turnLeftMaterial,
SpawnPoint.RoadType.RightTurn => turnRightMaterial,
_ => straightMaterial
};
newPlatform.GetComponent<Renderer>().material = roadMaterial;
yield return new WaitForSeconds(0.1f);
}
}
}
}
So it sounds like you basically have a working system for switching the materials and spawning you road parts and materials already look correctly according to your rotations - now you only need to identify the curves.
Actually this is pretty simple:
is the current part in X direction and the next will be in Z -> Left Turn
is the current part in Z direction and the next will be in X -> RightTurn
any other case is straight
So you could probably do something like
public struct SpawnPoint
{
public Vector3 position;
public Quaternion orientation;
public RoadType type;
public enum RoadType
{
Straight,
LeftTurn,
RightTurn
}
private enum Direction
{
// since your orientation by default equals the Z direction use that as default value for the first tile
Z,
X,
}
private Direction lastDirection;
public void Step(float distance)
{
type = RoadType.Straight;
if (Random.value < 0.5f)
{
position.x += distance;
orientation = Quaternion.Euler(0, 90, 0);
if(lastDirection == Direction.Z)
{
type = RoadType.RightTurn;
}
lastDirection = Direction.X;
}
else
{
position.z += distance;
orientation = Quaternion.Euler(0, 0, 0);
if(lastDirection == Direction.X)
{
type = RoadType.LeftTurn;
}
lastDirection = Direction.Z;
}
}
}
And you didn't show your spawn code but I would assume something like
public class Example : MonoBehaviour
{
public Material straightMaterial;
public Material turnLeftMaterial;
public Material turnRightMaterial;
public Renderer roadPrefab;
private void Awake()
{
var spawnPoint = new SpawnPoint();
for(var i = 0; i < 20; i++)
{
var roadTile = Instantiate(roadPrefab, spawnPoint.position, spawnPoint.orientation);
// do the Step after spawning the current tile but before assigning the material
// -> we want/need to know already where the next tile is going to be
spawnPoint.Step(1f);
var roadMaterial = spawnPoint.type switch
{
SpawnPoint.RoadType.LeftTurn => turnLeftMaterial,
SpawnPoint.RoadType.RightTurn => turnRightMaterial,
_ => straightMaterial
};
roadTile.GetComponent<Renderer>().material = roadMaterial;
}
}
}
Behold my Paint skills ;)
This will get you started using Quaternion.Dot.
using UnityEngine;
[RequireComponent(typeof(Renderer))]
public class NewBehaviourScript : MonoBehaviour
{
public Material Material1;
public Material Material2;
public Vector3 Euler = new(90, 0, 0);
private Renderer _renderer;
private void Start()
{
_renderer = GetComponent<Renderer>();
_renderer.material = Material1;
}
private void Update()
{
var dot = Quaternion.Dot(transform.rotation, Quaternion.Euler(Euler));
if (Mathf.Approximately(dot, 1.0f))
{
_renderer.material = Material2;
}
else
{
_renderer.material = Material1;
}
}
}
Using different materials for N, E, S, W corners:
using UnityEngine;
[RequireComponent(typeof(Renderer))]
public class NewBehaviourScript : MonoBehaviour
{
public Material Material1;
public Material Material2;
public Material Material3;
public Material Material4;
public Vector3 Euler1 = new(0, 0, 0);
public Vector3 Euler2 = new(0, 90, 0);
public Vector3 Euler3 = new(0, 180, 0);
public Vector3 Euler4 = new(0, 270, 0);
private Renderer _renderer;
private void Start()
{
_renderer = GetComponent<Renderer>();
_renderer.material = Material1;
}
private void Update()
{
if (Mathf.Approximately(Quaternion.Dot(transform.rotation, Quaternion.Euler(Euler1)), 1.0f))
{
_renderer.material = Material1;
}
if (Mathf.Approximately(Quaternion.Dot(transform.rotation, Quaternion.Euler(Euler2)), 1.0f))
{
_renderer.material = Material2;
}
if (Mathf.Approximately(Quaternion.Dot(transform.rotation, Quaternion.Euler(Euler3)), 1.0f))
{
_renderer.material = Material3;
}
if (Mathf.Approximately(Quaternion.Dot(transform.rotation, Quaternion.Euler(Euler4)), 1.0f))
{
_renderer.material = Material4;
}
}
}
Make sure to wrap the rotation past 360 degrees, else it'll always look yellow (4th material).

In Unity can I make the instantiate function accept custom coordinates instead of Vector3 or Transform?

I want to instantiate GameObjects(specifically hexagonal tiles) at the hexagonalCoodinates(hexcoordinates).
For this I wrote a custom coordinate system.
But I found out that unity doesn't accept anything other than Vector3 or transform.
How do I make it do that?
Or is there a easier way to do this?
This is the method to generate the gameObjects
private void TouchCell(Vector3 point)//This method instantiates cubes
{
point = transform.InverseTransformPoint(point);
HexCoordinates coordinates = HexCoordinates.FromPosition(point);
Instantiate(cubes, coordinates, Quaternion.identity);//<-The coordinate variable here is a hex coordinate.
Debug.Log("Touched at:" + coordinates);
}
And This is the Hex coordinate generator:
public struct HexCoordinates
{
public int X { get; private set; }
public int Z { get; private set; }
public int Y { get
{
return -X - Z;
} }
public HexCoordinates(int x,int z)
{
X = x;
Z = z;
}
public static HexCoordinates FromOffsetCoordinates(int x,int z)
{
return new HexCoordinates(x-z/2, z);
}
public override string ToString()
{
return "("+X.ToString()+","+Y.ToString()+","+Z.ToString()+")";
}
public string ToStringOnSeperateLines()
{
return X.ToString() + "\n" +Y.ToString()+ "\n" + Z.ToString();
}
public static HexCoordinates FromPosition(Vector3 point)//This converts the Vector3 to Hex coords
{
float x = point.x / (HexMetrics.InnerRadius * 2f);
float y = -x;
float offset = point.z / (HexMetrics.OuterRadius * 3f);
x -= offset;
y -= offset;
int iX = Mathf.RoundToInt(x);
int iY = Mathf.RoundToInt(y);
int iZ = Mathf.RoundToInt(-x - y);
if (iX + iY + iZ != 0)
{
float dX = Mathf.Abs(x-iX);
float dY = Mathf.Abs(y - iY);
float dZ = Mathf.Abs(-x-y-iZ);
if(dX>dY&&dX>dZ)
{
iX = -iY - iZ;
}
else if(dZ>dY)
{
iZ = -iX - iY;
}
}
return new HexCoordinates(iX,iZ);
}
}
Just convert from your HexCoordinates to Vector3 using any way:
create method for HexCoordinates, something like public Vector3 ToVector3() {...}
create implicit operator for implicit cast to Vector3
public struct HexCoordinates
{
public int X { get; private set; }
public int Z { get; private set; }
public int Y => -X - Z;
public HexCoordinates(int x,int z)
{
X = x;
Z = z;
}
...
public static implicit operator Vector3(HexCoordinates coords)
{
Vector3 result = // convert to Vector3
// Vector3 result = ToVector3() -- or like this for code reuse
return result;
}
public Vector3 ToVector3()
{
Vector3 result = //convert to Vector3
return result;
}
}
And then you can extend Unity's Object class and add overloading for Instantiate() method that will accept HexCoordinates, convert to Vector3 and call Instantiate()
public static class ObjectExtension
{
public static void Instantiate(this Object obj, Object origin, HexCoordinates coords, Quaternion q)
{
Vector3 position = coords.ToVector3();
obj.Instantiate(origin, position, q);
}
}
Also if you create implicit cast for your HexCoordinates to Vector3 you don't need to create overloading for Instantiate() method because converting will be implicitly
You are using using catlike coding's code. (This would have been helpful to mention in the question ;) ). In part 3 of the tutorial featuring this hex coordinate system, you can see how they would do something like below, accessing a hex inside of an array by calculating an index:
public HexCell GetCell (Vector3 position) {
position = transform.InverseTransformPoint(position);
HexCoordinates coordinates = HexCoordinates.FromPosition(position);
int index = coordinates.X + coordinates.Z * width + coordinates.Z / 2;
return cells[index];
}
So, since a HexCell has a transform.position, you can use that to get its center (making sure you don't access out of bounds):
private void TouchCell(Vector3 point)
{
point = transform.InverseTransformPoint(point);
HexCoordinates coordinates = HexCoordinates.FromPosition(point);
int index = coordinates.X + coordinates.Z * width + coordinates.Z / 2;
if (index >=0 && index < cells.Length)
{
Vector3 worldPos = cells[index].transform.position;
Instantiate(cubes, worldPos, Quaternion.identity);
Debug.Log("Touched at:" + coordinates);
}
}
Better yet, it may be worthwhile to make a method to retrieve this index, for the sake of code reuse:
private bool IsValidCellIndex(Vector3 point, out int index)
{
point = transform.InverseTransformPoint(point);
HexCoordinates coordinates = HexCoordinates.FromPosition(point);
index = coordinates.X + coordinates.Z * width + coordinates.Z / 2;
return index >=0 && index < cells.Length;
}
private void TouchCell(Vector3 point)
{
if (IsValidCellIndex(point, out int index))
{
Vector3 worldPos = cells[index].transform.position;
Instantiate(cubes, worldPos, Quaternion.identity);
Debug.Log("Touched at:" + worldPos);
}
}
Or, just use GetCell as the tutorial does :)

Unity2D pushing and pulling object of different shapes

Hello i am having trouble in my script, The player could only push a perfect square object. I added objects that are in L shape this one and i was trying to add pull by getting the sides of the collider but it is not working
the object has 2 boxcolliders. since it is an Lshape when the player push it at the center of the L if goes to the wrong direction since my code is based on the magnitude of the position of the player and the pushable object
here is my code for the pushable objects
public class PushableObject : MonoBehaviour {
[SerializeField] private Player player;
[SerializeField] private float speed = 2;
[SerializeField] private float distanceX = 2;
[SerializeField] private float distanceY = 2;
[SerializeField] List<PushableObject> objectsInArea = new List<PushableObject>();
private bool isPlayerColliding = false;
private bool isMoving = false;
public bool IsMoving
{
get { return isMoving; }
}
private void FixedUpdate()
{
if (isPlayerColliding && player.IsInteractingObject && isAnyOtherObjectMoving())
{
move();
isMoving = true;
}
else if(Input.GetKeyUp(KeyCode.E))
isMoving = false;
}
private void move()
{
Vector2 direction = transform.position - player.transform.position;
float x = Mathf.Abs(direction.x), y = Mathf.Abs(direction.y);
direction.x = x > y ? direction.x : 0;
direction.y = y > x ? direction.y : 0;
Vector2 targetPos = new Vector2(transform.position.x, transform.position.y) + direction;
transform.position = Vector2.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
}
private bool isAnyOtherObjectMoving()
{
foreach(PushableObject po in objectsInArea)
{
if (po.IsMoving)
return false;
}
return true;
}
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.CompareTag("Player"))
{
isPlayerColliding = true;
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Player"))
{
isPlayerColliding = false;
}
}
}

How to calculate SCREEN to WORLD position in 2D game

I have this class, I use it for drawing sprites in my 2D game:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
namespace Namespace
{
/// <summary>
/// Found this brilliant code at.
/// http://adambruenderman.wordpress.com/2011/04/05/create-a-2d-camera-in-xna-gs-4-0/
/// </summary>
public class CCamera2d
{
private const float zoomUpperLimit = 1.3f;
private const float zoomLowerLimit = .85f;
private float _zoom;
private Matrix _transform;
private Vector2 _pos;
private float _rotation;
private int _viewportWidth;
private int _viewportHeight;
private int _worldWidth;
private int _worldHeight;
public CCamera2d(Viewport viewport, int worldWidth, int worldHeight, float initialZoom = zoomLowerLimit)
{
_zoom = initialZoom;
_rotation = 0.0f;
_pos = Vector2.Zero;
_viewportWidth = viewport.Width;
_viewportHeight = viewport.Height;
_worldWidth = worldWidth;
_worldHeight = worldHeight;
}
#region Properties
public float Zoom
{
get { return _zoom; }
set
{
_zoom = value;
if (_zoom < zoomLowerLimit)
_zoom = zoomLowerLimit;
if (_zoom > zoomUpperLimit)
_zoom = zoomUpperLimit;
}
}
public float Rotation
{
get { return _rotation; }
set { _rotation = value; }
}
public void Move(Vector2 amount)
{
_pos += amount;
}
public Vector2 Pos
{
get { return _pos; }
set
{
float leftBarrier = (float)_viewportWidth *
.5f / _zoom;
float rightBarrier = _worldWidth -
(float)_viewportWidth * .5f / _zoom;
float topBarrier = _worldHeight -
(float)_viewportHeight * .5f / _zoom;
float bottomBarrier = (float)_viewportHeight *
.5f / _zoom;
_pos = value;
if (_pos.X < leftBarrier)
_pos.X = leftBarrier;
if (_pos.X > rightBarrier)
_pos.X = rightBarrier;
if (_pos.Y > topBarrier)
_pos.Y = topBarrier;
if (_pos.Y < bottomBarrier)
_pos.Y = bottomBarrier;
}
}
#endregion
public Matrix GetTransformation()
{
_transform =
Matrix.CreateTranslation(new Vector3(-_pos.X, -_pos.Y, 0)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(new Vector3(Zoom, Zoom, 1)) *
Matrix.CreateTranslation(new Vector3(_viewportWidth * 0.5f,
_viewportHeight * 0.5f, 0));
return _transform;
}
}
}
Now, I can't understand how to unproject a screen (mouse) position to world position.
It would be great to have a function like this:
Vector2 ScreenToWorld(Vector2 onScreen, Matrix CameraTransformation)
For WorldToScreen I use (works fine) function:
public Vector2 WorldToScreen(Vector2 inWorld)
{
return Vector2.Transform(inWorld, Camera.CurrentTransformation);
}
Inverted matrix:
public Vector2 ScreenToWorld(Vector2 onScreen)
{
var matrix = Matrix.Invert(World.Camera.CurrentTransformation);
return Vector2.Transform(onScreen, matrix);
}

Categories

Resources