context: using UnityEngine;
  using System.Collections;
  
  /*
   * http://www.opensource.org/licenses/lgpl-2.1.php
   * Copyright Defective Studios 2009-2011
   */
  ///<author>Matt Schoen</author>
  ///<date>5/21/2011</date>
  ///<email>schoen@defectivestudios.com</email>
  ///<version>10</version>
  /// <summary>
  /// Spline-based motion controller
  /// </summary>
  
  [RequireComponent(typeof(Rigidbody))]
  public class SplineController : MonoBehaviour {
  	const int MAX_OVER_ITER = 10;
  	public bool advancedMode;
  
  	public enum Mode { KEYBOARD, MOUSE, AUTO }
  	public Mode mode;
  
  	public bool looseFollow;
  	public float snapDistance = 0.1f;			//Mess with this if he "sticks" when he jumps
  
  	public static int SplineTopLayer = 31;
  
  	public float airForce = 10;
  	public float maxAirSpeed = 8;
  	public float runForce = 10;
  	public float maxRunSpeed = 10;
  	public float stopForce = 60;
  	public float coastForce = 8;
  	public float stopThresh = 2; 	// Cuts velocity at this value, to cancel back & forth jitters in stopping.
  	public float gravityForce = -25f;
  	public float jumpVelocity = 11;
  
  	public float horizAcceleration;
  	public float horizSpeed;
  	public int predictionLength = 80;	//150?
  	public int predictionStep = 5;		//10?
  
  	public Spline gravSpline;
  	public Spline currentSpline;
  	public int startVert;
  	public float rotationDamping = 10;
  	public SplineNode gravNode;
  	public SplineNode groundNode;
  	protected Vector3 gravPosition;
  	protected bool gravAhead;
  
  	public Vector3 hoverOffset;
  
  	public Vector3 dragSplinePosition;
  
  	public bool goInReverse;
  	public bool reverseOrientation;
  	public bool go;
  	public bool ignoreNodes;
  	public bool ignoreNextNode;
  	public bool startOnVert;
  
  	public bool KeyControl = true;
  
  	public float pauseTime;
  	float pauseStart = Mathf.Infinity;
  	float resumeSpeed;
  
  	void OnGUI() {
  		GUI.Box(new Rect(Screen.width - 75, 25, 75, 20), rigidbody.velocity.ToString());
  	}
  	public virtual void Start() {
  		if(gravSpline) {			//If the user has specified a gravity spline, set it up
  			gravNode = gravSpline.begin;
  			gravPosition = gravNode.transform.position;
  		}
  		if(currentSpline) {
  			currentSpline.followers.Add(this);
  			if(startOnVert) {
  				groundNode = currentSpline[startVert];
  				if(groundNode)
  					transform.position = groundNode.transform.position;
  			}
  		}
  	}
  	protected virtual void DoNode(SplineNode node) {
  		switch(node.type) {
  		case SplineNode.Type.CONTINUE:
  			horizSpeed = node.speed;
  			break;
  		case SplineNode.Type.PAUSE:
  			horizSpeed = 0;
  			pauseTime = node.pauseTime;
  			pauseStart = Time.time;
  			resumeSpeed = node.speed;
  			break;
  		case SplineNode.Type.STOP:
  			horizSpeed = 0;
  			break;
  		case SplineNode.Type.REVERSE:
  			horizSpeed = 0;
  			goInReverse = !goInReverse;
  			pauseStart = Time.time;
  			pauseTime = node.pauseTime;
  			resumeSpeed = node.speed;
  			break;
  		}
  	}
  	void Drive(Vector3 velocity, float input) {
  		if(groundNode)
  			horizSpeed = Vector3.Dot(velocity, groundNode.forward.normalized);
  		else if(gravNode)
  			horizSpeed = Vector3.Dot(velocity, gravNode.forward.normalized);
  		else
  			horizSpeed = Vector3.Dot(velocity, Vector3.right.normalized);
  		if(input == 0)
  			Stop(coastForce, horizSpeed);
  		else if((horizSpeed > 1 && input < 0) || (horizSpeed < -1 && input > 0))
  			Stop(stopForce, horizSpeed);
  		else {
  			if(input > 0)
  				goInReverse = true;
  			else
  				goInReverse = false;
  			if(groundNode) {
  				if(Mathf.Abs(horizSpeed) < maxRunSpeed)
  					horizAcceleration = input * runForce;
  			} else {
  				if(Mathf.Abs(horizSpeed) < maxAirSpeed) {
  					horizAcceleration = input * airForce;
  				} else {
  					//Debug.Break();
  					horizAcceleration = 0;
  				}
  			}
  		}
  	}
  	public virtual void Update() {
  		if(go) {
  			/*
  			 * Horizontal control input handling
  			 */
  			switch(mode) {
  			case Mode.KEYBOARD:
  				float input = Input.GetAxis("Horizontal");
  				if(KeyControl)
  					Drive(rigidbody.velocity, input);
  				break;
  			}
  			///*
  			// * For debugging: Press the b key to pause
  			// */
  			//if(Input.GetKeyDown(KeyCode.B)) {
  			//    //Debug.Break();
  			//}
  		}
  	}
  	public virtual void FixedUpdate() {
  		if(go) {
  			/*
  			 * Update velocity
  			 * THIS IS THE ONLY PLACE WE SHOULD DO THIS!!!
  			 */
  			if(Time.time > pauseStart + pauseTime) {
  				horizSpeed = resumeSpeed;
  				pauseStart = Mathf.Infinity;
  				ignoreNextNode = true;
  			} else ignoreNextNode = false;
  			if(groundNode) rigidbody.velocity = SplineMovement();
  			else rigidbody.velocity = AirMovement();
  			if(gravSpline)
  				GravMovement(transform.position, out gravPosition);
  			/*
  			 * Set the rotation sonic will Slerp to (hehe)
  			 * if we have a ground vertex, use it's forward vector
  			 * if we have a grav vertex, use it's forward vector
  			 * if we don't have either, look forward
  			 */
  			Quaternion targetRotation = Quaternion.identity;
  			if(groundNode)
  				targetRotation = Quaternion.LookRotation((goInReverse ^ reverseOrientation ? 1 : -1) * groundNode.transform.forward, groundNode.transform.up);
  			else if(gravNode)
  				targetRotation = Quaternion.LookRotation((goInReverse ^ reverseOrientation ? 1 : -1) * gravNode.transform.forward, gravNode.transform.up);
  			else
  				targetRotation = Quaternion.LookRotation((goInReverse ^ reverseOrientation ? 1 : -1) * Vector3.right, Vector3.up);
  			transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * rotationDamping);
  		}
  	}
  	void OnDrawGizmos() {
  		//Ground = red
  		//Grav = green
  		//Other = blue
  		Gizmos.color = Color.cyan;
  		if(groundNode) {
  			Gizmos.DrawCube(groundNode.transform.position, Vector3.one * 0.1f);
  			Gizmos.DrawLine(groundNode.transform.position, groundNode.transform.position + groundNode.forward);
  		}
  		Gizmos.color = Color.green;
  		if(currentSpline)
  			Gizmos.DrawCube(currentSpline.transform.position, Vector3.one * 0.1f);
  	}
  	//	public void UpdateSpline(){
  	//		if(currentSpline != currentSplineSource.spline){
  	//			Svertex oldBegin = currentSpline.begin;
  	//			Svertex newBegin = currentSplineSource.spline.begin;
  	//			while(oldBegin.next != null && newBegin.next != null){		//roll forward through both splines until we find the old groundCV
  	//				if(oldBegin == groundCV)
  	//					break;
  	//				oldBegin = oldBegin.next;
  	//				newBegin = newBegin.next;
  	//			}
  	//			groundCV = newBegin;
  	//			currentSpline = currentSplineSource.spline;
  	//		}
  	//	}
  	public void Detach() {
  		//Debug.Log("detach" + Time.frameCount);
  		currentSpline = null;
  		groundNode = null;
  		transform.parent = null;
  	}
  	public bool FindSplineVertex(Spline s, Vector3 a, Vector3 b, out SplineNode vert, out Vector3 position) {
  		return FindSplineVertex(s, a, b, null, out vert, out position);
  	}
  	bool FindSplineVertex(Spline s, Vector3 a, Vector3 b, SplineNode guess, out SplineNode vert, out Vector3 position) {
  		SplineNode countup, countdown;
  		if(s != null) {
  			if(guess == null) {
  				if(!(s.begin && s.end)) {
  					Debug.LogError("Could not find vertex, begin and end not set");
  					vert = null;
  					position = Vector3.zero;
  					return false;
  				}
  				countup = s.begin;
  				countdown = s.end;
  			} else {
  				countdown = guess;
  				countup = guess;
  			}
  			Utility.Vector3Pair points;
  			while(countdown != null || countup != null) {
  				if(countup.next != null) {
  					//#if(DEBUG_DRAW||DEBUG_DRAW_LND)
  					Debug.DrawLine(countup.transform.position, countup.next.transform.position, Color.magenta);
  					//#endif
  					points = Utility.Dist3DSegToSeg(a, b, countup.transform.position, countup.next.transform.position);
  					if((points.a - points.b).magnitude < snapDistance) {
  						//Debug.Log(points.a + ", " + points.b);
  
gt: 						Debug.DrawLine(points.b, transform.position, Color.magenta);