Created
August 9, 2017 11:26
-
-
Save omgwtfgames/2218520d416409e6963016e09a44fe69 to your computer and use it in GitHub Desktop.
Helical object layout in Unity
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; | |
public class HelicalLayout : MonoBehaviour | |
{ | |
int _numberOfNodes; | |
public float Radius = 1f; | |
public float Pitch = 1f; | |
public float Turn = 30f; | |
public float TurnPhase = 0f; | |
public Vector3 AxisDirection = Vector3.up; | |
public bool SingleTurn = false; // override Turn so we always have a single turn (eg a ring of nodes) | |
public bool LookAtAxis = true; | |
public bool LookAtTransform = false; | |
public Transform LookAt; | |
public bool FlipNodeFacing = true; | |
public enum UpdateMethod { | |
None = 0, | |
FixedUpdate = 1, | |
Update = 2, | |
LateUpdate = 3 | |
} | |
public UpdateMethod UpdateLayoutIn; | |
public List<Transform> Nodes; | |
// TODO: Turn this into an ILayoutModifier object with a GetValue() or similar method | |
// This way we can make them serializable with input attributes and put them in the Inspector as lists of modifiers | |
public delegate float LayoutModifier(float y, float value); | |
// we can add modifiers to this list, to be applied to the layout | |
// (we could do this with a regular multicast delegate and iterate over GetInvocationList(), but there's no situation | |
// where we would want to call that delegate as a multicast [where we can't capture every return value] | |
// we always iterate the delegate list, so we just make it explicit) | |
// TODO: Turn these into lists of ILayoutModifier objects | |
public LayoutModifier RadiusModifier; | |
public LayoutModifier PitchModifier; | |
public LayoutModifier TurnModifier; | |
// public ILayoutModifier NodeRotationModifier; // returns a Quaternion to apply to the localRotation of the Node | |
//IEnumerator<Vector3> _pointGenerator; | |
void Awake() | |
{ | |
//RadiusModifier = new LayoutModifier(CosineOffsetLayoutModifier); | |
//PitchModifier = new LayoutModifier(CosineOffsetLayoutModifier); | |
//TurnModifier = new LayoutModifier(CosineOffsetLayoutModifier); | |
//_generateNodes(NumberOfNodes, NodePrefab); | |
if (LookAtTransform && LookAtAxis) | |
{ | |
Debug.LogError("Can't LookAtAxis and LookAtTransform", gameObject); | |
} | |
} | |
void Start() | |
{ | |
UpdateNodeList(); | |
ApplyLayout(); | |
} | |
void Update() | |
{ | |
if (UpdateLayoutIn == UpdateMethod.Update) ApplyLayout(); | |
} | |
void FixedUpdate() | |
{ | |
if (UpdateLayoutIn == UpdateMethod.FixedUpdate) ApplyLayout(); | |
} | |
void LateUpdate() | |
{ | |
if (UpdateLayoutIn == UpdateMethod.LateUpdate) ApplyLayout(); | |
} | |
public void UpdateNodeList() | |
{ | |
if (Nodes == null) Nodes = new List<Transform>(); | |
Nodes.Clear(); | |
foreach (Transform child in transform) | |
{ | |
Nodes.Add(child); | |
} | |
} | |
float CosineOffsetLayoutModifier(float y, float value) | |
{ | |
float freq = 1f; | |
float amp = 0.1f; | |
float offsetSpeed = 0.2f * Mathf.PI; | |
float newv = value + (Mathf.Cos((y + (offsetSpeed * Time.time)) * freq) * Radius * amp); | |
return newv; | |
} | |
void _generateNodes(int numberOfNodes, GameObject nodePrefab) | |
{ | |
if (Nodes == null) Nodes = new List<Transform>(); | |
for (int i = 0; i < numberOfNodes; i++) | |
{ | |
GameObject node = new GameObject(); | |
Nodes.Add(node.transform); | |
node.name = string.Format("Node-{0}", i); | |
node.transform.parent = transform; | |
if (nodePrefab != null) | |
{ | |
GameObject child = Instantiate(nodePrefab, node.transform.position, Quaternion.identity) as GameObject; | |
child.transform.parent = node.transform; | |
child.transform.rotation = Quaternion.identity; | |
} | |
} | |
ApplyLayout(); | |
} | |
Vector3 NearestPointOnAxis(Vector3 axisDirection, Vector3 point, bool isNormalized = false) | |
{ | |
if (!isNormalized) axisDirection.Normalize(); | |
var d = Vector3.Dot(point, axisDirection); | |
return axisDirection * d; | |
} | |
void LookAtHelixAxis(Transform node) | |
{ | |
var nearestPointOnAxis = NearestPointOnAxis(AxisDirection, node.position); | |
var worldUpDirection = AxisDirection; | |
//var nearestPointOnAxis = new Vector3( | |
// _transform.position.x, | |
// node.transform.position.y, | |
// _transform.position.z | |
// ); | |
//var worldUpDirection = _transform.up; | |
node.transform.LookAt(nearestPointOnAxis, | |
worldUpDirection * (FlipNodeFacing ? -1 : 1) | |
); | |
} | |
void ApplyLayout() | |
{ | |
if (SingleTurn) Turn = 360f / Nodes.Count; | |
using (var _points = GetHelixPointGenerator().GetEnumerator()) | |
{ | |
foreach (var node in Nodes) | |
{ | |
if (_points.MoveNext()) | |
{ | |
node.localPosition = _points.Current; | |
} | |
else | |
{ | |
break; | |
} | |
if (LookAtAxis) LookAtHelixAxis(node); | |
if (LookAtTransform) | |
{ | |
node.transform.LookAt(LookAt.position, AxisDirection * (FlipNodeFacing ? -1 : 1)); | |
} | |
} | |
} | |
} | |
/* TODO: Add this many nodes, by using an overloaded | |
* version of GetHelixPointGenerator(int index) that allows skipping ahead to an initial i value | |
* so we can extend from the highest Node index. | |
*/ | |
void ExtendNodes(int numberOfNodes) | |
{ | |
} | |
/* | |
* Generates points along an y-axis aligned helix. | |
*/ | |
IEnumerable<Vector3> GetHelixPointGenerator() | |
{ | |
float _radius = Radius; | |
float _pitch = Pitch; | |
float _turn = Turn; | |
float yPerStep = _pitch * (_turn / 360f); | |
int i = 0; | |
while (true) | |
{ | |
float y = yPerStep * i; | |
if (RadiusModifier != null) _radius = RadiusModifier(y, Radius); | |
if (PitchModifier != null) _pitch = PitchModifier(y, Pitch); | |
if (TurnModifier != null) _turn = TurnModifier(y, Turn); | |
// we build a helix paralell to the Y-axis, then rotate everything | |
// to the user specified AxisDirection | |
Vector3 p = new Vector3(_radius, y, 0f); | |
p = Quaternion.AngleAxis((_turn * i) + TurnPhase, Vector3.up) * p; | |
//p = Quaternion.AngleAxis(TurnPhase, Vector3.up) * p; | |
p = Quaternion.FromToRotation(Vector3.up, AxisDirection) * p; | |
yield return p; | |
i++; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment