I’ve been wracking my brain on this one for a few days. I have the need to have a script where I can specify a list of rotations and I wanted to draw rays from the object to point out from the object in each configured rotation. However, the rays are not always correct and when I rotate the object they become warped when I need them to properly rotate along with it.
Here is a simple script that contains the problem:
using UnityEngine;
public class TestingGizmos : MonoBehaviour {
[System.Serializable]
public class RollResultRotation {
[SerializeField] protected Vector3 _rotation = Vector3.zero;
public Vector3 Rotation { get => _rotation; }
}
public Transform targetTransform = null;
public RollResultRotation[] rotationResults = new RollResultRotation[] { };
private void OnDrawGizmosSelected() {
if (targetTransform == null) { return; }
Vector3 pos = targetTransform.position;
Gizmos.color = Color.red;
for (int x = 0; x < rotationResults.Length; x++) {
Gizmos.DrawRay(pos, targetTransform.localRotation * Quaternion.Euler(rotationResults[x].Rotation) * targetTransform.forward);
}
}
}
As a simple test, you can create a cube, place this script on it, specify its transform as the target, and then create set three rotation values of (0,0,0), (90,0,0), and (0,90,0). If the object has no rotation, the rays appear correct. However, when you start rotating it you will see that some of the rays become warped. Additionally, setting a z axis just doesn’t ever seem to do anything to the rays.
I have tried many different ways to draw the ray but none of them work correctly. My guess is that it is due to how Quaternion.Euler works but I’m not sure how to get around the issue.
EDIT: I have added my own response for a work around that I did as a means to more easily accomplish my goal. I still don’t know how to properly translate a Vector3 rotational value into a ray that is properly drawn as a Gizmo to match the front facing of an object.
A good trick for this one is to work in local coordinates and let the gizmos matrix turn this into world coordinates for you.
private void OnDrawGizmosSelected() {
if (targetTransform == null) { return; }
Gizmos.color = Color.red;
Gizmos.matrix = targetTransform.localToWorldMatrix;
for (int x = 0; x < rotationResults.Length; x++)
{
Gizmos.DrawRay(Vector3.zero, Quaternion.Euler(rotationResults[x].Rotation) * Vector3.forward);
}
}
The line Gizmos.matrix = targetTransform.localToWorldMatrix;
is what allows you to specify your coordinates in terms of your transform’s space.
This is equivalent to calling TransformPoint / TransformDirection from the transform whose space you wish to use.
private void OnDrawGizmosSelected() {
if (targetTransform == null) { return; }
Gizmos.color = Color.red;
for (int x = 0; x < rotationResults.Length; x++)
{
Gizmos.DrawRay( targetTransform.TransformPoint(Vector3.zero), targetTransform.TransformDirection(Quaternion.Euler(rotationResults[x].Rotation) * Vector3.forward));
}
}
The problem with your solution is that you’re using your transform’s rotation multiple times, since you’re using the targetTransform’s rotation to rotate the ray’s direction, we don’t need to then use the targetTransform’s forward vector also. Similarly, you could just use the forward vector and not rotate by the targetTransform’s rotation. Below is the corrected version of your code, using the targetTransform’s rotation and not its forward direction:
private void OnDrawGizmosSelected() {
if (targetTransform == null) { return; }
Vector3 pos = targetTransform.position;
Gizmos.color = Color.red;
for (int x = 0; x < rotationResults.Length; x++)
{
Gizmos.DrawRay( pos, targetTransform.rotation * Quaternion.Euler(rotationResults[x].Rotation) * Vector3.forward);
}
}
I still have not found a solution to this but I have come up with what I think is a clever work-around. So, the entire point of this is that I wanted to basically be able to define different faces on dice and provide meaning as to what the value of that face is. As an example, let’s say I have a d12 and I want to be able to “roll” it and have it end its roll on a value of 6. To do that, I would need to know the rotation of that value.
OK! I wanted to be able to show a gizmo so I would know what all sides I have already configured but since I cannot get that working I started to think of another solution. Instead, I came up with the idea of writing some code to automatically collect the normals of the mesh and then convert those into rotational values which I could then manually go through and fill in their numeric value by hand. Here is some code I wrote to make this happen:
public Vector3[] rotationResults;
[ContextMenu("Auto Detect Rotations")]
private void AutoDetectRotations() {
MeshFilter filter = transform.GetComponent<MeshFilter>();
if (filter != null && filter.sharedMesh != null) {
Vector3[] normals = filter.sharedMesh.normals;
var results = new List<Vector3>;
for (int x=0; x < normals.Length; x++) {
results.Add(Quaternion.FromToRotation(normals[x], transform.forward));
}
// NOTE: Duplicates can occur which can be removed however one sees fit to handle it
rotationResults = results.ToArray();
}
}
This works in that it saves me the time from having to manually figure out the rotational values. I can then just go through each value found and set it on the object to find out which value is facing towards the front and set it in the editor.
As far as showing these values as a gizmo, I have no idea. My only guess is that if you take a Vector3 euler angle set directly on an object and then put that through the method Quaternion.Euler it does not produce the expected results. Idk, maybe somebody else knows the solution to this.