Created
February 3, 2023 20:23
-
-
Save Libberator/26c9176e4e51d7a52481ab90175d265d to your computer and use it in GitHub Desktop.
Roll-A-Tetrahedron
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; | |
// This is just a very basic example to get you started. | |
// To use, attach to an empty GameObject and use the ContextMenu options. | |
// Ideally this would be an EditorWindow script. But since this is just meant for a one-time use, it's not too important. | |
// Note: This does not generate UVs. Mapping a texture to just 4 potential UV coordinates is difficult; consider triplicating vertices or alternative approach. | |
public class TetrahedronMeshCreator : MonoBehaviour | |
{ | |
private enum Orientation { PointyEndForward, PointyEndRight } | |
[SerializeField] private Material _defaultMaterial; | |
[SerializeField] private Orientation _orientation = Orientation.PointyEndForward; | |
#if UNITY_EDITOR | |
[Tooltip("This is for saving the mesh data to a file.")] | |
[SerializeField] private string _meshSaveName = "NewMeshData"; | |
#endif | |
private const float ONE_OVER_ROOT_THREE = 0.577350269f; // floored center to vertex along the base | |
private const float ROOT_THREE_OVER_SIX = 0.288675135f; // floored center to mid-edge along the base | |
// private const float OVERALL_HEIGHT = 0.816496581f; // sqrt(2/3) = base to top vertex. for reference | |
private const float CENTER_TO_BOT = 0.204124145f; // sqrt(6)/12 = height diff from center to floor | |
private const float CENTER_TO_TOP = 0.612372436f; // OVERALL_HEIGHT - CENTER_TO_BOT | |
private void Reset() | |
{ | |
var shader = Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard"); | |
_defaultMaterial = new Material(shader); | |
} | |
// pointy end is forward | |
private readonly Vector3[] _verticesF = new Vector3[4] { | |
new Vector3(0.5f, -CENTER_TO_BOT, -ROOT_THREE_OVER_SIX), // back-right | |
new Vector3(0f, -CENTER_TO_BOT, ONE_OVER_ROOT_THREE), // forward | |
new Vector3(-0.5f, -CENTER_TO_BOT, -ROOT_THREE_OVER_SIX), // back-left | |
new Vector3(0f, CENTER_TO_TOP, 0f), // top center | |
}; | |
// pointy end to the right | |
private readonly Vector3[] _verticesR = new Vector3[4] { | |
new Vector3(ONE_OVER_ROOT_THREE, -CENTER_TO_BOT, 0f), // right | |
new Vector3(-ROOT_THREE_OVER_SIX, -CENTER_TO_BOT, 0.5f), // front-left | |
new Vector3(-ROOT_THREE_OVER_SIX, -CENTER_TO_BOT, -0.5f), // back-left | |
new Vector3(0f, CENTER_TO_TOP, 0f), // top center | |
}; | |
private readonly int[] _triangles = new int[12] | |
{ | |
0, 1, 2, // bottom face | |
1, 0, 3, // front-right face | |
0, 2, 3, // back or back-right face | |
2, 1, 3 // front-left or left face | |
}; | |
public Mesh GenerateMeshData() | |
{ | |
var verts = _orientation == Orientation.PointyEndForward ? _verticesF : _verticesR; | |
var mesh = new Mesh() { | |
vertices = verts, | |
triangles = _triangles | |
}; | |
mesh.RecalculateNormals(); | |
return mesh; | |
} | |
[ContextMenu("Create Tetrahedron")] | |
public void CreateTetrahedron() | |
{ | |
var mesh = GenerateMeshData(); | |
var root = new GameObject("Tetrahedron").transform; | |
var child = new GameObject("Mesh Visuals"); | |
child.transform.SetParent(root); | |
child.transform.localPosition = new Vector3(0f, CENTER_TO_BOT, 0f); | |
child.AddComponent<MeshFilter>().sharedMesh = mesh; | |
child.AddComponent<MeshRenderer>().material = _defaultMaterial; | |
child.AddComponent<MeshCollider>().sharedMesh = mesh; | |
#if UNITY_EDITOR | |
UnityEditor.EditorGUIUtility.PingObject(root); | |
#endif | |
Debug.Log("Tetrahedron created! Please add a [TetrahedronMover] component to the root GameObject.", root); | |
} | |
#if UNITY_EDITOR | |
[ContextMenu("Save Mesh Data to Assets")] | |
public void SaveAsset() | |
{ | |
var savePath = $"Assets/{_meshSaveName}.mesh"; | |
var mesh = GenerateMeshData(); | |
UnityEditor.AssetDatabase.CreateAsset(mesh, savePath); | |
var asset = UnityEditor.AssetDatabase.LoadAssetAtPath(savePath, typeof(Mesh)); | |
UnityEditor.EditorGUIUtility.PingObject(asset); | |
Debug.Log($"Mesh saved to: \"{savePath}\".", asset); | |
} | |
#endif | |
} |
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; | |
using System.Collections; | |
[SelectionBase] | |
public class TetrahedronMover : MonoBehaviour | |
{ | |
[Tooltip("This is where the raycasts will originate from and what gets rotated. Use child mesh object.")] | |
[SerializeField] private Transform _center; | |
[SerializeField, Min(0.05f)] private float _timeToMove = 0.5f; | |
[SerializeField] private AnimationCurve _ease = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); // select OutSine preset or customize instead | |
[Header("Misc")] | |
[SerializeField] private bool _randomlyWalk = false; | |
private const float ROOT_THREE_OVER_SIX = 0.288675135f; // floored center to edge, or half a step | |
private bool _isMoving = false; | |
private Transform _mainCam; | |
private void Start() | |
{ | |
_mainCam = Camera.main.transform; | |
Physics.queriesHitBackfaces = true; // Note: this is a global setting | |
} | |
private void Update() | |
{ | |
if (_isMoving) return; | |
var direction = GetInputDirection(); | |
if (direction == Vector3.zero) return; | |
StartCoroutine(RollATetrahedron(direction)); | |
} | |
private Vector3 GetInputDirection() | |
{ | |
if (_randomlyWalk) | |
{ | |
var rng = Random.insideUnitCircle; | |
return new Vector3(rng.x, 0f, rng.y); | |
} | |
var input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical")); | |
var camAngle = _mainCam.eulerAngles.y; | |
return Quaternion.Euler(0f, camAngle, 0f) * input; | |
} | |
private IEnumerator RollATetrahedron(Vector3 direction) | |
{ | |
if (!Physics.Raycast(_center.position, direction, out RaycastHit hit, 1f)) yield break; | |
_isMoving = true; | |
// Set up the movement vectors for Slerping | |
var halfStep = ROOT_THREE_OVER_SIX * new Vector3(hit.normal.x, 0f, hit.normal.z).normalized; | |
var startOffset = _center.localPosition - halfStep; | |
var targetOffset = _center.localPosition + halfStep; | |
var anchor = transform.position - startOffset; | |
// Set up the rotation quaternions for Slerping | |
var deltaRot = Quaternion.FromToRotation(hit.normal, Vector3.down); | |
var startRot = _center.rotation; | |
var targetRot = deltaRot * startRot; | |
var elapsedTime = 0f; | |
while (elapsedTime < _timeToMove) | |
{ | |
var t = _ease.Evaluate(elapsedTime / _timeToMove); | |
transform.position = anchor + Vector3.Slerp(startOffset, targetOffset, t); | |
_center.rotation = Quaternion.Slerp(startRot, targetRot, t); | |
yield return null; | |
elapsedTime += Time.deltaTime; | |
} | |
transform.position = anchor + targetOffset; | |
_center.rotation = targetRot; | |
_isMoving = false; | |
} | |
} |
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; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
[SelectionBase] | |
public class TetrahedronMoverRotateAround : MonoBehaviour | |
{ | |
[SerializeField] private List<Transform> _vertices; | |
[SerializeField, Min(0.05f)] private float _timeToMove = 0.75f; | |
private const float DEGREES = 109.4712206f; | |
private bool _isMoving = false; | |
private Transform _mainCam; | |
private void Start() | |
{ | |
_mainCam = Camera.main.transform; | |
} | |
private void Update() | |
{ | |
if (_isMoving) return; | |
var direction = GetInputDirection(); | |
if (direction == Vector3.zero) return; | |
StartCoroutine(RollATetrahedron(direction)); | |
} | |
private Vector3 GetInputDirection() | |
{ | |
var input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical")); | |
var camAngle = _mainCam.eulerAngles.y; | |
return Quaternion.Euler(0f, camAngle, 0f) * input; | |
} | |
private IEnumerator RollATetrahedron(Vector3 dir) | |
{ | |
_isMoving = true; | |
// acts as our "carrot on a stick", used for a distance check | |
var target = transform.position + dir; | |
// given world position vertices, ignore top-most vertex and take the closest two | |
var closestVertices = _vertices.Where(v => v.position.y < 0.5f) | |
.OrderBy(v => Vector3.Distance(v.position, target)).Take(2).ToArray(); | |
// the direction of the axis vector is important for how RotateAround works | |
var axis = closestVertices[1].position - closestVertices[0].position; | |
// we can use the Cross Product to know if this will rotate correctly | |
if (Vector3.Cross(dir, axis).y < 0f) // the resulting vector should point upwards | |
axis = -axis; | |
// either one will work for the anchor point | |
var anchor = closestVertices[0].position; | |
var totalDegrees = 0f; | |
while (totalDegrees < DEGREES) | |
{ | |
var deltaDeg = DEGREES * Time.deltaTime / _timeToMove; | |
transform.RotateAround(anchor, axis, deltaDeg); | |
totalDegrees += deltaDeg; | |
yield return null; | |
} | |
transform.RotateAround(anchor, axis, DEGREES - totalDegrees); // undo any slight overshooting | |
_isMoving = false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment