Project: Oh Buoy!

Tools and Technology: C++, Arduino, C#, Unity, Solidworks, 3D printing, miscellaneous hardware, simplex noise maths, SimTools, Live for Speed

After I created my own Stewart Platform it was time to push it further, creating an interactive game (“Oh Buoy!”) and custom peripheral (custom Lego boat).

Sample Code

C++ code Arduino code used to control the platform with Inverse Kinematics and Matrix maths.
Download Arduino code.

C# code used to communicate serial data from Unity to Arduino. Also, the code to move the buoys with Simplex Noise.

public class ArduinoHelper : MonoBehaviour {

	[StructLayout(LayoutKind.Explicit)]
	struct floatyBytes
	{
		[FieldOffset(0)]
		public byte byte0;
		[FieldOffset(1)]
		public byte byte1;
		[FieldOffset(2)]
		public byte byte2;
		[FieldOffset(3)]
		public byte byte3;
		[FieldOffset(0)]
		public float floaty;

		public floatyBytes(float _floaty = 0.0f)
		{
			byte0 = 0;  // The compiler thinks these bytes are
			byte1 = 0;  // not set at the end of this constructor.
			byte2 = 0;  // It is wrong, of course, but we need to
			byte3 = 0;  // make it happy.  The float overwrites these.
			floaty = _floaty;
		}
	}
		
	byte[] startByte = {33};  // '!'
	byte[] endByte = {35};  // '#'
	floatyBytes[] tempDOFs = new floatyBytes[6];
	byte[] tempBytes = new byte[4];
	byte[] tempByte = new byte[1];
	float sendTime;
	float serialTimeBuffer = 0.02f;

	SerialPort serialPort;


	void Start () {
		sendTime = Time.time + serialTimeBuffer;
		serialPort = new SerialPort("COM7", 9600);
		serialPort.Open();
	}

	//  In keeping with the reference spreadsheet,
	//  Order: x, y, z, pitch, roll, yaw
	public void Send6DOF(float[] _dofs)
	{
		
		if (Time.time > sendTime) {
			if (serialPort.IsOpen) {
				serialPort.Write (startByte, 0, 1);
				
				for (int i = 0; i < 6; i++) {
					tempDOFs [i].floaty = _dofs [i];

					tempBytes [0] = tempDOFs [i].byte0;
					tempBytes [1] = tempDOFs [i].byte1;
					tempBytes [2] = tempDOFs [i].byte2;
					tempBytes [3] = tempDOFs [i].byte3;
					serialPort.Write (tempBytes, 0, 4);
				}

				serialPort.Write (endByte, 0, 1);
				//serialPort.BaseStream.Flush ();  // This sends the data right away, with no buffering
			}
			sendTime = Time.time + serialTimeBuffer;
		}

			
	}

	void OnApplicationQuit()
	{
		if (null != serialPort && serialPort.IsOpen) {
			serialPort.Close ();
		}
	}
}


// ********************************************************************
// ********************************************************************

public class BuoyMovement : MonoBehaviour {

	const float ANGLE_MAX = 14.0f;
	Vector3 tempVec3 = Vector3.zero;
	Vector3 tempPos;
	bool acquired = false;

	[SerializeField]
	AudioClip buoyDing;
	AudioSource m_audio;

	// Use this for initialization
	void Start () {
		tempPos = transform.position;
		m_audio = GetComponent ();
	}
	
	// Update is called once per frame
	void FixedUpdate () {
		// Add random floaty angles
		float currentPositionZ = transform.position.z;
		tempVec3.z = getAngle (0.0f + currentPositionZ );
		tempVec3.x = getAngle (180.5f + currentPositionZ );

		transform.eulerAngles = tempVec3;

		tempPos.y = getAngle (90.0f + currentPositionZ ) / 112.0f + 1.0f;
		transform.position = tempPos;
	}

	float pseudoRandGen(long seed)
	{
		seed %= 256;
		seed += 71;
		long tempLong = (101111111111 * seed * seed) % 10001;
		return ((float)tempLong / 10001.0f) * (2.0f * ANGLE_MAX) - ANGLE_MAX;
	}

	float getAngle(float _ratioOffset)
	{
		float currentTime = (Time.time * 0.5f) + _ratioOffset;
		long currentTimeIndex = (long)currentTime % 256;
		long nextTimeIndex = (currentTimeIndex + 1) % 256;

		float currentAngleIndex = pseudoRandGen (currentTimeIndex);
		float nextAngleIndex = pseudoRandGen (nextTimeIndex);

		float ratio = currentTime - (float)currentTimeIndex;
		ratio = ease1 (ratio);

		return lerp (ratio, currentAngleIndex, nextAngleIndex);
	}

	float lerp(float _ratio, float _num1, float _num2)
	{
		return (_num2 - _num1) * _ratio + _num1;
	}

	float ease1(float _ratio)
	{
		return 1 - (Mathf.Cos(_ratio * Mathf.PI) + 1.0f) * 0.5f;
	}

	public bool acquireBuoy()
	{
		if (acquired)
			return false;
		
		acquired = true;

		// Ding!
		m_audio.PlayOneShot(buoyDing);

		Light[] buoyLight = GetComponentsInChildren ();
		for (int i = 0; i < buoyLight.Length; i++) {
			buoyLight [i].color = Color.green;
		}
		GetComponentInChildren ().Play ();

		return true;
	}
}