Skip to content

Instantly share code, notes, and snippets.

@omgwtfgames
Created August 9, 2017 11:26
Show Gist options
  • Save omgwtfgames/2218520d416409e6963016e09a44fe69 to your computer and use it in GitHub Desktop.
Save omgwtfgames/2218520d416409e6963016e09a44fe69 to your computer and use it in GitHub Desktop.
Helical object layout in Unity
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