-
Potential solution: https://www.gamedev.net/forums/topic/484984-skeletal-animation---non-uniform-scale/
-
Description: https://answers.unrealengine.com/questions/158091/weird-scaling-behaviour-of-actor-hierarchy.html
-
https://www.marti.works/transform-class-position-rotation-and-scale/
-
For constraining point to axis
-
For shoemake constraint
-
Existing libraries
-
Tutorials
-
Source code
-
Random notes
- Banshee 3D has a rotation tool that is similar to Unity's. Potentially look at how this works for infinite scroll.
- My closest point on line implementation is taken almost directly from Torque3D
- The transform hierarchy stuff was originally inspired by Ogre3D, tough UE4 works the same way
Last active
July 10, 2023 11:19
-
-
Save gszauer/27e0bfc3dcb3003d608e502559edae82 to your computer and use it in GitHub Desktop.
Rotation Gizmo
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
function GetWorldMatrix_CombineExperimental(transform) { | |
var position = [ | |
1, 0, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 1, 0, | |
transform.position[0], transform.position[1], transform.position[2], 1 | |
]; | |
var x = Mul_QV(transform.rotation, [1, 0, 0]); | |
var y = Mul_QV(transform.rotation, [0, 1, 0]); | |
var z = Mul_QV(transform.rotation, [0, 0, 1]); | |
var rotation = [ | |
x[0], x[1], x[2], 0, | |
y[0], y[1], y[2], 0, | |
z[0], z[1], z[2], 0, | |
0, 0, 0, 1 | |
] | |
var scale = [ | |
transform.scale[0], 0, 0, 0, | |
0, transform.scale[1], 0, 0, | |
0, 0, transform.scale[2], 0, | |
0, 0, 0, 1 | |
] | |
var SR = Mul_MM(scale, rotation); | |
var TSR = Mul_MM(SR, position) | |
var local = TSR; | |
var world = local; | |
transform.worldRotation = Copy_Q(transform.rotation); | |
if (t |
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 UnityEngine; | |
public class Rotator : MonoBehaviour { | |
public Vector3 arcballPosition = new Vector3(0,0,0); | |
public float arcballRadius = 1.0f; | |
public GameObject debugStartPoint = null; | |
public GameObject debugCurrentPoint = null; | |
private Quaternion startRotation = Quaternion.identity; | |
private Vector3 startHitPosition = Vector3.zero; | |
private bool isRunning = false; | |
void Awake() { | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(false); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(false); | |
} | |
} | |
void Update() { | |
Vector3 mousePos = Input.mousePosition; | |
Ray screenRay = Camera.main.ScreenPointToRay(mousePos); | |
if (!isRunning) { | |
if (Input.GetKeyDown(KeyCode.R) || Input.GetKey(KeyCode.R)) { | |
if (RaycastSphere(arcballPosition, arcballRadius, screenRay.origin, screenRay.direction, out startHitPosition)) { | |
startRotation = transform.rotation; | |
isRunning = true; | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(true); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(true); | |
} | |
} | |
} | |
} | |
else { | |
if (Input.GetKeyUp(KeyCode.R) || !Input.GetKey(KeyCode.R)) { | |
isRunning = false; | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(false); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(false); | |
} | |
} | |
else { | |
Vector3 currentHitPosition = Vector3.zero; | |
if (!RaycastSphere(arcballPosition, arcballRadius, screenRay.origin, screenRay.direction, out currentHitPosition)) { | |
// TODO: Make plane to raycast against! | |
} | |
/* Start & current are both points on a sphere | |
A quaternion is constructed like so: | |
q.w == cos(angle / 2) | |
q.<xyz> == sin(angle / 2) | |
The sine & cosine of the angles relate to the | |
dot & cross products of the start & current vectors. | |
dot(start, current) == cos(theta) | |
cross(start, current).<xyz> == sin(theta) * <xyz> | |
The problem is that the dot product will return in cos(theta), | |
but we want cos(theta / 2). Same with the cross product. | |
One way to solve this is to create a half-way vector between | |
start & current. Let's call it half. Use this to construct | |
the quaternion that rotates from start to current. | |
The resulting quaternion must be normalized. | |
If u == -v, a shortest path is not possible. See this for details: | |
https://stackoverflow.com/questions/1171849/finding-quaternion-representing-the-rotation-from-one-vector-to-another | |
*/ | |
Vector3 halfWayVector = startHitPosition + (currentHitPosition - startHitPosition) * 0.5f; | |
Vector3 perpendicularAxis = Vector3.Cross(startHitPosition, halfWayVector); | |
Quaternion fromToRotation; | |
fromToRotation.w = Vector3.Dot(startHitPosition, halfWayVector); | |
fromToRotation.x = perpendicularAxis.x; | |
fromToRotation.y = perpendicularAxis.y; | |
fromToRotation.z = perpendicularAxis.z; | |
fromToRotation = NormalizeQuaternion(fromToRotation); | |
transform.rotation = fromToRotation * startRotation; | |
if (debugStartPoint != null) { | |
debugStartPoint.transform.position = startHitPosition; | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.transform.position = currentHitPosition; | |
} | |
} | |
} | |
} | |
protected Quaternion NormalizeQuaternion(Quaternion quat) { | |
float len = quat.x * quat.x + quat.y * quat.y + quat.z * quat.z + quat.w * quat.w; | |
if (len != 0.0f) { | |
len = Mathf.Sqrt(len); | |
quat.x /= len; | |
quat.y /= len; | |
quat.z /= len; | |
quat.w /= len; | |
} | |
return quat; | |
} | |
protected bool RaycastSphere(Vector3 spherePosition, float sphereRadius, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
Vector3 e = spherePosition - rayOrigin; | |
float rSq = sphereRadius * sphereRadius; | |
float eSq = e.sqrMagnitude; | |
float a = Vector3.Dot(e, rayDirection.normalized); | |
float bSq = eSq - (a * a); | |
float f = Mathf.Sqrt(Mathf.Abs((rSq) - bSq)); | |
float t = a - f; | |
if (rSq - (eSq - a * a) < 0.0f) { // No Collision | |
return false; | |
} | |
else if (eSq < rSq) { // Ray starts inside the sphere | |
t = a + f;// Just reverse direction | |
} | |
hitPosition = rayOrigin + rayDirection.normalized * t; | |
return true; | |
} | |
} |
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 UnityEngine; | |
public class RotatorV10 : MonoBehaviour { | |
public Vector3 arcballPosition = new Vector3(0, 0, 0); | |
public float arcballRadius = 1.0f; | |
private Quaternion startRotation = Quaternion.identity; | |
private Vector3 startHitPosition = Vector3.zero; | |
private Vector3 startPointOnPlane = Vector3.zero; | |
private Vector3 startPointOnCameraPlane = Vector3.zero; | |
public bool constraintGlobal = true; | |
public bool debugShowPlane = false; | |
private bool isRunning = false; | |
public GameObject debugStartPoint = null; | |
public GameObject debugCurrentPoint = null; | |
public GameObject debugArcBall = null; | |
public GameObject debugPlane = null; | |
public GameObject debugConstraintPlane = null; | |
public GameObject debugAxisLocal = null; | |
public GameObject debugAxisGlobal = null; | |
public GameObject debugAxisLocal_X = null; | |
public GameObject debugAxisGlobal_X = null; | |
public GameObject debugAxisLocal_Y = null; | |
public GameObject debugAxisGlobal_Y = null; | |
public GameObject debugAxisLocal_Z = null; | |
public GameObject debugAxisGlobal_Z = null; | |
void Awake() { | |
DebugVisible = false; | |
if (debugArcBall != null) { | |
arcballPosition = debugArcBall.transform.position; | |
arcballRadius = debugArcBall.transform.lossyScale.x * 0.5f; | |
} | |
// transform.rotation = Quaternion.identity; | |
} | |
void Update() { | |
// Potentially set the constraint axis | |
Vector3 constraint = Constraint; | |
// Don't do anything if there is no constraint! | |
if (constraint == Vector3.zero) { | |
isRunning = false; | |
DebugVisible = false; | |
return; | |
} | |
// Get the mouse position & create a world space ray from it | |
Vector3 mousePos = Input.mousePosition; | |
Ray screenRay = Camera.main.ScreenPointToRay(mousePos); | |
// Construct a plane that has the normal facing the camera and always bisects the arcball sphere | |
Vector3 cameraPlaneNormal = Camera.main.transform.forward * -1.0f; | |
float cameraPlaneDistance = Vector3.Dot(cameraPlaneNormal, arcballPosition); | |
// Find where the world space ray intersects the plane that was created | |
Vector3 pointOnPlane = Vector3.zero; | |
RaycastPlane(cameraPlaneNormal, cameraPlaneDistance, screenRay.origin, screenRay.direction, out pointOnPlane); | |
Vector3 pointOnCameraPlane = pointOnPlane; | |
// Using some trig, project the point on the plane onto the hemisphere of the arcball. | |
// A^2 + B^2 = C^2 | |
// C^2 = Radius^2 | |
// A^2 = distance of point on plane from arcball ^2 | |
// Clamp A^2 to C^2. That way the mouse can never trully leave the sphere | |
float cSq = arcballRadius * arcballRadius; | |
float aSq = (arcballPosition - pointOnPlane).sqrMagnitude; | |
if (aSq > cSq) aSq = cSq; | |
float bSq = cSq - aSq; | |
// SampledPoint holds where on the hemisphere the point on plane is. Remember, it's clamped to the edge of the sphere! | |
Vector3 mapDirection = cameraPlaneNormal; | |
Vector3 sampledPoint = arcballPosition + (pointOnPlane - arcballPosition).normalized * Mathf.Sqrt(aSq) + (mapDirection * Mathf.Sqrt(bSq)); | |
// Construct a plane out of the constrain axis | |
Vector3 constraintPlaneNormal = constraint.normalized; | |
float constraintPlaneDistance = Vector3.Dot(constraintPlaneNormal, arcballPosition); | |
Ray closestPointRay = new Ray(); | |
closestPointRay.origin = sampledPoint; | |
if (Mathf.Abs (Vector3.Dot (constraintPlaneNormal, cameraPlaneNormal)) < 0.9f) { // NON-EDGE CASE | |
closestPointRay.direction = Vector3.Cross (Vector3.Cross (cameraPlaneNormal, constraintPlaneNormal), cameraPlaneNormal); | |
RaycastPlaneIgnoringRayDirection (constraintPlaneNormal, constraintPlaneDistance, closestPointRay.origin, closestPointRay.direction, out pointOnPlane); | |
RaycastPlaneIgnoringRayDirection (constraintPlaneNormal, constraintPlaneDistance, pointOnCameraPlane, closestPointRay.direction, out pointOnCameraPlane); | |
} | |
sampledPoint = arcballPosition + (pointOnPlane - arcballPosition).normalized * arcballRadius; | |
if (!isRunning) { | |
if (IsRotationActive) { | |
if (RaycastSphere(arcballPosition, arcballRadius, screenRay.origin, screenRay.direction, out startHitPosition)) { | |
startHitPosition = sampledPoint; | |
startRotation = transform.rotation; | |
startPointOnPlane = pointOnPlane; // Will be used for distance calculation | |
startPointOnCameraPlane = pointOnCameraPlane; | |
isRunning = true; | |
DebugVisible = true; | |
} | |
} | |
} | |
else { | |
if (!IsRotationActive) { | |
DebugVisible = false; | |
} | |
else { | |
Vector3 currentPointOnCameraPlane = pointOnCameraPlane; | |
Vector3 currentHitPosition = sampledPoint; | |
Vector3 startVector = startHitPosition - arcballPosition; | |
Vector3 currentVector = currentHitPosition - arcballPosition; | |
Vector3 axis = Vector3.Cross(startVector, currentVector).normalized; | |
float distance = (startPointOnCameraPlane - currentPointOnCameraPlane).magnitude; | |
float turns = distance / (4f * arcballRadius); // Every 4R of distance = 1 full rotation! Unit is currently in number of rotations. | |
float angle = turns * 6.28319f; // convert rotations to radians. One full rotation = 6.28 radians. | |
//Debug.Log("Angle: " + angle); | |
float hCos = Mathf.Cos(angle * 0.5f); | |
float hSin = Mathf.Sin(angle * 0.5f); | |
// Create the actual quaternion from the above values. This isn't a from-to rotation per-say. We still use the same vectors to figure out the | |
// angle of orientation, but the actual angle comes from distance of mouse movement. | |
Quaternion fromToRotation = Quaternion.identity; | |
fromToRotation.w = hCos; | |
fromToRotation.x = hSin * axis.x; | |
fromToRotation.y = hSin * axis.y; | |
fromToRotation.z = hSin * axis.z; | |
fromToRotation = NormalizeQuaternion(fromToRotation); | |
if (Mathf.Abs (Vector3.Dot (constraintPlaneNormal, cameraPlaneNormal)) > 0.9f) { // EDGE CASE | |
fromToRotation = HalfQuaternionMethod (startVector, currentVector); | |
fromToRotation = NormalizeQuaternion(fromToRotation); | |
} | |
// Apply the rotation. | |
transform.rotation = fromToRotation * startRotation; | |
if (debugStartPoint != null) { | |
debugStartPoint.transform.position = startHitPosition;//startPointOnCameraPlane; | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.transform.position = currentHitPosition;//currentPointOnCameraPlane; | |
} | |
} | |
} | |
} | |
protected Quaternion NormalizeQuaternion(Quaternion quat) { | |
float len = quat.x * quat.x + quat.y * quat.y + quat.z * quat.z + quat.w * quat.w; | |
if (len != 0.0f) { | |
len = Mathf.Sqrt(len); | |
quat.x /= len; | |
quat.y /= len; | |
quat.z /= len; | |
quat.w /= len; | |
} | |
return quat; | |
} | |
bool DebugVisible { | |
set { | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(value); | |
if (value) { | |
debugStartPoint.transform.position = startPointOnPlane; | |
} | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(value); | |
if (value) { | |
debugCurrentPoint.transform.position = startPointOnPlane; | |
} | |
} | |
if (debugPlane != null) { | |
debugPlane.SetActive(value && debugShowPlane); | |
debugPlane.transform.position = transform.position; | |
} | |
if (debugConstraintPlane != null) { | |
debugConstraintPlane.SetActive(value && debugShowPlane); | |
debugConstraintPlane.transform.position = transform.position; | |
} | |
if (debugAxisLocal != null) { | |
debugAxisLocal.SetActive(value && !constraintGlobal); | |
} | |
if (debugAxisGlobal != null) { | |
debugAxisGlobal.SetActive(value && constraintGlobal); | |
} | |
if (debugAxisLocal_X != null) { | |
debugAxisLocal_X.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisLocal_Y != null) { | |
debugAxisLocal_Y.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisLocal_Z != null) { | |
debugAxisLocal_Z.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
if (debugAxisGlobal_X != null) { | |
debugAxisGlobal_X.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisGlobal_Y != null) { | |
debugAxisGlobal_Y.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisGlobal_Z != null) { | |
debugAxisGlobal_Z.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
} | |
} | |
// Quaternion.fromToRotation | |
protected Quaternion HalfQuaternionMethod(Vector3 v0, Vector3 v1) { | |
Quaternion result = Quaternion.identity; | |
Vector3 axis = Vector3.zero; | |
float k_cos_theta = Vector3.Dot(v0, v1); | |
float k = Mathf.Sqrt(v0.sqrMagnitude * v1.sqrMagnitude); | |
if (k_cos_theta / k == -1) { | |
result.w = 0.0f; | |
axis = OrthogonalAxis(v0).normalized; | |
} | |
else { | |
result.w = k_cos_theta + k; | |
axis = Vector3.Cross(v0, v1); | |
} | |
result.x = axis.x; | |
result.y = axis.y; | |
result.z = axis.z; | |
return NormalizeQuaternion(result); | |
} | |
protected Vector3 OrthogonalAxis(Vector3 v) { | |
float x = Mathf.Abs(v.x); | |
float y = Mathf.Abs(v.y); | |
float z = Mathf.Abs(v.z); | |
Vector3 other = Vector3.zero; | |
if (x < y) { | |
if (x < z) { | |
other = Vector3.right; // x axis | |
} | |
else { | |
other = Vector3.forward; // z axis | |
} | |
} | |
else { | |
if (y < z) { | |
other = Vector3.up; // y axis | |
} | |
else { | |
other = Vector3.forward; // z axis | |
} | |
} | |
return Vector3.Cross(v, other); | |
} | |
bool IsRotationActive { | |
get { | |
if (Input.GetKey(KeyCode.Z) || Input.GetKey(KeyCode.X) || Input.GetKey(KeyCode.Y)) { | |
return true; | |
} | |
return false; | |
} | |
} | |
protected Vector3 _constraint = Vector3.zero; | |
public Vector3 Constraint { | |
get { | |
if (Input.GetKeyDown(KeyCode.X)) { | |
_constraint = constraintGlobal ? Camera.main.transform.up : transform.up; | |
} | |
if (Input.GetKeyDown(KeyCode.Y)) { | |
_constraint = constraintGlobal ? Camera.main.transform.right : transform.right; | |
} | |
if (Input.GetKeyDown(KeyCode.Z)) { | |
_constraint = constraintGlobal ? Camera.main.transform.forward : transform.forward; | |
} | |
if (Input.GetKeyUp(KeyCode.X) || Input.GetKeyUp(KeyCode.Y) || Input.GetKeyUp(KeyCode.Z)) { | |
_constraint = Vector3.zero; | |
} | |
if (debugPlane != null && IsRotationActive) { | |
debugPlane.transform.localRotation = Quaternion.LookRotation(_constraint, Vector3.up); | |
} | |
if (debugConstraintPlane != null && IsRotationActive) { | |
debugConstraintPlane.transform.localRotation = Quaternion.LookRotation(Camera.main.transform.forward * -1.0f, Vector3.up); | |
} | |
return _constraint; | |
} | |
} | |
protected bool RaycastPlane(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
return false; | |
} | |
protected bool RaycastPlaneIgnoringRayDirection(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
// Plane normal should not matter for this one!!! | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
//if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
//} | |
//return false; | |
} | |
protected bool RaycastSphere(Vector3 spherePosition, float sphereRadius, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
Vector3 e = spherePosition - rayOrigin; | |
float rSq = sphereRadius * sphereRadius; | |
float eSq = e.sqrMagnitude; | |
float a = Vector3.Dot(e, rayDirection); | |
float bSq = eSq - (a * a); | |
float f = Mathf.Sqrt(Mathf.Abs((rSq) - bSq)); | |
float t = a - f; | |
if (rSq - (eSq - a * a) < 0.0f) { // No Collision | |
return false; | |
} | |
else if (eSq < rSq) { // Ray starts inside the sphere | |
t = a + f;// Just reverse direction | |
} | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
} |
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 UnityEngine; | |
public class RotatorV2 : MonoBehaviour { | |
public Vector3 arcballPosition = new Vector3(0,0,0); | |
public float arcballRadius = 1.0f; | |
public GameObject debugStartPoint = null; | |
public GameObject debugCurrentPoint = null; | |
private Quaternion startRotation = Quaternion.identity; | |
private Vector3 startHitPosition = Vector3.zero; | |
private bool isRunning = false; | |
void Awake() { | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(false); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(false); | |
} | |
} | |
void Update() { | |
Vector3 mousePos = Input.mousePosition; | |
Vector3 radiusWorldPosition = arcballPosition + Camera.main.transform.right * arcballRadius; | |
Vector3 arcScreenPosition = Camera.main.WorldToScreenPoint(arcballPosition); | |
arcScreenPosition.z = 0.0f; | |
Vector3 radiusScreenPosition = Camera.main.WorldToScreenPoint(radiusWorldPosition); | |
radiusScreenPosition.z = 0.0f; | |
float screenRadius = (radiusScreenPosition - arcScreenPosition).magnitude; | |
Vector3 pointOnSphere = Vector3.zero; | |
pointOnSphere.x = (mousePos.x - arcScreenPosition.x) / screenRadius; | |
pointOnSphere.y = (mousePos.y - arcScreenPosition.y) / screenRadius; | |
float r = pointOnSphere.x * pointOnSphere.x + pointOnSphere.y * pointOnSphere.y; | |
if (r > 1.0f) { | |
float s = 1.0f / Mathf.Sqrt(r); | |
pointOnSphere.x = pointOnSphere.x * s; | |
pointOnSphere.y = pointOnSphere.y * s; | |
pointOnSphere.z = 0.0f; | |
} | |
else { | |
pointOnSphere.z = Mathf.Sqrt(1.0f - r); | |
} | |
pointOnSphere.z *= -1.0f; | |
if (!isRunning) { | |
if (Input.GetKeyDown(KeyCode.R) || Input.GetKey(KeyCode.R)) { | |
startHitPosition = pointOnSphere; | |
startRotation = transform.rotation; | |
isRunning = true; | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(true); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(true); | |
} | |
} | |
} | |
else { | |
if (Input.GetKeyUp(KeyCode.R) || !Input.GetKey(KeyCode.R)) { | |
isRunning = false; | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(false); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(false); | |
} | |
} | |
else { | |
Vector3 currentHitPosition = pointOnSphere; | |
Vector3 halfWayVector = startHitPosition + (currentHitPosition - startHitPosition) * 0.5f; | |
Vector3 perpendicularAxis = Vector3.Cross(startHitPosition, halfWayVector); | |
Quaternion fromToRotation; | |
fromToRotation.w = Vector3.Dot(startHitPosition, halfWayVector); | |
fromToRotation.x = perpendicularAxis.x; | |
fromToRotation.y = perpendicularAxis.y; | |
fromToRotation.z = perpendicularAxis.z; | |
fromToRotation = NormalizeQuaternion(fromToRotation); | |
transform.rotation = fromToRotation * startRotation; | |
if (debugStartPoint != null) { | |
debugStartPoint.transform.position = startHitPosition; | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.transform.position = currentHitPosition; | |
} | |
} | |
} | |
} | |
protected Quaternion NormalizeQuaternion(Quaternion quat) { | |
float len = quat.x * quat.x + quat.y * quat.y + quat.z * quat.z + quat.w * quat.w; | |
if (len != 0.0f) { | |
len = Mathf.Sqrt(len); | |
quat.x /= len; | |
quat.y /= len; | |
quat.z /= len; | |
quat.w /= len; | |
} | |
return quat; | |
} | |
} |
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 UnityEngine; | |
public class RotatorV3 : MonoBehaviour { | |
public Vector3 arcballPosition = new Vector3(0, 0, 0); | |
public float arcballRadius = 1.0f; | |
public GameObject debugStartPoint = null; | |
public GameObject debugCurrentPoint = null; | |
public GameObject debugArcBall = null; | |
private Quaternion startRotation = Quaternion.identity; | |
private Vector3 startHitPosition = Vector3.zero; | |
private bool isRunning = false; | |
void Awake() { | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(false); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(false); | |
} | |
if (debugArcBall != null) { | |
arcballPosition = debugArcBall.transform.position; | |
arcballRadius = debugArcBall.transform.lossyScale.x * 0.5f; | |
} | |
} | |
void Update() { | |
Vector3 mousePos = Input.mousePosition; | |
Ray screenRay = Camera.main.ScreenPointToRay(mousePos); | |
if (!isRunning) { | |
if (Input.GetKeyDown(KeyCode.R) || Input.GetKey(KeyCode.R)) { | |
if (RaycastSphere(arcballPosition, arcballRadius, screenRay.origin, screenRay.direction, out startHitPosition)) { | |
startRotation = transform.rotation; | |
isRunning = true; | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(true); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(true); | |
} | |
} | |
} | |
} | |
else { | |
if (Input.GetKeyUp(KeyCode.R) || !Input.GetKey(KeyCode.R)) { | |
isRunning = false; | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(false); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(false); | |
} | |
} | |
else { | |
Vector3 currentHitPosition = Vector3.zero; | |
if (!RaycastSphere(arcballPosition, arcballRadius, screenRay.origin, screenRay.direction, out currentHitPosition)) { | |
// Plane bisects sphere in half | |
Vector3 planeNormal = Camera.main.transform.forward * -1.0f; // This should probably be sphere forward? | |
float planeDistance = Vector3.Dot(planeNormal, arcballPosition); | |
if (!RaycastPlane(planeNormal, planeDistance, screenRay.origin, screenRay.direction, out currentHitPosition)) { | |
Debug.LogError("Mouse did not intersect either sphere or plane"); | |
} | |
} | |
// Don't test an absolute position. Everything should be relative to the center of the sphere | |
Vector3 startVector = startHitPosition - arcballPosition; | |
Vector3 currentVector = currentHitPosition - arcballPosition; | |
Quaternion fromToRotation = HalfQuaternionMethod(startVector, currentVector); | |
transform.rotation = fromToRotation * startRotation; | |
if (debugStartPoint != null) { | |
debugStartPoint.transform.position = startHitPosition; | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.transform.position = currentHitPosition; | |
} | |
} | |
} | |
} | |
protected Vector3 ClosestPointOnRay(Vector3 point, Vector3 rayOrigin, Vector3 rayDirection) { | |
rayDirection = rayDirection.normalized; | |
float t = Vector3.Dot(point - rayOrigin, rayDirection); | |
t = Mathf.Max(t, 0.0f); | |
return rayOrigin + rayDirection * t; | |
} | |
// Quaternion.fromToRotation | |
protected Quaternion NaiveMethod(Vector3 v0, Vector3 v1) { | |
Vector3 halfWayVector = v0 + (v1 - v0) * 0.5f; | |
Vector3 perpendicularAxis = Vector3.Cross(v0, halfWayVector); | |
Quaternion result = Quaternion.identity; | |
result.w = Vector3.Dot(v0, halfWayVector); | |
result.x = perpendicularAxis.x; | |
result.y = perpendicularAxis.y; | |
result.z = perpendicularAxis.z; | |
return NormalizeQuaternion(result); | |
} | |
// Quaternion.fromToRotation | |
protected Quaternion HalfVectorMethod(Vector3 v0, Vector3 v1) { | |
Quaternion result = Quaternion.identity; | |
Vector3 axis = Vector3.zero; | |
v0 = v0.normalized; | |
v1 = v1.normalized; | |
if (v0 == v1 * -1.0f) { | |
result.w = 0.0f; | |
axis = OrthogonalAxis(v0).normalized; | |
} | |
else { | |
Vector3 half = (v0 + v1).normalized; | |
result.w = Vector3.Dot(v0, half); | |
axis = Vector3.Cross(v0, half); | |
} | |
result.x = axis.x; | |
result.y = axis.y; | |
result.z = axis.z; | |
return NormalizeQuaternion(result); | |
} | |
// Quaternion.fromToRotation | |
protected Quaternion HalfQuaternionMethod(Vector3 v0, Vector3 v1) { | |
Quaternion result = Quaternion.identity; | |
Vector3 axis = Vector3.zero; | |
float k_cos_theta = Vector3.Dot(v0, v1); | |
float k = Mathf.Sqrt(v0.sqrMagnitude * v1.sqrMagnitude); | |
if (k_cos_theta / k == -1) { | |
result.w = 0.0f; | |
axis = OrthogonalAxis(v0).normalized; | |
} | |
else { | |
result.w = k_cos_theta + k; | |
axis = Vector3.Cross(v0, v1); | |
} | |
result.x = axis.x; | |
result.y = axis.y; | |
result.z = axis.z; | |
return NormalizeQuaternion(result); | |
} | |
protected Vector3 OrthogonalAxis(Vector3 v) { | |
float x = Mathf.Abs(v.x); | |
float y = Mathf.Abs(v.y); | |
float z = Mathf.Abs(v.z); | |
Vector3 other = Vector3.zero; | |
if (x < y) { | |
if (x < z) { | |
other = Vector3.right; // x axis | |
} | |
else { | |
other = Vector3.forward; // z axis | |
} | |
} | |
else { | |
if (y < z) { | |
other = Vector3.up; // y axis | |
} | |
else { | |
other = Vector3.forward; // z axis | |
} | |
} | |
return Vector3.Cross(v, other); | |
} | |
protected Quaternion NormalizeQuaternion(Quaternion quat) { | |
float len = quat.x * quat.x + quat.y * quat.y + quat.z * quat.z + quat.w * quat.w; | |
if (len != 0.0f) { | |
len = Mathf.Sqrt(len); | |
quat.x /= len; | |
quat.y /= len; | |
quat.z /= len; | |
quat.w /= len; | |
} | |
return quat; | |
} | |
protected bool RaycastPlane(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
if (nd >= 0.0f) { // Wrong direction | |
return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
return false; | |
} | |
protected bool RaycastSphere(Vector3 spherePosition, float sphereRadius, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
Vector3 e = spherePosition - rayOrigin; | |
float rSq = sphereRadius * sphereRadius; | |
float eSq = e.sqrMagnitude; | |
float a = Vector3.Dot(e, rayDirection); | |
float bSq = eSq - (a * a); | |
float f = Mathf.Sqrt(Mathf.Abs((rSq) - bSq)); | |
float t = a - f; | |
if (rSq - (eSq - a * a) < 0.0f) { // No Collision | |
return false; | |
} | |
else if (eSq < rSq) { // Ray starts inside the sphere | |
t = a + f;// Just reverse direction | |
} | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
} |
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 UnityEngine; | |
public class RotatorV4 : MonoBehaviour { | |
public Vector3 arcballPosition = new Vector3(0, 0, 0); | |
public float arcballRadius = 1.0f; | |
public GameObject debugStartPoint = null; | |
public GameObject debugCurrentPoint = null; | |
public GameObject debugArcBall = null; | |
private Quaternion startRotation = Quaternion.identity; | |
private Vector3 startHitPosition = Vector3.zero; | |
private Vector3 startPointOnPlane = Vector3.zero; | |
private bool isRunning = false; | |
void Awake() { | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(false); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(false); | |
} | |
if (debugArcBall != null) { | |
arcballPosition = debugArcBall.transform.position; | |
arcballRadius = debugArcBall.transform.lossyScale.x * 0.5f; | |
} | |
} | |
void Update() { | |
// Get the mouse position & create a world space ray from it | |
Vector3 mousePos = Input.mousePosition; | |
Ray screenRay = Camera.main.ScreenPointToRay(mousePos); | |
// Construct a plane that has the normal facing the camera and always bisects the arcball sphere | |
Vector3 planeNormal = Camera.main.transform.forward * -1.0f; | |
float planeDistance = Vector3.Dot(planeNormal, arcballPosition); | |
// Find where the world space ray intersects the plane that was created | |
Vector3 pointOnPlane = Vector3.zero; | |
RaycastPlane(planeNormal, planeDistance, screenRay.origin, screenRay.direction, out pointOnPlane); | |
// Using some trig, project the point on the plane onto the hemisphere of the arcball. | |
// A^2 + B^2 = C^2 | |
// C^2 = Radius^2 | |
// A^2 = distance of point on plane from arcball ^2 | |
// Clamp A^2 to C^2. That way the mouse can never trully leave the sphere | |
float cSq = arcballRadius * arcballRadius; | |
float aSq = (arcballPosition - pointOnPlane).sqrMagnitude; | |
if (aSq > cSq) aSq = cSq; | |
float bSq = cSq - aSq; | |
// SampledPoint holds where on the hemisphere the point on plane is. Remember, it's clamped to the edge of the sphere! | |
Vector3 sampledPoint = arcballPosition + (pointOnPlane - arcballPosition).normalized * Mathf.Sqrt(aSq) + (planeNormal * Mathf.Sqrt(bSq)); | |
if (!isRunning) { | |
if (Input.GetKeyDown(KeyCode.R) || Input.GetKey(KeyCode.R)) { | |
if (RaycastSphere(arcballPosition, arcballRadius, screenRay.origin, screenRay.direction, out startHitPosition)) { | |
// startHitPosition is cached by the raycast function | |
startRotation = transform.rotation; | |
startPointOnPlane = pointOnPlane; // Will be used for distance calculation | |
isRunning = true; | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(true); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(true); | |
} | |
} | |
} | |
} | |
else { | |
if (Input.GetKeyUp(KeyCode.R) || !Input.GetKey(KeyCode.R)) { | |
isRunning = false; | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(false); | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(false); | |
} | |
} | |
else { | |
Vector3 currentPointOnPlane = pointOnPlane; | |
Vector3 currentHitPosition = sampledPoint; | |
// Find how far on the plane the user has dragged the mouse. | |
// Every 1 radian of distance = 1/4 of a turn. So 4 radians of distance = 1 turn | |
// This of course will need to be converted to radians. 1 full turn = 6.28 radians | |
float totalDistance = (startPointOnPlane - currentPointOnPlane).magnitude; | |
float numTurns = totalDistance / (4f * arcballRadius); // Every 4R of distance = 1 full rotation! Unit is currently in number of rotations. | |
float angle = numTurns * 6.28319f; // convert rotations to radians. One full rotation = 6.28 radians. | |
// We now know the angle of rotation. Remember the formula for quaternion constructs angle x 2, so we need to half this. | |
float hCos = Mathf.Cos(angle * 0.5f); | |
float hSin = Mathf.Sin(angle * 0.5f); | |
// Construct the axis of rotation. In order to work in world space, all vectors should be relative | |
// to arcball position! (I'm not sure if the axis of rotation needs to be normalized or not). | |
Vector3 startVector = startHitPosition - arcballPosition; | |
Vector3 currentVector = currentHitPosition - arcballPosition; | |
Vector3 axis = Vector3.Cross(startVector, currentVector).normalized; | |
// Create the actual quaternion from the above values. This isn't a from-to rotation per-say. We still use the same vectors to figure out the | |
// angle of orientation, but the actual angle comes from distance of mouse movement. | |
Quaternion fromToRotation = Quaternion.identity; | |
fromToRotation.w = hCos; | |
fromToRotation.x = hSin * axis.x; | |
fromToRotation.y = hSin * axis.y; | |
fromToRotation.z = hSin * axis.z; | |
fromToRotation = NormalizeQuaternion(fromToRotation); | |
// Apply the rotation. | |
transform.rotation = fromToRotation * startRotation; | |
if (debugStartPoint != null) { | |
debugStartPoint.transform.position = startHitPosition; | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.transform.position = currentHitPosition; | |
} | |
} | |
} | |
} | |
protected Quaternion NormalizeQuaternion(Quaternion quat) { | |
float len = quat.x * quat.x + quat.y * quat.y + quat.z * quat.z + quat.w * quat.w; | |
if (len != 0.0f) { | |
len = Mathf.Sqrt(len); | |
quat.x /= len; | |
quat.y /= len; | |
quat.z /= len; | |
quat.w /= len; | |
} | |
return quat; | |
} | |
protected bool RaycastPlane(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
return false; | |
} | |
protected bool RaycastSphere(Vector3 spherePosition, float sphereRadius, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
Vector3 e = spherePosition - rayOrigin; | |
float rSq = sphereRadius * sphereRadius; | |
float eSq = e.sqrMagnitude; | |
float a = Vector3.Dot(e, rayDirection); | |
float bSq = eSq - (a * a); | |
float f = Mathf.Sqrt(Mathf.Abs((rSq) - bSq)); | |
float t = a - f; | |
if (rSq - (eSq - a * a) < 0.0f) { // No Collision | |
return false; | |
} | |
else if (eSq < rSq) { // Ray starts inside the sphere | |
t = a + f;// Just reverse direction | |
} | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
} |
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 UnityEngine; | |
public class RotatorV5 : MonoBehaviour { | |
public Vector3 transformCenter = new Vector3(0, 0, 0); | |
public float transformRadius = 1.0f; | |
public GameObject debugStartPoint = null; | |
public GameObject debugCurrentPoint = null; | |
public GameObject debugArcBall = null; | |
public GameObject debugPlane = null; | |
public GameObject debugAxisLocal = null; | |
public GameObject debugAxisGlobal = null; | |
public GameObject debugAxisLocal_X = null; | |
public GameObject debugAxisGlobal_X = null; | |
public GameObject debugAxisLocal_Y = null; | |
public GameObject debugAxisGlobal_Y = null; | |
public GameObject debugAxisLocal_Z = null; | |
public GameObject debugAxisGlobal_Z = null; | |
private Quaternion startRotation = Quaternion.identity; | |
private Vector3 startHitPosition = Vector3.zero; | |
public bool constraintGlobal = true; | |
public bool debugShowPlane = false; | |
private bool isRunning = false; | |
void Awake() { | |
DebugVisible = false; | |
if (debugArcBall != null) { | |
transformCenter = debugArcBall.transform.position; | |
transformRadius = debugArcBall.transform.lossyScale.x * 0.5f; | |
} | |
} | |
void Update() { | |
// Potentially set the constraint axis | |
Vector3 constraint = Constraint; | |
// Don't do anything if there is no constraint! | |
if (constraint == Vector3.zero) { | |
isRunning = false; | |
DebugVisible = false; | |
return; | |
} | |
// Get the mouse position & create a world space ray from it | |
Vector3 mousePos = Input.mousePosition; | |
Ray screenRay = Camera.main.ScreenPointToRay(mousePos); | |
// Construct a plane out of the constrain axis | |
Vector3 constraintPlaneNormal = constraint.normalized; | |
float constraintPlaneDifference = Vector3.Dot(constraintPlaneNormal, transformCenter); | |
// Construct the camera plane (a plane that faces the camera) | |
Vector3 cameraPlaneNormal = Camera.main.transform.forward * -1.0f; | |
float cameraPlaneDistance = Vector3.Dot(cameraPlaneNormal, transformCenter); | |
// Find the point on the plane | |
Vector3 pointOnPlane = Vector3.zero; | |
Vector3 sampledPoint = Vector3.zero; | |
// These are needed to detect the edge cases in which the method proposed falls appart | |
float planeCameraDifference = Vector3.Dot(constraintPlaneNormal, Camera.main.transform.forward * -1.0f); | |
float planeMouseDifference = Vector3.Dot(constraintPlaneNormal, screenRay.direction.normalized); | |
// Edge case 1: The plane and mouse ray are perpendicular | |
// Edge case 2: The plane is perpendicular to the camera | |
if (Mathf.Abs(planeCameraDifference) < 0.01f || Mathf.Abs(planeMouseDifference) < 0.01f) { | |
// Find the closest point on the camera plane | |
if (!RaycastPlaneIgnoringRayDirection(cameraPlaneNormal, cameraPlaneDistance, screenRay.origin, screenRay.direction, out pointOnPlane)) { | |
Debug.LogError("This isn't supposed to happen! Should be an assert, but Unity."); | |
} | |
// Find the closest point on the constraint plane to that point on the camera plane | |
// This gives us a point on the axis, just not "bubbled" out | |
float distance = Vector3.Dot(constraintPlaneNormal, pointOnPlane) - constraintPlaneDifference; | |
pointOnPlane = pointOnPlane - constraintPlaneNormal * distance; | |
// Make sure sampled point is always at the edge of the sphere | |
// Need to do a hemi-map for this | |
float cSq = transformRadius * transformRadius; | |
float aSq = (transformCenter - pointOnPlane).sqrMagnitude; | |
if (aSq > cSq) aSq = cSq; | |
float bSq = cSq - aSq; | |
sampledPoint = transformCenter + (pointOnPlane - transformCenter).normalized * Mathf.Sqrt(aSq) + (Camera.main.transform.forward * -1.0f * Mathf.Sqrt(bSq)); | |
} | |
// Normal case | |
else { | |
// Find the closest point on the constraint plane | |
if (!RaycastPlaneIgnoringRayDirection(constraintPlaneNormal, constraintPlaneDifference, screenRay.origin, screenRay.direction, out pointOnPlane)) { | |
Debug.LogError("This isn't supposed to happen! Should be an assert, but Unity."); | |
} | |
// Make sure sampled point is always at the edge of the sphere | |
sampledPoint = transformCenter + (pointOnPlane - transformCenter).normalized * transformRadius; | |
} | |
// For debug purposes, let's see where the mouse should be! | |
Debug.DrawLine(transformCenter, pointOnPlane, Color.cyan); | |
if (!isRunning) { | |
if (IsRotationActive) { | |
if (RaycastSphere(transformCenter, transformRadius, screenRay.origin, screenRay.direction, out startHitPosition)) { | |
startHitPosition = sampledPoint; | |
startRotation = constraintGlobal ? transform.rotation : transform.rotation; | |
isRunning = true; | |
DebugVisible = true; | |
} | |
} | |
} | |
else { | |
if (!IsRotationActive) { | |
DebugVisible = false; | |
} | |
else { | |
Vector3 currentHitPosition = sampledPoint; | |
// Construct the axis of rotation. In order to work in world space, all vectors should be relative | |
// to arcball position! (I'm not sure if the axis of rotation needs to be normalized or not). | |
Vector3 startVector = startHitPosition - transformCenter; | |
Vector3 currentVector = currentHitPosition - transformCenter; | |
Quaternion fromToRotation = Quaternion.FromToRotation(startVector, currentVector); | |
// Apply the rotation. | |
transform.rotation = fromToRotation * startRotation; | |
if (debugStartPoint != null) { | |
debugStartPoint.transform.position = startHitPosition; | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.transform.position = currentHitPosition; | |
} | |
} | |
} | |
} | |
bool DebugVisible { | |
set { | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(value); | |
if (value) { | |
debugStartPoint.transform.position = startHitPosition; | |
} | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(value); | |
if (value) { | |
debugCurrentPoint.transform.position = startHitPosition; | |
} | |
} | |
if (debugPlane != null) { | |
debugPlane.SetActive(value && debugShowPlane); | |
} | |
if (debugAxisLocal != null) { | |
debugAxisLocal.SetActive(value && !constraintGlobal); | |
} | |
if (debugAxisGlobal != null) { | |
debugAxisGlobal.SetActive(value && constraintGlobal); | |
} | |
if (debugAxisLocal_X != null) { | |
debugAxisLocal_X.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisLocal_Y != null) { | |
debugAxisLocal_Y.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisLocal_Z != null) { | |
debugAxisLocal_Z.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
if (debugAxisGlobal_X != null) { | |
debugAxisGlobal_X.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisGlobal_Y != null) { | |
debugAxisGlobal_Y.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisGlobal_Z != null) { | |
debugAxisGlobal_Z.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
} | |
} | |
bool IsRotationActive { | |
get { | |
if (Input.GetKey(KeyCode.Z) || Input.GetKey(KeyCode.X) || Input.GetKey(KeyCode.Y)) { | |
return true; | |
} | |
return false; | |
} | |
} | |
protected Vector3 _constraint = Vector3.zero; | |
public Vector3 Constraint { | |
get { | |
if (Input.GetKeyDown(KeyCode.X)) { | |
_constraint = constraintGlobal ? Camera.main.transform.up : transform.up; | |
} | |
if (Input.GetKeyDown(KeyCode.Y)) { | |
_constraint = constraintGlobal ? Camera.main.transform.right : transform.right; | |
} | |
if (Input.GetKeyDown(KeyCode.Z)) { | |
_constraint = constraintGlobal ? Camera.main.transform.forward : transform.forward; | |
} | |
if (Input.GetKeyUp(KeyCode.X) || Input.GetKeyUp(KeyCode.Y) || Input.GetKeyUp(KeyCode.Z)) { | |
_constraint = Vector3.zero; | |
} | |
if (debugPlane != null && IsRotationActive) { | |
debugPlane.transform.localRotation = Quaternion.LookRotation(_constraint, Vector3.up); | |
} | |
return _constraint; | |
} | |
} | |
protected bool RaycastPlaneIgnoringRayDirection(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
// Plane normal should not matter for this one!!! | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
//if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
//} | |
//return false; | |
} | |
protected bool RaycastSphere(Vector3 spherePosition, float sphereRadius, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
Vector3 e = spherePosition - rayOrigin; | |
float rSq = sphereRadius * sphereRadius; | |
float eSq = e.sqrMagnitude; | |
float a = Vector3.Dot(e, rayDirection); | |
float bSq = eSq - (a * a); | |
float f = Mathf.Sqrt(Mathf.Abs((rSq) - bSq)); | |
float t = a - f; | |
if (rSq - (eSq - a * a) < 0.0f) { // No Collision | |
return false; | |
} | |
else if (eSq < rSq) { // Ray starts inside the sphere | |
t = a + f;// Just reverse direction | |
} | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
} |
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 UnityEngine; | |
public class RotatorV6 : MonoBehaviour { | |
public Vector3 arcballPosition = Vector3.zero; | |
public float arcballRadius = 1.0f; | |
public GameObject debugStartPoint = null; | |
public GameObject debugCurrentPoint = null; | |
public GameObject debugArcBall = null; | |
public GameObject debugPlane = null; | |
public GameObject debugAxisLocal = null; | |
public GameObject debugAxisGlobal = null; | |
public GameObject debugAxisLocal_X = null; | |
public GameObject debugAxisGlobal_X = null; | |
public GameObject debugAxisLocal_Y = null; | |
public GameObject debugAxisGlobal_Y = null; | |
public GameObject debugAxisLocal_Z = null; | |
public GameObject debugAxisGlobal_Z = null; | |
private Quaternion startRotation = Quaternion.identity; | |
private Vector3 startHitPosition = Vector3.zero; | |
public bool constraintGlobal = true; | |
public bool debugShowPlane = false; | |
private bool isRunning = false; | |
//private Vector3 referenceUp; | |
private Vector3 referenceRight; | |
//private Vector3 referenceForward; | |
void Awake() { | |
DebugVisible = false; | |
if (debugArcBall != null) { | |
arcballPosition = debugArcBall.transform.position; | |
arcballRadius = debugArcBall.transform.lossyScale.x * 0.5f; | |
} | |
//transform.rotation = Quaternion.identity; | |
} | |
void Update() { | |
// Don't do anything if there is no constraint! | |
if (Constraint == Vector3.zero) { | |
isRunning = false; | |
DebugVisible = false; | |
return; | |
} | |
// Get the mouse position & create a world space ray from it | |
Vector3 mousePos = Input.mousePosition; | |
Ray screenRay = Camera.main.ScreenPointToRay(mousePos); | |
// Construct the camera plane (a plane that faces the camera) | |
Vector3 cameraPlaneNormal = Camera.main.transform.forward * -1.0f; | |
float cameraPlaneDistance = Vector3.Dot(cameraPlaneNormal, arcballPosition); | |
// Find the point on the plane | |
Vector3 pointOnCameraPlane = Vector3.zero; | |
if (!RaycastPlane(cameraPlaneNormal, cameraPlaneDistance, screenRay.origin, screenRay.direction, out pointOnCameraPlane)) { | |
Debug.LogError("This isn't supposed to happen! Should be an assert, but Unity."); | |
} | |
Vector3 constraintAxisNormal = Constraint; | |
if (Mathf.Abs(Vector3.Dot(cameraPlaneNormal, constraintAxisNormal)) > 0.6f) { | |
constraintAxisNormal = referenceRight; | |
// TODO: Set left or right scalar! | |
} | |
float constraintAxisDistance = Vector3.Dot(constraintAxisNormal, arcballPosition); | |
Vector3 pointOnConstraintAxis = ClosestPoint(constraintAxisNormal, constraintAxisDistance, pointOnCameraPlane); | |
if (!isRunning) { | |
if (IsRotationActive) { | |
if (RaycastSphere(arcballPosition, arcballRadius, screenRay.origin, screenRay.direction, out startHitPosition)) { | |
startHitPosition = pointOnConstraintAxis; | |
startRotation = constraintGlobal ? transform.rotation : transform.rotation; | |
isRunning = true; | |
DebugVisible = true; | |
} | |
} | |
} | |
else { | |
if (!IsRotationActive) { | |
DebugVisible = false; | |
} | |
else { | |
Vector3 currentHitPosition = pointOnConstraintAxis; | |
Vector3 testAxis = Vector3.Cross(cameraPlaneNormal, constraintAxisNormal).normalized; | |
float distance = (startHitPosition - currentHitPosition).magnitude; | |
float rotations = distance / (4.0f * arcballRadius); | |
float angle = rotations * 6.28319f; | |
float scale = -1.0f; | |
if (Vector3.Dot((currentHitPosition - startHitPosition).normalized, testAxis) < 0.0f) { | |
scale *= -1.0f; | |
} | |
//scale = -1.0f * Vector3.Dot((currentHitPosition - startHitPosition).normalized, testAxis); | |
angle *= scale; | |
Debug.Log("Scale: " + scale + ", Angle: " + angle); | |
transform.rotation = Quaternion.AngleAxis(Mathf.Rad2Deg * angle, Constraint) * startRotation; | |
if (debugStartPoint != null) { | |
debugStartPoint.transform.position = startHitPosition; | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.transform.position = currentHitPosition; | |
} | |
} | |
} | |
} | |
bool DebugVisible { | |
set { | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(value); | |
if (value) { | |
debugStartPoint.transform.position = startHitPosition; | |
} | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(value); | |
if (value) { | |
debugCurrentPoint.transform.position = startHitPosition; | |
} | |
} | |
if (debugPlane != null) { | |
debugPlane.SetActive(value && debugShowPlane); | |
} | |
if (debugAxisLocal != null) { | |
debugAxisLocal.SetActive(value && !constraintGlobal); | |
} | |
if (debugAxisGlobal != null) { | |
debugAxisGlobal.SetActive(value && constraintGlobal); | |
} | |
if (debugAxisLocal_X != null) { | |
debugAxisLocal_X.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisLocal_Y != null) { | |
debugAxisLocal_Y.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisLocal_Z != null) { | |
debugAxisLocal_Z.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
if (debugAxisGlobal_X != null) { | |
debugAxisGlobal_X.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisGlobal_Y != null) { | |
debugAxisGlobal_Y.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisGlobal_Z != null) { | |
debugAxisGlobal_Z.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
} | |
} | |
bool IsRotationActive { | |
get { | |
if (Input.GetKey(KeyCode.Z) || Input.GetKey(KeyCode.X) || Input.GetKey(KeyCode.Y)) { | |
return true; | |
} | |
return false; | |
} | |
} | |
protected Vector3 _constraint = Vector3.zero; | |
public Vector3 Constraint { | |
get { | |
if (Input.GetKeyDown(KeyCode.X)) { | |
_constraint = constraintGlobal ? Camera.main.transform.up : transform.up; | |
} | |
if (Input.GetKeyDown(KeyCode.Y)) { | |
_constraint = constraintGlobal ? Camera.main.transform.right : transform.right; | |
} | |
if (Input.GetKeyDown(KeyCode.Z)) { | |
_constraint = constraintGlobal ? Camera.main.transform.forward : transform.forward; | |
} | |
if (Input.GetKeyUp(KeyCode.X) || Input.GetKeyUp(KeyCode.Y) || Input.GetKeyUp(KeyCode.Z)) { | |
_constraint = Vector3.zero; | |
} | |
if (debugPlane != null && IsRotationActive) { | |
debugPlane.transform.localRotation = Quaternion.LookRotation(_constraint, Vector3.up); | |
} | |
if (Input.GetKeyDown(KeyCode.X) || Input.GetKeyDown(KeyCode.Y) || Input.GetKeyDown(KeyCode.Z)) { | |
if (constraintGlobal) { | |
//referenceUp = Camera.main.transform.up; | |
referenceRight = Camera.main.transform.right; | |
//referenceForward = Camera.main.transform.forward; | |
} | |
else { | |
//referenceUp = transform.up; | |
referenceRight = transform.right; | |
//referenceForward = transform.forward; | |
} | |
} | |
//referenceUp = Camera.main.transform.up; | |
referenceRight = Camera.main.transform.right; | |
//referenceForward = Camera.main.transform.forward; | |
return _constraint; | |
} | |
} | |
Vector3 ClosestPoint(Vector3 planeNormal, float planeDistance, Vector3 point) { | |
planeNormal = planeNormal.normalized; | |
float distance = Vector3.Dot(planeNormal, point) - planeDistance; | |
return point - planeNormal * distance; | |
} | |
protected bool RaycastPlane(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
// Plane normal should not matter for this one!!! | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
return false; | |
} | |
protected bool RaycastSphere(Vector3 spherePosition, float sphereRadius, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
Vector3 e = spherePosition - rayOrigin; | |
float rSq = sphereRadius * sphereRadius; | |
float eSq = e.sqrMagnitude; | |
float a = Vector3.Dot(e, rayDirection); | |
float bSq = eSq - (a * a); | |
float f = Mathf.Sqrt(Mathf.Abs((rSq) - bSq)); | |
float t = a - f; | |
if (rSq - (eSq - a * a) < 0.0f) { // No Collision | |
return false; | |
} | |
else if (eSq < rSq) { // Ray starts inside the sphere | |
t = a + f;// Just reverse direction | |
} | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
} |
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 UnityEngine; | |
public class RotatorV7 : MonoBehaviour { | |
private Quaternion startRotation = Quaternion.identity; | |
private Vector3 startPointOnPlane = Vector3.zero; | |
Vector3 startPointOnCameraPlane = Vector3.zero; | |
public Vector3 arcballCenter = new Vector3(0, 0, 0); | |
public float arcballRadius = 1.0f; | |
public GameObject debugStartPoint = null; | |
public GameObject debugCurrentPoint = null; | |
public GameObject debugArcBall = null; | |
public GameObject debugPlane = null; | |
public GameObject debugConstraintPlane = null; | |
public GameObject debugAxisLocal = null; | |
public GameObject debugAxisGlobal = null; | |
public GameObject debugAxisLocal_X = null; | |
public GameObject debugAxisGlobal_X = null; | |
public GameObject debugAxisLocal_Y = null; | |
public GameObject debugAxisGlobal_Y = null; | |
public GameObject debugAxisLocal_Z = null; | |
public GameObject debugAxisGlobal_Z = null; | |
public bool constraintGlobal = true; | |
public bool debugShowPlane = false; | |
private bool isRunning = false; | |
void Awake() { | |
DebugVisible = false; | |
if (debugArcBall != null) { | |
arcballCenter = debugArcBall.transform.position; | |
arcballRadius = debugArcBall.transform.lossyScale.x * 0.5f; | |
} | |
transform.rotation = Quaternion.identity; | |
} | |
void Update() { | |
// Potentially set the constraint axis | |
Vector3 constraint = Constraint; | |
// Don't do anything if there is no constraint! | |
if (constraint == Vector3.zero) { | |
isRunning = false; | |
DebugVisible = false; | |
return; | |
} | |
// Get the mouse position & create a world space ray from it | |
Vector3 mousePos = Input.mousePosition; | |
Ray screenRay = Camera.main.ScreenPointToRay(mousePos); | |
// Construct a plane that has the normal facing the camera and always bisects the arcball sphere | |
Vector3 cameraPlaneNormal = Camera.main.transform.forward * -1.0f; | |
float cameraPlaneDistance = Vector3.Dot(cameraPlaneNormal, arcballCenter); | |
// Construct a plane out of the constrain axis | |
Vector3 constraintPlaneNormal = constraint.normalized; | |
float constraintPlaneDistance = Vector3.Dot(constraintPlaneNormal, arcballCenter); | |
// Find where the world space ray intersects the plane that was created | |
Vector3 pointOnPlane = Vector3.zero; | |
RaycastPlane(cameraPlaneNormal, cameraPlaneDistance, screenRay.origin, screenRay.direction, out pointOnPlane); | |
Vector3 pointOnCameraPlane = pointOnPlane; | |
// Find the closest point on the constraint plane to that point on the camera plane | |
// This gives us a point on the axis, just not "bubbled" out | |
Ray closestPointRay = new Ray(); | |
closestPointRay.origin = pointOnPlane; | |
if (Mathf.Abs(Vector3.Dot(constraintPlaneNormal, cameraPlaneNormal)) > 0.9f) { // EDGE CASE | |
closestPointRay.direction = Vector3.Cross(Vector3.Cross(cameraPlaneNormal, Camera.main.transform.right), cameraPlaneNormal); | |
RaycastPlaneIgnoringRayDirection(Camera.main.transform.right, Vector3.Dot(Camera.main.transform.right, arcballCenter), closestPointRay.origin, closestPointRay.direction, out pointOnPlane); | |
} | |
else { // NORMAL CASE | |
closestPointRay.direction = Vector3.Cross(Vector3.Cross(cameraPlaneNormal, constraintPlaneNormal), cameraPlaneNormal); | |
RaycastPlaneIgnoringRayDirection(constraintPlaneNormal, constraintPlaneDistance, closestPointRay.origin, closestPointRay.direction, out pointOnPlane); | |
} | |
if (!isRunning) { | |
if (IsRotationActive) { | |
if (RaycastSphere(arcballCenter, arcballRadius, screenRay.origin, screenRay.direction, out startPointOnPlane)) { | |
startRotation = transform.rotation; | |
startPointOnPlane = pointOnPlane; | |
startPointOnCameraPlane = pointOnCameraPlane; | |
isRunning = true; | |
DebugVisible = true; | |
} | |
} | |
} | |
else { | |
if (!IsRotationActive) { | |
DebugVisible = false; | |
} | |
else { | |
Vector3 currentPointOnPlane = pointOnPlane; | |
// Find how far on the plane the user has dragged the mouse. | |
// Every 1 radian of distance = 1/4 of a turn. So 4 radians of distance = 1 turn | |
// This of course will need to be converted to radians. 1 full turn = 6.28 radians | |
float totalDistance = (startPointOnPlane - currentPointOnPlane).magnitude; | |
float numTurns = totalDistance / (4f * arcballRadius); // Every 4R of distance = 1 full rotation! Unit is currently in number of rotations. | |
float angle = numTurns * 6.28319f; // convert rotations to radians. One full rotation = 6.28 radians. | |
// We now know the angle of rotation. Remember the formula for quaternion constructs angle x 2, so we need to half this. | |
float hCos = Mathf.Cos(angle * 0.5f); | |
float hSin = Mathf.Sin(angle * 0.5f); | |
// Construct the axis of rotation. | |
Vector3 axis = Constraint; | |
// Normal case, flip axis when doubling over self | |
if (Mathf.Abs(Vector3.Dot(constraintPlaneNormal, cameraPlaneNormal)) > 0.9f) { | |
// This code is duplicated from below. Not sure how to fix it proper :( | |
Vector3 testPoint = (currentPointOnPlane - startPointOnPlane).normalized; | |
float dot = Vector3.Dot(testPoint, Vector3.Cross(Camera.main.transform.right, cameraPlaneNormal)); | |
Debug.Log("dot: " + dot); | |
if (dot < 0.0f) { | |
axis *= -1.0f; | |
} | |
dot = Vector3.Dot((startPointOnCameraPlane - arcballCenter).normalized, Camera.main.transform.right); | |
if (dot < 0f) { | |
Debug.Log("Edge flip"); | |
axis *= -1.0f; | |
} | |
} | |
else { | |
Vector3 testPoint = (currentPointOnPlane - startPointOnPlane).normalized; | |
float dot = Vector3.Dot(testPoint, Vector3.Cross(constraintPlaneNormal, cameraPlaneNormal)); | |
Debug.Log("dot: " + dot); | |
if (dot < 0.0f) { | |
axis *= -1.0f; | |
} | |
} | |
// Create the actual quaternion from the above values. This isn't a from-to rotation per-say. We still use the same vectors to figure out the | |
// angle of orientation, but the actual angle comes from distance of mouse movement. | |
Quaternion fromToRotation = Quaternion.identity; | |
fromToRotation.w = hCos; | |
fromToRotation.x = hSin * axis.x; | |
fromToRotation.y = hSin * axis.y; | |
fromToRotation.z = hSin * axis.z; | |
fromToRotation = NormalizeQuaternion(fromToRotation); | |
// Apply the rotation. | |
transform.rotation = fromToRotation * startRotation; | |
if (debugStartPoint != null) { | |
debugStartPoint.transform.position = startPointOnPlane; | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.transform.position = currentPointOnPlane; | |
} | |
} | |
} | |
} | |
protected Quaternion NormalizeQuaternion(Quaternion quat) { | |
float len = quat.x * quat.x + quat.y * quat.y + quat.z * quat.z + quat.w * quat.w; | |
if (len != 0.0f) { | |
len = Mathf.Sqrt(len); | |
quat.x /= len; | |
quat.y /= len; | |
quat.z /= len; | |
quat.w /= len; | |
} | |
return quat; | |
} | |
bool DebugVisible { | |
set { | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(value); | |
if (value) { | |
debugStartPoint.transform.position = startPointOnPlane; | |
} | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(value); | |
if (value) { | |
debugCurrentPoint.transform.position = startPointOnPlane; | |
} | |
} | |
if (debugPlane != null) { | |
debugPlane.SetActive(value && debugShowPlane); | |
debugPlane.transform.position = transform.position; | |
} | |
if (debugConstraintPlane != null) { | |
debugConstraintPlane.SetActive(value && debugShowPlane); | |
debugConstraintPlane.transform.position = transform.position; | |
} | |
if (debugAxisLocal != null) { | |
debugAxisLocal.SetActive(value && !constraintGlobal); | |
} | |
if (debugAxisGlobal != null) { | |
debugAxisGlobal.SetActive(value && constraintGlobal); | |
} | |
if (debugAxisLocal_X != null) { | |
debugAxisLocal_X.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisLocal_Y != null) { | |
debugAxisLocal_Y.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisLocal_Z != null) { | |
debugAxisLocal_Z.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
if (debugAxisGlobal_X != null) { | |
debugAxisGlobal_X.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisGlobal_Y != null) { | |
debugAxisGlobal_Y.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisGlobal_Z != null) { | |
debugAxisGlobal_Z.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
} | |
} | |
bool IsRotationActive { | |
get { | |
if (Input.GetKey(KeyCode.Z) || Input.GetKey(KeyCode.X) || Input.GetKey(KeyCode.Y)) { | |
return true; | |
} | |
return false; | |
} | |
} | |
protected Vector3 _constraint = Vector3.zero; | |
public Vector3 Constraint { | |
get { | |
if (Input.GetKeyDown(KeyCode.X)) { | |
_constraint = constraintGlobal ? Camera.main.transform.up : transform.up; | |
} | |
if (Input.GetKeyDown(KeyCode.Y)) { | |
_constraint = constraintGlobal ? Camera.main.transform.right : transform.right; | |
} | |
if (Input.GetKeyDown(KeyCode.Z)) { | |
_constraint = constraintGlobal ? Camera.main.transform.forward : transform.forward; | |
} | |
if (Input.GetKeyUp(KeyCode.X) || Input.GetKeyUp(KeyCode.Y) || Input.GetKeyUp(KeyCode.Z)) { | |
_constraint = Vector3.zero; | |
} | |
if (debugPlane != null && IsRotationActive) { | |
debugPlane.transform.localRotation = Quaternion.LookRotation(_constraint, Vector3.up); | |
} | |
if (debugConstraintPlane != null && IsRotationActive) { | |
debugConstraintPlane.transform.localRotation = Quaternion.LookRotation(Camera.main.transform.forward * -1.0f, Vector3.up); | |
} | |
return _constraint; | |
} | |
} | |
protected bool RaycastPlane(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
return false; | |
} | |
protected bool RaycastPlaneIgnoringRayDirection(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
// Plane normal should not matter for this one!!! | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
//if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
//} | |
//return false; | |
} | |
protected bool RaycastSphere(Vector3 spherePosition, float sphereRadius, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
Vector3 e = spherePosition - rayOrigin; | |
float rSq = sphereRadius * sphereRadius; | |
float eSq = e.sqrMagnitude; | |
float a = Vector3.Dot(e, rayDirection); | |
float bSq = eSq - (a * a); | |
float f = Mathf.Sqrt(Mathf.Abs((rSq) - bSq)); | |
float t = a - f; | |
if (rSq - (eSq - a * a) < 0.0f) { // No Collision | |
return false; | |
} | |
else if (eSq < rSq) { // Ray starts inside the sphere | |
t = a + f;// Just reverse direction | |
} | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
} |
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 UnityEngine; | |
public class RotatorV8 : MonoBehaviour { | |
public Vector3 arcballPosition = new Vector3(0, 0, 0); | |
public float arcballRadius = 1.0f; | |
private Quaternion startRotation = Quaternion.identity; | |
private Vector3 startHitPosition = Vector3.zero; | |
private Vector3 startPointOnPlane = Vector3.zero; | |
protected Vector3 startPointOnCameraPlane = Vector3.zero; | |
public bool constraintGlobal = true; | |
public bool debugShowPlane = false; | |
private bool isRunning = false; | |
public GameObject debugStartPoint = null; | |
public GameObject debugCurrentPoint = null; | |
public GameObject debugArcBall = null; | |
public GameObject debugPlane = null; | |
public GameObject debugConstraintPlane = null; | |
public GameObject debugAxisLocal = null; | |
public GameObject debugAxisGlobal = null; | |
public GameObject debugAxisLocal_X = null; | |
public GameObject debugAxisGlobal_X = null; | |
public GameObject debugAxisLocal_Y = null; | |
public GameObject debugAxisGlobal_Y = null; | |
public GameObject debugAxisLocal_Z = null; | |
public GameObject debugAxisGlobal_Z = null; | |
void Awake() { | |
DebugVisible = false; | |
if (debugArcBall != null) { | |
arcballPosition = debugArcBall.transform.position; | |
arcballRadius = debugArcBall.transform.lossyScale.x * 0.5f; | |
} | |
// transform.rotation = Quaternion.identity; | |
} | |
void Update() { | |
// Potentially set the constraint axis | |
Vector3 constraint = Constraint; | |
// Don't do anything if there is no constraint! | |
if (constraint == Vector3.zero) { | |
isRunning = false; | |
DebugVisible = false; | |
return; | |
} | |
// Get the mouse position & create a world space ray from it | |
Vector3 mousePos = Input.mousePosition; | |
Ray screenRay = Camera.main.ScreenPointToRay(mousePos); | |
// Construct a plane that has the normal facing the camera and always bisects the arcball sphere | |
Vector3 cameraPlaneNormal = Camera.main.transform.forward * -1.0f; | |
float cameraPlaneDistance = Vector3.Dot(cameraPlaneNormal, arcballPosition); | |
// Find where the world space ray intersects the plane that was created | |
Vector3 pointOnPlane = Vector3.zero; | |
RaycastPlane(cameraPlaneNormal, cameraPlaneDistance, screenRay.origin, screenRay.direction, out pointOnPlane); | |
Vector3 pointOnCameraPlane = pointOnPlane; | |
// Construct a plane out of the constrain axis | |
Vector3 constraintPlaneNormal = constraint.normalized; | |
float constraintPlaneDistance = Vector3.Dot(constraintPlaneNormal, arcballPosition); | |
Ray closestPointRay = new Ray(); | |
closestPointRay.origin = pointOnPlane; | |
if (Mathf.Abs(Vector3.Dot(constraintPlaneNormal, cameraPlaneNormal)) > 0.9f) { // EDGE CASE | |
closestPointRay.direction = Vector3.Cross(Vector3.Cross(cameraPlaneNormal, Camera.main.transform.right), cameraPlaneNormal); | |
RaycastPlaneIgnoringRayDirection(Camera.main.transform.right, Vector3.Dot(Camera.main.transform.right, arcballPosition), closestPointRay.origin, closestPointRay.direction, out pointOnPlane); | |
} | |
else { // NORMAL CASE | |
closestPointRay.direction = Vector3.Cross(Vector3.Cross(cameraPlaneNormal, constraintPlaneNormal), cameraPlaneNormal); | |
RaycastPlaneIgnoringRayDirection(constraintPlaneNormal, constraintPlaneDistance, closestPointRay.origin, closestPointRay.direction, out pointOnPlane); | |
} | |
// Using some trig, project the point on the plane onto the hemisphere of the arcball. | |
// A^2 + B^2 = C^2 | |
// C^2 = Radius^2 | |
// A^2 = distance of point on plane from arcball ^2 | |
// Clamp A^2 to C^2. That way the mouse can never trully leave the sphere | |
float cSq = arcballRadius * arcballRadius; | |
float aSq = (arcballPosition - pointOnPlane).sqrMagnitude; | |
if (aSq > cSq) aSq = cSq; | |
float bSq = cSq - aSq; | |
// SampledPoint holds where on the hemisphere the point on plane is. Remember, it's clamped to the edge of the sphere! | |
Vector3 mapDirection; | |
if (Mathf.Abs(Vector3.Dot(constraintPlaneNormal, cameraPlaneNormal)) > 0.9f) { // EDGE CASE | |
mapDirection = Vector3.Cross(constraintPlaneNormal, Vector3.Cross(cameraPlaneNormal, Camera.main.transform.right)); | |
} | |
else { // NORMAL CASE | |
mapDirection = Vector3.Cross(constraintPlaneNormal, Vector3.Cross(cameraPlaneNormal, constraintPlaneNormal)); | |
} | |
Vector3 sampledPoint = arcballPosition + (pointOnPlane - arcballPosition).normalized * Mathf.Sqrt(aSq) + (mapDirection * Mathf.Sqrt(bSq)); | |
sampledPoint = arcballPosition + (sampledPoint - arcballPosition).normalized * arcballRadius; | |
if (!isRunning) { | |
if (IsRotationActive) { | |
if (RaycastSphere(arcballPosition, arcballRadius, screenRay.origin, screenRay.direction, out startHitPosition)) { | |
startHitPosition = sampledPoint; | |
startRotation = transform.rotation; | |
startPointOnPlane = pointOnPlane; // Will be used for distance calculation | |
startPointOnCameraPlane = pointOnCameraPlane; | |
isRunning = true; | |
DebugVisible = true; | |
} | |
} | |
} | |
else { | |
if (!IsRotationActive) { | |
DebugVisible = false; | |
} | |
else { | |
Vector3 currentPointOnPlane = pointOnPlane; | |
Vector3 currentHitPosition = sampledPoint; | |
// Find how far on the plane the user has dragged the mouse. | |
// Every 1 radian of distance = 1/4 of a turn. So 4 radians of distance = 1 turn | |
// This of course will need to be converted to radians. 1 full turn = 6.28 radians | |
float totalDistance = (startPointOnPlane - currentPointOnPlane).magnitude; | |
float numTurns = totalDistance / (4f * arcballRadius); // Every 4R of distance = 1 full rotation! Unit is currently in number of rotations. | |
float angle = numTurns * 6.28319f; // convert rotations to radians. One full rotation = 6.28 radians. | |
// We now know the angle of rotation. Remember the formula for quaternion constructs angle x 2, so we need to half this. | |
float hCos = Mathf.Cos(angle * 0.5f); | |
float hSin = Mathf.Sin(angle * 0.5f); | |
// Construct the axis of rotation. In order to work in world space, all vectors should be relative | |
// to arcball position! (I'm not sure if the axis of rotation needs to be normalized or not). | |
Vector3 startVector = startHitPosition - arcballPosition; | |
Vector3 currentVector = currentHitPosition - arcballPosition; | |
Vector3 axis = Vector3.Cross(startVector, currentVector).normalized; | |
if (Mathf.Abs(Vector3.Dot(constraintPlaneNormal, cameraPlaneNormal)) > 0.9f) { | |
float dot = Vector3.Dot((startPointOnCameraPlane - arcballPosition).normalized, Camera.main.transform.right); | |
if (dot < 0f) { | |
Debug.Log("Edge flip"); | |
axis *= -1.0f; | |
} | |
} | |
// Create the actual quaternion from the above values. This isn't a from-to rotation per-say. We still use the same vectors to figure out the | |
// angle of orientation, but the actual angle comes from distance of mouse movement. | |
Quaternion fromToRotation = Quaternion.identity; | |
fromToRotation.w = hCos; | |
fromToRotation.x = hSin * axis.x; | |
fromToRotation.y = hSin * axis.y; | |
fromToRotation.z = hSin * axis.z; | |
fromToRotation = NormalizeQuaternion(fromToRotation); | |
// Apply the rotation. | |
transform.rotation = fromToRotation * startRotation; | |
if (debugStartPoint != null) { | |
debugStartPoint.transform.position = startPointOnPlane; | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.transform.position = currentHitPosition; | |
} | |
} | |
} | |
} | |
protected Quaternion NormalizeQuaternion(Quaternion quat) { | |
float len = quat.x * quat.x + quat.y * quat.y + quat.z * quat.z + quat.w * quat.w; | |
if (len != 0.0f) { | |
len = Mathf.Sqrt(len); | |
quat.x /= len; | |
quat.y /= len; | |
quat.z /= len; | |
quat.w /= len; | |
} | |
return quat; | |
} | |
bool DebugVisible { | |
set { | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(value); | |
if (value) { | |
debugStartPoint.transform.position = startPointOnPlane; | |
} | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(value); | |
if (value) { | |
debugCurrentPoint.transform.position = startPointOnPlane; | |
} | |
} | |
if (debugPlane != null) { | |
debugPlane.SetActive(value && debugShowPlane); | |
debugPlane.transform.position = transform.position; | |
} | |
if (debugConstraintPlane != null) { | |
debugConstraintPlane.SetActive(value && debugShowPlane); | |
debugConstraintPlane.transform.position = transform.position; | |
} | |
if (debugAxisLocal != null) { | |
debugAxisLocal.SetActive(value && !constraintGlobal); | |
} | |
if (debugAxisGlobal != null) { | |
debugAxisGlobal.SetActive(value && constraintGlobal); | |
} | |
if (debugAxisLocal_X != null) { | |
debugAxisLocal_X.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisLocal_Y != null) { | |
debugAxisLocal_Y.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisLocal_Z != null) { | |
debugAxisLocal_Z.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
if (debugAxisGlobal_X != null) { | |
debugAxisGlobal_X.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisGlobal_Y != null) { | |
debugAxisGlobal_Y.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisGlobal_Z != null) { | |
debugAxisGlobal_Z.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
} | |
} | |
bool IsRotationActive { | |
get { | |
if (Input.GetKey(KeyCode.Z) || Input.GetKey(KeyCode.X) || Input.GetKey(KeyCode.Y)) { | |
return true; | |
} | |
return false; | |
} | |
} | |
protected Vector3 _constraint = Vector3.zero; | |
public Vector3 Constraint { | |
get { | |
if (Input.GetKeyDown(KeyCode.X)) { | |
_constraint = constraintGlobal ? Camera.main.transform.up : transform.up; | |
} | |
if (Input.GetKeyDown(KeyCode.Y)) { | |
_constraint = constraintGlobal ? Camera.main.transform.right : transform.right; | |
} | |
if (Input.GetKeyDown(KeyCode.Z)) { | |
_constraint = constraintGlobal ? Camera.main.transform.forward : transform.forward; | |
} | |
if (Input.GetKeyUp(KeyCode.X) || Input.GetKeyUp(KeyCode.Y) || Input.GetKeyUp(KeyCode.Z)) { | |
_constraint = Vector3.zero; | |
} | |
if (debugPlane != null && IsRotationActive) { | |
debugPlane.transform.localRotation = Quaternion.LookRotation(_constraint, Vector3.up); | |
} | |
if (debugConstraintPlane != null && IsRotationActive) { | |
debugConstraintPlane.transform.localRotation = Quaternion.LookRotation(Camera.main.transform.forward * -1.0f, Vector3.up); | |
} | |
return _constraint; | |
} | |
} | |
protected bool RaycastPlane(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
return false; | |
} | |
protected bool RaycastPlaneIgnoringRayDirection(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
// Plane normal should not matter for this one!!! | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
//if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
//} | |
//return false; | |
} | |
protected bool RaycastSphere(Vector3 spherePosition, float sphereRadius, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
Vector3 e = spherePosition - rayOrigin; | |
float rSq = sphereRadius * sphereRadius; | |
float eSq = e.sqrMagnitude; | |
float a = Vector3.Dot(e, rayDirection); | |
float bSq = eSq - (a * a); | |
float f = Mathf.Sqrt(Mathf.Abs((rSq) - bSq)); | |
float t = a - f; | |
if (rSq - (eSq - a * a) < 0.0f) { // No Collision | |
return false; | |
} | |
else if (eSq < rSq) { // Ray starts inside the sphere | |
t = a + f;// Just reverse direction | |
} | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
} |
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 UnityEngine; | |
public class RotatorV9 : MonoBehaviour { | |
public Vector3 arcballPosition = new Vector3(0, 0, 0); | |
public float arcballRadius = 1.0f; | |
private Quaternion startRotation = Quaternion.identity; | |
private Vector3 startHitPosition = Vector3.zero; | |
private Vector3 startPointOnPlane = Vector3.zero; | |
protected Vector3 startPointOnCameraPlane = Vector3.zero; | |
public bool constraintGlobal = true; | |
public bool debugShowPlane = false; | |
private bool isRunning = false; | |
public GameObject debugStartPoint = null; | |
public GameObject debugCurrentPoint = null; | |
public GameObject debugArcBall = null; | |
public GameObject debugPlane = null; | |
public GameObject debugConstraintPlane = null; | |
public GameObject debugAxisLocal = null; | |
public GameObject debugAxisGlobal = null; | |
public GameObject debugAxisLocal_X = null; | |
public GameObject debugAxisGlobal_X = null; | |
public GameObject debugAxisLocal_Y = null; | |
public GameObject debugAxisGlobal_Y = null; | |
public GameObject debugAxisLocal_Z = null; | |
public GameObject debugAxisGlobal_Z = null; | |
void Awake() { | |
DebugVisible = false; | |
if (debugArcBall != null) { | |
arcballPosition = debugArcBall.transform.position; | |
arcballRadius = debugArcBall.transform.lossyScale.x * 0.5f; | |
} | |
// transform.rotation = Quaternion.identity; | |
} | |
void Update() { | |
// Potentially set the constraint axis | |
Vector3 constraint = Constraint; | |
// Don't do anything if there is no constraint! | |
if (constraint == Vector3.zero) { | |
isRunning = false; | |
DebugVisible = false; | |
return; | |
} | |
// Get the mouse position & create a world space ray from it | |
Vector3 mousePos = Input.mousePosition; | |
Ray screenRay = Camera.main.ScreenPointToRay(mousePos); | |
// Construct a plane that has the normal facing the camera and always bisects the arcball sphere | |
Vector3 cameraPlaneNormal = Camera.main.transform.forward * -1.0f; | |
float cameraPlaneDistance = Vector3.Dot(cameraPlaneNormal, arcballPosition); | |
// Find where the world space ray intersects the plane that was created | |
Vector3 pointOnPlane = Vector3.zero; | |
RaycastPlane(cameraPlaneNormal, cameraPlaneDistance, screenRay.origin, screenRay.direction, out pointOnPlane); | |
Vector3 pointOnCameraPlane = pointOnPlane; | |
// Using some trig, project the point on the plane onto the hemisphere of the arcball. | |
// A^2 + B^2 = C^2 | |
// C^2 = Radius^2 | |
// A^2 = distance of point on plane from arcball ^2 | |
// Clamp A^2 to C^2. That way the mouse can never trully leave the sphere | |
float cSq = arcballRadius * arcballRadius; | |
float aSq = (arcballPosition - pointOnPlane).sqrMagnitude; | |
if (aSq > cSq) aSq = cSq; | |
float bSq = cSq - aSq; | |
// SampledPoint holds where on the hemisphere the point on plane is. Remember, it's clamped to the edge of the sphere! | |
Vector3 mapDirection = cameraPlaneNormal; | |
Vector3 sampledPoint = arcballPosition + (pointOnPlane - arcballPosition).normalized * Mathf.Sqrt(aSq) + (mapDirection * Mathf.Sqrt(bSq)); | |
// Construct a plane out of the constrain axis | |
Vector3 constraintPlaneNormal = constraint.normalized; | |
float constraintPlaneDistance = Vector3.Dot(constraintPlaneNormal, arcballPosition); | |
Ray closestPointRay = new Ray(); | |
closestPointRay.origin = sampledPoint; | |
if (Mathf.Abs (Vector3.Dot (constraintPlaneNormal, cameraPlaneNormal)) < 0.9f) { // NON-EDGE CASE | |
closestPointRay.direction = Vector3.Cross (Vector3.Cross (cameraPlaneNormal, constraintPlaneNormal), cameraPlaneNormal); | |
RaycastPlaneIgnoringRayDirection (constraintPlaneNormal, constraintPlaneDistance, closestPointRay.origin, closestPointRay.direction, out sampledPoint); | |
sampledPoint = arcballPosition + (sampledPoint - arcballPosition).normalized * arcballRadius; | |
} else { // EDGE CASE | |
sampledPoint = arcballPosition + (pointOnPlane - arcballPosition).normalized * arcballRadius; | |
} | |
if (!isRunning) { | |
if (IsRotationActive) { | |
if (RaycastSphere(arcballPosition, arcballRadius, screenRay.origin, screenRay.direction, out startHitPosition)) { | |
startHitPosition = sampledPoint; | |
startRotation = transform.rotation; | |
startPointOnPlane = pointOnCameraPlane; // Will be used for distance calculation | |
startPointOnCameraPlane = pointOnCameraPlane; | |
isRunning = true; | |
DebugVisible = true; | |
} | |
} | |
} | |
else { | |
if (!IsRotationActive) { | |
DebugVisible = false; | |
} | |
else { | |
Vector3 currentPointOnPlane = pointOnCameraPlane; | |
Vector3 currentHitPosition = sampledPoint; | |
Vector3 startVector = startHitPosition - arcballPosition; | |
Vector3 currentVector = currentHitPosition - arcballPosition; | |
// Create the actual quaternion from the above values. This isn't a from-to rotation per-say. We still use the same vectors to figure out the | |
Quaternion fromToRotation = HalfQuaternionMethod (startVector, currentVector); | |
fromToRotation = NormalizeQuaternion(fromToRotation); | |
// Apply the rotation. | |
transform.rotation = fromToRotation * startRotation; | |
if (debugStartPoint != null) { | |
debugStartPoint.transform.position = startHitPosition; | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.transform.position = currentHitPosition; | |
} | |
} | |
} | |
} | |
protected Quaternion NormalizeQuaternion(Quaternion quat) { | |
float len = quat.x * quat.x + quat.y * quat.y + quat.z * quat.z + quat.w * quat.w; | |
if (len != 0.0f) { | |
len = Mathf.Sqrt(len); | |
quat.x /= len; | |
quat.y /= len; | |
quat.z /= len; | |
quat.w /= len; | |
} | |
return quat; | |
} | |
bool DebugVisible { | |
set { | |
if (debugStartPoint != null) { | |
debugStartPoint.SetActive(value); | |
if (value) { | |
debugStartPoint.transform.position = startPointOnPlane; | |
} | |
} | |
if (debugCurrentPoint != null) { | |
debugCurrentPoint.SetActive(value); | |
if (value) { | |
debugCurrentPoint.transform.position = startPointOnPlane; | |
} | |
} | |
if (debugPlane != null) { | |
debugPlane.SetActive(value && debugShowPlane); | |
debugPlane.transform.position = transform.position; | |
} | |
if (debugConstraintPlane != null) { | |
debugConstraintPlane.SetActive(value && debugShowPlane); | |
debugConstraintPlane.transform.position = transform.position; | |
} | |
if (debugAxisLocal != null) { | |
debugAxisLocal.SetActive(value && !constraintGlobal); | |
} | |
if (debugAxisGlobal != null) { | |
debugAxisGlobal.SetActive(value && constraintGlobal); | |
} | |
if (debugAxisLocal_X != null) { | |
debugAxisLocal_X.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisLocal_Y != null) { | |
debugAxisLocal_Y.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisLocal_Z != null) { | |
debugAxisLocal_Z.SetActive(value && !constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
if (debugAxisGlobal_X != null) { | |
debugAxisGlobal_X.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.X)); | |
} | |
if (debugAxisGlobal_Y != null) { | |
debugAxisGlobal_Y.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Y)); | |
} | |
if (debugAxisGlobal_Z != null) { | |
debugAxisGlobal_Z.SetActive(value && constraintGlobal && Input.GetKey(KeyCode.Z)); | |
} | |
} | |
} | |
// Quaternion.fromToRotation | |
protected Quaternion HalfQuaternionMethod(Vector3 v0, Vector3 v1) { | |
Quaternion result = Quaternion.identity; | |
Vector3 axis = Vector3.zero; | |
float k_cos_theta = Vector3.Dot(v0, v1); | |
float k = Mathf.Sqrt(v0.sqrMagnitude * v1.sqrMagnitude); | |
if (k_cos_theta / k == -1) { | |
result.w = 0.0f; | |
axis = OrthogonalAxis(v0).normalized; | |
} | |
else { | |
result.w = k_cos_theta + k; | |
axis = Vector3.Cross(v0, v1); | |
} | |
result.x = axis.x; | |
result.y = axis.y; | |
result.z = axis.z; | |
return NormalizeQuaternion(result); | |
} | |
protected Vector3 OrthogonalAxis(Vector3 v) { | |
float x = Mathf.Abs(v.x); | |
float y = Mathf.Abs(v.y); | |
float z = Mathf.Abs(v.z); | |
Vector3 other = Vector3.zero; | |
if (x < y) { | |
if (x < z) { | |
other = Vector3.right; // x axis | |
} | |
else { | |
other = Vector3.forward; // z axis | |
} | |
} | |
else { | |
if (y < z) { | |
other = Vector3.up; // y axis | |
} | |
else { | |
other = Vector3.forward; // z axis | |
} | |
} | |
return Vector3.Cross(v, other); | |
} | |
bool IsRotationActive { | |
get { | |
if (Input.GetKey(KeyCode.Z) || Input.GetKey(KeyCode.X) || Input.GetKey(KeyCode.Y)) { | |
return true; | |
} | |
return false; | |
} | |
} | |
protected Vector3 _constraint = Vector3.zero; | |
public Vector3 Constraint { | |
get { | |
if (Input.GetKeyDown(KeyCode.X)) { | |
_constraint = constraintGlobal ? Camera.main.transform.up : transform.up; | |
} | |
if (Input.GetKeyDown(KeyCode.Y)) { | |
_constraint = constraintGlobal ? Camera.main.transform.right : transform.right; | |
} | |
if (Input.GetKeyDown(KeyCode.Z)) { | |
_constraint = constraintGlobal ? Camera.main.transform.forward : transform.forward; | |
} | |
if (Input.GetKeyUp(KeyCode.X) || Input.GetKeyUp(KeyCode.Y) || Input.GetKeyUp(KeyCode.Z)) { | |
_constraint = Vector3.zero; | |
} | |
if (debugPlane != null && IsRotationActive) { | |
debugPlane.transform.localRotation = Quaternion.LookRotation(_constraint, Vector3.up); | |
} | |
if (debugConstraintPlane != null && IsRotationActive) { | |
debugConstraintPlane.transform.localRotation = Quaternion.LookRotation(Camera.main.transform.forward * -1.0f, Vector3.up); | |
} | |
return _constraint; | |
} | |
} | |
protected bool RaycastPlane(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
return false; | |
} | |
protected bool RaycastPlaneIgnoringRayDirection(Vector3 planeNormal, float planeDistance, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
planeNormal = planeNormal.normalized; | |
float nd = Vector3.Dot(rayDirection, planeNormal); | |
float pn = Vector3.Dot(rayOrigin, planeNormal); | |
// Plane normal should not matter for this one!!! | |
if (nd >= 0.0f) { // Wrong direction | |
//return false; | |
} | |
float t = (planeDistance - pn) / nd; | |
//if (t >= 0.0f) { | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
//} | |
//return false; | |
} | |
protected bool RaycastSphere(Vector3 spherePosition, float sphereRadius, Vector3 rayOrigin, Vector3 rayDirection, out Vector3 hitPosition) { | |
hitPosition = Vector3.zero; | |
rayDirection = rayDirection.normalized; | |
Vector3 e = spherePosition - rayOrigin; | |
float rSq = sphereRadius * sphereRadius; | |
float eSq = e.sqrMagnitude; | |
float a = Vector3.Dot(e, rayDirection); | |
float bSq = eSq - (a * a); | |
float f = Mathf.Sqrt(Mathf.Abs((rSq) - bSq)); | |
float t = a - f; | |
if (rSq - (eSq - a * a) < 0.0f) { // No Collision | |
return false; | |
} | |
else if (eSq < rSq) { // Ray starts inside the sphere | |
t = a + f;// Just reverse direction | |
} | |
hitPosition = rayOrigin + rayDirection * t; | |
return true; | |
} | |
} |
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
// This is a unity style (free) rotation gizmo. There is no easy way to constrain rotation on any axis (afaik) | |
// Rot-dir could be projected and re-normalized, but that looks like a major pain to do | |
// It's not a pixel perfect rotation sceme, i don't really enjoy it. | |
// Rotation is based on an acumulated delta of the mouse. Think of it more like a tredmill with an axis rather than an arc-ball | |
using UnityEngine; | |
public class Rotator : MonoBehaviour { | |
private Vector3 sartMousePos; | |
private Vector3 lastMousePos; | |
private bool isRunning = false; | |
void Update() { | |
Vector3 mousePos = Input.mousePosition; | |
if (!isRunning) { | |
if (Input.GetKeyDown(KeyCode.R)) { | |
isRunning = true; | |
sartMousePos = mousePos; | |
} | |
} | |
else { | |
Vector3 delta = mousePos - lastMousePos; | |
Vector3 rotDir = Camera.main.transform.TransformDirection(new Vector3(delta.y, -delta.x, 0)); | |
transform.rotation = Quaternion.AngleAxis(delta.magnitude, rotDir.normalized) * transform.rotation; | |
if (Input.GetKeyUp(KeyCode.R)) { | |
isRunning = false; | |
} | |
} | |
lastMousePos = mousePos; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment