-
-
Save mandarinx/2e2691ff14fdc753fa73 to your computer and use it in GitHub Desktop.
A quick example of creating a gear or object that can spin with mouse cursor. Expects object to rotate along its Forward vector.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using UnityEngine; | |
using System.Linq; | |
public class GearRotator : MonoBehaviour | |
{ | |
[SerializeField] private float m_radiusThreshold = 4f; | |
private float m_startingAngle; | |
private Quaternion m_startingRotation; | |
private bool m_isBeingRotated; | |
private void Update() | |
{ | |
if (Input.GetMouseButtonUp(0)) | |
{ | |
m_isBeingRotated = false; | |
} | |
if (Input.GetMouseButton(0)) | |
{ | |
// Create a Ray using the mouse's position into the world. | |
Ray mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition); | |
// Construct a plane using our current point. This could probably be cached | |
// on first frame of GetMouseButton but meh. | |
Plane plane = new Plane(transform.forward, transform.position); | |
float intersectDist; | |
bool bIntersected = plane.Raycast(mouseRay, out intersectDist); | |
// Calculate an intersection point by seeing how far awa from our mouseRay origin | |
// the plane lies. | |
Vector3 intersectPoint = mouseRay.origin + (mouseRay.direction * intersectDist); | |
// When they first press the mouse, check if they're within range. If they are, mark us as | |
// being rotated, otherwise, we ignore it. | |
if (Input.GetMouseButtonDown(0)) | |
{ | |
// Early out if this intersection point is too far away from our gear. | |
if (Vector3.Distance(transform.position, intersectPoint) > m_radiusThreshold) | |
return; | |
m_isBeingRotated = true; | |
} | |
// Early out if we're not currently being rotated. | |
if (!m_isBeingRotated) | |
return; | |
if (bIntersected) | |
{ | |
// Get a angle between the intersect point and us. | |
float angleDegrees = PointPairToBearingDegrees(Camera.main.transform.forward, transform.position, intersectPoint); | |
// Store the angle if the mouse was just pressed so we can subtract it. | |
// This lets us know the delta on each frame (in degrees), allowing us to | |
// smoothly rotate from current position instead of snapping. | |
if(Input.GetMouseButtonDown(0)) | |
{ | |
m_startingAngle = angleDegrees; | |
m_startingRotation = transform.rotation; | |
Debug.Log("Set Starting Angle: " + m_startingAngle); | |
} | |
float deltaAngle = angleDegrees - m_startingAngle; | |
Debug.Log("deltaAngle: " + deltaAngle); | |
// We use Vector3.forward here and not transform.forward, otehrwise we add weird off-axis | |
// rotations and it breaks everything. Quaternions! | |
Quaternion additiveRot = Quaternion.AngleAxis(deltaAngle, Vector3.forward); | |
// Finally add the rotation. | |
transform.rotation = m_startingRotation * additiveRot; | |
} | |
Debug.DrawLine(transform.position, intersectPoint, Color.green); | |
Debug.Log("Intersect Dist: " + intersectDist); | |
} | |
} | |
private float PointPairToBearingDegrees(Vector3 cameraNormal, Vector3 startPoint, Vector3 endPoint) | |
{ | |
Vector3 direction = endPoint - startPoint; | |
// Pick the correct pair of coordinates to run our Mathf.Atan2 on. If we assume X and Y, then it won't | |
// work along the X plane, as X will always be zero, etc. Oof. | |
Vector2 anglePair = FindPairForDirection(cameraNormal, direction); | |
float dirRadians = Mathf.Atan2(anglePair.y, anglePair.x); // Atan2 = Direction -> Radians | |
float dirDegrees = dirRadians * (180.0f / Mathf.PI); // To degrees - this has a discontinuity at -180/180. | |
dirDegrees = (dirDegrees > 0.0f ? dirDegrees : (360.0f + dirDegrees)); // Correct for 0-360. | |
return dirDegrees; | |
} | |
private Vector2 FindPairForDirection(Vector3 cameraNormal, Vector3 direction) | |
{ | |
// Find the closest unit plane. | |
Dictionary<int, float> planes = new Dictionary<int, float>(); | |
planes[0] = Vector3.Dot(transform.forward, Vector3.right); // X axis plane | |
planes[1] = Vector3.Dot(transform.forward, Vector3.up); // Y axis plane | |
planes[2] = Vector3.Dot(transform.forward, Vector3.forward); // Z axis plane | |
// Sort them to find the closest one. | |
var sortedPlanes = planes.OrderBy(i => i.Value).Reverse().ToArray(); | |
Debug.Log("Closest Plane: " + sortedPlanes[0].Key); | |
switch(sortedPlanes[0].Key) | |
{ | |
// X Axis, return y and z. | |
case 0: | |
return new Vector2(direction.y, direction.z); | |
// Y Axis, return x and z | |
case 1: | |
return new Vector2(direction.z, direction.x); | |
// Z axis, return y and x | |
case 2: | |
return new Vector2(direction.x, direction.y); | |
} | |
return Vector2.zero; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment