Mech Console Construction

We made the Mech Simulator console!  From its humble pre-visualization beginnings, it is now a reality!

On the far side of this picture we have a yellow and red e-stop button fitted into the console.  This button will be wired directly to the motion base platform, bypassing the Unity3D simulation and all other electronics for a true full-emergency stop.

The three circle buttons (engage, start, shutdown) fit perfectly, but there was one problem.  There were incandescent bulbs in each bulb that ran on 12 volt power.  We didn’t want to create a 12 volt power supply, plus the bulbs weren’t bright, were too hot, wouldn’t last long, etc.  I ended up raiding my personal stash and grabbing three 10mm “ultra-bright” LEDs.  These looked great!  Well, mostly great:

Yeah, there’s a definite hot-spot in the button.  The LED was close the plastic cover and so there was a bright spot in the middle.  Fortunately we are Makers and had several solutions at our disposal.  We went with my favorite… sandblasting!

See that clear LED on the left?  That’s the normal clear bulb.  The one on the right?  The sandblasted one!  We literally took the LED out back and blasted sand at it so the light would diffuse.  The result:

Perfect even lighting in the button.  Awesome!

Next we needed to put the 10″ LCD monitor in the middle.  We 3D printed a border facade for the front, but needed some way to attach the monitor back to the console so that it wouldn’t fall through.  We could have used screws and some bent steel strips, but ultimately went with something more awesome: vacuform a back!  Fortunately we had a spare console front, so I used that as the base for the mold (as well as the monitor itself):

We heated up the vacuform machine, heated the plastic, turned on the suction and:

We had a plastic mold that fit our monitor!  Let’s see how it looks in the console:

It looks great!  And it works great, too!

Our console as a whole was designed to take 30 different pieces to create.  That might not a lot, but it was a real-life giant jigsaw puzzle!  I spent a couple of hours on Sunday meticulously recreating the console at one-tenth scale.  It was invaluable as a reference when putting together the real thing, both as a guide and to reference angles and part numbers (look closely in the following picture and you can see the numbers).  It even revealed a mistake in one of the pieces that we were able to fix with little ado.  Check out the painted console in the background:

There are a few more pieces to this puzzle (decals, wiring, etc.) including the mysterious gray object at the far corner of the above table.  Hint: It’s a custom holder for the Oculus Rift CV1 IR camera that will be mounted to the center of the console.

Stay tuned to see the console up on the platform with the chair and controls in the Mechbay!

 

 

Mech Simulator: Console Prototype

I spent the weekend prototyping a console interface for our Mech Simulator. I was able to integrate an Arduino Uno, a Nuc box, and Unity communicating over UDP/IP. This is what I call fun!

Skycraft

It’s always a good day when I get to venture out to Skycraft Surplus! They have a bunch of electric parts to tinker with and it is very difficult to leave empty-handed!
I found a few things that might work on the mech simulator or in the Mechbay, but they were border-line pricey. Maybe next time!

Project: Codingame.com – There is no spoon

Tools and Technology: C++

There’s a great site called Codingame that gives practice code challenges, including weekly and sponsored challenges, as well as coder-vs-coder and 5-minute challenges. This site helps keep me coding every day. I’ve completed all of the Easy puzzles and am now pushing through the Medium ones. This is from the There Is No Spoon challenge.

Sample Code

#include 
#include 
using namespace std;

int main()
{
    int width; // the number of cells on the X axis
    cin >> width; cin.ignore();
    int height; // the number of cells on the Y axis
    cin >> height; cin.ignore();
    string *grid = new string[height]; // grid of characters, each either 0 or .
    for (int i = 0; i < height; i++) {
        getline(cin, grid[i]); // line of characters
    }
    

    for (int vert = 0; vert < height; vert++)
    {
     for (int horz = 0; horz < width; horz++)
     {
         if('0' == grid[vert][horz]) // If we found a 
         {
              // Print the base node coords
              cout << horz << " " << vert << " ";
              
              // Print possible right neighbor coords
              int i = 0;
              int tempx = -1;
              int tempy = -1;
              while(width != horz + ++i)
              {
                  if('0' == grid[vert][horz + i])
                  {
                      tempx = horz + i;
                      tempy = vert;
                      break;
                  }
              }
              cout << tempx << " " << tempy << " ";
              
              // Print possible bottom neighbor coords
              i = 0;
              tempx = tempy = -1;
              while(height != vert + ++i)
              {
                  if('0' == grid[vert + i][horz])
                  {
                      tempx = horz;
                      tempy = vert + i;
                      break;
                  }
              }
              cout << tempx << " " << tempy << " " << endl;
         }
     }  
    }
    
    delete[] grid;
}

BenSchuler.com Revamp

Hello there!  Ben here.  I’ve decided to transform my hodgepodge of a site into a more organized hodgepodge of a site.  And so: WordPress.  I’ll likely keep bits and pieces of projects on here as well as a more formal portfolio and resume.

For those of you who don’t know, I’m finishing my schooling in Simulation and Visualization (lots of Virtual Reality and Digital Fabrication) very soon and am job hunting in the Washington D.C. Metro area.  If you have any ideas, let me know!

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;
	}
}

Project: Nahaulis – A Gun of Three Spirits

Tools and Technology: C#, Unity, PhotoShop, Agile/SCRUM

“Nahaulis: A Gun of Three Spirits is a 2D explorative, action-adventure game that takes place in an Aztec temple where the player uses Nahaulis to defeat the manifested god, Tezcatlipoca.”

My development group and I spent a month and a half creating this platformer in multiple Agile sprints. My responsibilities included projectiles, minimap, animatics, a few enemies, and a lot of bug hunting.

C# code for the part of the minimap controller.

void DisableAllMapSquares()
	{
		for (int i = 0; i < gom_MinimapSquares.Length; i++) {
			gom_MinimapSquares[i].SetActive(false);
		}

		gom_AlternateEndingSquare.SetActive(false);

	}

	int GetMiniMapIndexByRoomAndSquare(int _mmRoom, int _mmSquare)
	{
		for (int i = 0; i < m_MinimapSquares.Length; ++i) {
			if ((_mmRoom == m_MinimapSquares[i].RoomIndex)&&(_mmSquare == m_MinimapSquares[i].SquareIndex)) {
				return i;
			}
		}
		return -1;
	}

	public void LoadMinimapRoomsFromPlayerPrefs()
	{
		string currentRoomString;
		for (int i = 1; i <= 28; i++) {
		if (PlayerPrefs.HasKey ("MM_ActiveRoom_" + i) && PlayerPrefs.GetString("MM_ActiveRoom_" + i).Length >= 8) {
                    currentRoomString = PlayerPrefs.GetString ("MM_ActiveRoom_" + i);
				for (int square = 0; square < 8; square++) {
					if ("1" == currentRoomString.Substring (7 - square, 1)) {
						ActivateMinimapRoomAndSquare (i, square);
					}
				}
			}
		}
	}

	void TurnOnPlayerPrefSquare(int _mmRoom, int _mmSquare)
	{
        // This resets the entire room to ZERO.  DO NOT USE, EVERRRRRRRRRRR
        //PlayerPrefs.SetString ("MM_ActiveRoom_" + _mmRoom, "00000000");
        //return;

        //Debug.Log(PlayerPrefs.GetString("MM_ActiveRoom_" + _mmRoom).Length + " " + _mmSquare);

		if (!PlayerPrefs.HasKey ("MM_ActiveRoom_" + _mmRoom) || PlayerPrefs.GetString("MM_ActiveRoom_" + _mmRoom).Length < 8) 
		{
			PlayerPrefs.SetString ("MM_ActiveRoom_" + _mmRoom, "00000000");
		}
			
		string tempString = PlayerPrefs.GetString ("MM_ActiveRoom_" + _mmRoom);

		if ("0" == tempString.Substring(7 - _mmSquare, 1)) { // If it's not yet set:
			string ReturnString = "";
			for (int i = 0; i < 8; i++) {
				if ((7 - _mmSquare) == i) {
					ReturnString += "1";
				} else {
					ReturnString += tempString.Substring (i, 1);
				}
			}
			PlayerPrefs.SetString ("MM_ActiveRoom_" + _mmRoom, ReturnString);
		}
}



	public void ActivateMinimapRoomAndSquare(int _mmRoom, int _mmSquare, bool _isFromRoomMap = false)
	{
		m_currentRoom = _mmRoom;
		m_currentSquare = _mmSquare;

		// Set the PlayerPrefs appropriately
		TurnOnPlayerPrefSquare(_mmRoom, _mmSquare);


		// Turn on the Game Object
		gom_MinimapSquares[GetMiniMapIndexByRoomAndSquare(_mmRoom,_mmSquare)].SetActive(true);

		// Update the minimap position
		Vector2 currentMapPosition = GetMapPositionFromRoomAndSquare(_mmRoom, _mmSquare);
		m_TargetPosition = new Vector2(m_BaseX - (m_MMUnitMultiplier * currentMapPosition.x) + m_OffsetX, m_BaseY + (m_MMUnitMultiplier * currentMapPosition.y) + m_OffsetY);

		// Jump to the current room the first time
		if ((_isFromRoomMap)&&(!m_bHasInitialized)) {
			m_MapPartContainer.transform.position = m_TargetPosition;
			m_bHasInitialized = true;
		}
	}

Project: ASCII Roguelike Dungeon Crawler

Tools and Technology: C++

ASCII Roguelike Dungeon Crawler is a short game I started in an early programming class.

C++ code for the drawing the map.

void MapData::drawMap(void)
{

	int offsetX = 59;
	int offsetY = 1;

	for (int rows = 0; rows < MAP_HEIGHT + 1; rows++)
	{
		for (int cols = 0; cols < MAP_WIDTH + 1; cols++)
		{
			Console::SetCursorPosition(cols * 2 + offsetX, rows * 2 + offsetY);

			// Print out the "column" value (currently not used)
			Console::ForegroundColor(ConsoleColor::DarkGray);
			cout << char(197);
			Console::ResetColor();

			// Print out the Horizontal Wall type (top wall)
			if (getSquare(rows, cols)->getHWallType() == WallData::WallType::Wall_Solid)
			{
				if (cols != MAP_WIDTH)
				{
					Console::ForegroundColor(ConsoleColor::DarkGray);
					cout << char(196);
					Console::ResetColor();
				}
			}
			else if (getSquare(rows, cols)->getHWallType() == WallData::WallType::Door_Locked)
			{
				cout << '=';
			}
			else if (getSquare(rows, cols)->getHWallType() == WallData::WallType::Door_Boss)
			{
				Console::ForegroundColor(ConsoleColor::DarkMagenta);
				cout << char(219);
				Console::ResetColor();
			}
			else
			{
				cout << ' ';
			}

			Console::SetCursorPosition(cols * 2 + offsetX, rows * 2 + 1 + offsetY); //Jump the cursor down a row

			// Print out the Vertical Wall type (left wall)
			if (getSquare(rows, cols)->getVWallType() == WallData::WallType::Wall_Solid)
			{
				if (rows != MAP_HEIGHT)
				{
					Console::ForegroundColor(ConsoleColor::DarkGray);
					cout << char(179);
					Console::ResetColor();
				}
			}
			else if (getSquare(rows, cols)->getVWallType() == WallData::WallType::Door_Boss)
			{
				Console::ForegroundColor(ConsoleColor::DarkMagenta);
				cout << char(219);
				Console::ResetColor();
			}
			else
			{
				cout << ' ';
			}

			// Print out the Floor Object Type (This is whatever is sitting in the room)
			if ((getSquare(rows, cols)->getFloorObjectType() == FloorObjectData::FloorObjectType::Character_NPC) && (cols != MAP_WIDTH) && (rows != MAP_HEIGHT))
			{
				Console::ForegroundColor(ConsoleColor::Red);
				cout << '&';
				Console::ResetColor();
			}
			else if ((getSquare(rows, cols)->getFloorObjectType() == FloorObjectData::FloorObjectType::Character_NPC_Boss) && (cols != MAP_WIDTH) && (rows != MAP_HEIGHT))
			{
				Console::ForegroundColor(ConsoleColor::Magenta);
				cout << '&';
				Console::ResetColor();
			}
			else if ((getSquare(rows, cols)->getFloorObjectType() == FloorObjectData::FloorObjectType::Character_PC) && (cols != MAP_WIDTH) && (rows != MAP_HEIGHT))
			{
				Console::ForegroundColor(ConsoleColor::Cyan);
				switch (m_pcParty->getDirection())
				{
				case PlayerParty::Direction::North:
					cout << '^';
					break;
				case PlayerParty::Direction::East:
					cout << '>';
					break;
				case PlayerParty::Direction::South:
					cout << 'v';
					break;
				case PlayerParty::Direction::West:
					cout << '<';
					break;
				default:
					break;
				}
				Console::ResetColor();
			}
			else
			{
				cout << ' ';
			}
		}
		cout << endl;
	}

	// Draw enemies colleted
	Console::ForegroundColor(ConsoleColor::Red);
	Console::SetCursorPosition(offsetX, offsetY + 20);
	for (int i = 0; i < enemiesCollected; i++)
	{
		cout << "& ";
	}
	Console::ResetColor();

	drawViewport();
}