Building a Solar System in XNA (Part 1)

Posted on March 25, 2008 by Chris at 1:52 pm

Building a virtual solar system cant be too hard, right? Well, I can imagine how people might think that it would just be a matter of whacking in a couple of planets, perhaps a little space ship, and Bob’s your uncle. I’m quite sure that by the end of this, I’ll have proven those people wrong.. I’m thinking that I’ve actually bitten off a bit more than I can chew with this one :)

Anyway.. the topic that this article will attempt to demonstrate is how to build a solar system, using Microsoft XNA as the basic framework. The reason for using XNA, as opposed to DirectX or Open GL, is that (if done correctly), games compiled with XNA can work directly on your Xbox 360. And since I have one of these nifty devices, and I bought it under the premise of writing programs for it.. I really thought I should try and learn a bit about.. well.. writing programs for it :)

I plan to separate this article into three parts, so that the information can be more easily assimilated, and so that I dont end up writing a HUGE blog entry containing too much information, which no one will read. (Who am I kidding.. I’m the only one who reads this blog anyway).

Part 1 - Will cover the basic preparations, and end with a simple application that we will use to build on.

Part 2 - We will do a bit of work with a Scene Graph, add some Planets (and maybe some sattelites), and get things moving.

Part 3 - We will add in some collision detection, and take a look at the gravity of the situation.

The Solar System

The zones of the Solar system: the inner solar system, the asteroid belt, the giant planets (jovians) and the Kuiper Belt. Orbits not to scale.(cue cool space theme music of your choice)

You all remember remember the lessons from school about our Solar System, and it’s 8 planets. I obviously dont remember correctly, cause when I went to school there were 9 planets, but aparently one of those planets exploded got re-categorised as a “dwarf planet”. In any case, our version of the Solar System is going to have 8 planets, and 1 asteroid belt, some sattelites(moons) if we get round to it, and we will let the astronomers argue about what happens out further.

Now, with our picture in hand, plus a little extra information, we can make a table of objects that will be included in the simulation.

Object Distance Radius Mass
Sun 0 AU 6.955 ? 105 km 1.9891 ? 1030 kg
Mercury 0.47 AU 2,439.7 km 3.3022 ? 1023 kg
Venus 0.73 AU 6,051.8 km 4.8685 ? 1024 kg
Earth 1.02 AU 6,371.0 km 5.9736 ? 1024 kg
Mars 1.67 AU 3,396.2 km 6.4185 ? 1023 kg
Asteroid Belt 2.80 AU N/A N/A
Jupiter 5.46 AU 71,492 km 1.8986 x 1027 kg
Saturn 10.12 AU 60,268 km 5.6846 x 1026 kg
Uranus 20.08 AU 25,559 km 8.6810 x 1025 kg
Neptune 30.44 AU 24,764 km 1.0243 x 1026 kg

One of the immediate problems that you can see here is that these objects are fairly big (one might say big enough to have their own gravitational pull). The problem with them being so large is that they are also visible from a loooonnnng way off. In most 3d games and whatnot, you would use a viewing frustrum to cut things off that were a long way away, because they would be simply too small to see. Everyone knows that the Sun is visible from Earth, even though it is 150 million km away, so you just wouldn’t get away with removing it from the scene. I’m thinking that perhaps we could use some kind of scaling algorigthm to determine if an object is visible, since the objects shown are basically going to be big spheres (really really big spheres), it shouldn’t be too difficult to do this. I’d be thinking that this will be requiring some trial and error testing out between Part 2 and Part 3.

Ixnay on Dissin’ the XNA

Ok, just between us, I’m all for Microsoft bashing.. I mean, they are a massive target, so you pretty much can’t miss.. and, you get cred for doing so :D

For all the Microsoft hating going around, I do like my Xbox, and I do like being able to potentially write games for it. However, I don’t like the need to pay membership to the XNA Creators Club, just to be able to play with your own applications on your own console. Fortunately, for us cheap soandsos, they have made XNA Game Studio 2.0 free to download and use. This means that you can write Xbox compatable code, and test it on your own system. Then, if you feel it is good enough to pay for, get a membership and show it off on your Xbox. Thus confirming (again) to all your friends that you are a complete nerd.

Since I own a legal a copy Visual Studio 2005, all the sample code should be compatable with that version. However, it should (in theory) work with any other version that you can install XNA Game Studio against. You might just have to do a bit of hammering to get it to work.

Now, I’m not going to walk through all of the “this is how you set up the project” to “here’s your first triangle” stuff.. there are plenty of other sites, including the XNA Creators Club which will run through this for you, and save ME the trouble of introducing you to 3d. So.. now.. for your homework, go download XNA, install it, run through some tutorials, and I’ll see you some time next week.

I’ve written a basic game that I will modify as we create the solarsystem. You can download the source here, or the binaries here. Just in case you were wondering, yes, that is the space ship model from the Spacewar starter kit. I’m no artist, nor do I have any art packages, so for this little example I hope it’s ok to use that model :)

Source: Download

Binaries: Download.

A Network Protocol for Movement (Part 3)

Posted on March 14, 2008 by Chris at 5:00 pm

A quick recap before we begin. In Part 1, I described a network protocol for moving objects around in 3d space. In Part 2, I wrote a server application that recieved connection requests and mob updates, and forwarded mob positions on to all other clients.

In this entry I’ll be writing about the Client Application that goes with the movement server. The application will basically be a 2d ships flying around type application, which looks like the screenshot to the right. The red blob on the screen is the Mob that is controlled by this client, while the green blobs are controlled by other clients. The blobs are supposed to look like turtles, but you can see my mspaint skills at work here :)

The server application is basically the same one that we used last time, with one or two bug fixes. As with the last entry, you can download the acompanying code to play with. I’ve also added the compiled binaries if you would prefer to just play with them.

Warning: Math Content

The only problem with working with 3d (even if you are displaying it in 2d), is that you need to know a little extra math to get things working properly. If you know how all this math works, congratulations. If not, here are some helpful hints and links that will assist you in reading my ugly math ridden code.

A Vector is basically a 3 dimentional {X,Y,Z} coordinate that can determine an objects position in 3d space, or be used for a variety of other porpoises (yes, they can be used for dolphins! who said they cant?). In fact we are also using Unit Vectors (vectors with a magnitude/length of 1 unit), to determine where Up, Right and Forward are.

The Cross Product of two vectors can be used to determine a third mutually orthogonal (right angled) vector. You will notice that we are only sending the Up and Right vectors using our movement protocol. To create our position matrix, we need to calculate a Forward or LookAt vector, which is simply done using the cross product of our Up and Right vectors.

The Dot Product of two vectors can be used to detrmine the cosine of the angle between them. This is how we figure out which way our little space ships are pointing. Given that our coordinate system is a right-handed y-up, z-facing one, we perform the dot product of {0,0,-1} and the Forward Vector. Finally, if the Forward.X value is less than 0, we make an adjustment, realising that the angle we are looking at is a reflex angle (greater than 180).

We use a Matrix to store the current Up, Right, Forward, and Location vectors. We can then use Matrix Multiplication against another matrix (such as a Translation, or Rotation Matrix) to move the object around the 3d space.

To create our position matrix can be made up using our Right (R), Up (U), Forward (F), and Position (P) vectors in the following 4×4 array

[R.X, R.Y, R.Z, 0.0]

[U.X, U.Y, U.Z, 0.0]

[F.X, F.Y, F.Z, 0.0]

[P.X, P.Y, P.Z, 1.0]

Note: I did kind of come up with the positioning matrix by myself from what I knew of 3d math, and how matrices worked.. so if I’ve got it wrong please do post a comment, so I can beat myself with a large stick.

Show Me the Coding!!!

Ok, now we have done the obligatory foray into the math behind the workings of the er.. game.. thingame (which is incedently the hardest part), we can now look at the client side networking code, which is actually what this entry is all about.

The general process that the client performs is as follows

  • Connect to the server using a LOC_Connect message
  • Recieve a LOC_AcceptConnection message, and store the given MobID for future use
  • Every 100ms or so update the location, orientation, and velocity of the current MobID using the LOC_UpdateMobLocation message
  • Every so often check for new LOC_MobLocation messages and update the relevant mob from the list
  • Attempt to update the screen to maintain a 30fps frame rate

Connecting to the Server

Connecting to thw server is done in two parts. First, the client will send a LOC_Connect message to the server, using the code below:

public void Connect(String address, int port)
{
   MemoryStream stream = new MemoryStream((int)LOC_ConnectMessage.MessageSize);
   BinaryWriter writer = new BinaryWriter(stream);

   if (m_Connection != null) return;

   // connect to the server
   m_Connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
   m_Connection.NoDelay = true; // disable nagle algorithm
   m_Connection.Connect(address, port);

   // send the connect message
   Random r = new Random();
   writer.Write((Byte)ProtocolCommand.LOC_Connect);
   // just send a random session id, the server doesnt use this right now
   writer.Write((int)r.Next());
   m_Connection.Send(stream.GetBuffer());
}

Note: On line 1 I am specifying the size of the stream to create, in the MemoryStream constructor (new MemoryStream((int)LOC_ConnectMessage.MessageSize);). If you don’t do this, the buffer size that you end up with when you pull it out using the GetBuffer method will be too large, which will screw up both the client and server communications. This is also one of the bugs that were fixed in the server app.

Next, the client will recieve the LOC_AcceptConnection message and store the MobID for use in future location update messages.

if (m_Connection.Available >= (LOC_ConnectMessage.MessageSize))
{
   buffer = new byte[LOC_ConnectMessage.MessageSize];
   m_Connection.Receive(buffer);

   reader = new BinaryReader(new MemoryStream(buffer));
   reader.ReadByte();
   m_MyMobId = reader.ReadInt32();

   m_NextMessage = 0x00;
   bProcessed = true;
   Connected(this, new EventArgs());
}

After the read loop (see next section) determins that the next message being recieved is a LOC_AcceptConnection message, the available data is checked, and the AcceptConnection message is pulled off the socket. We then read a single byte (with which we do nothing, because it is the actual command), and store the MobID in m_MyMobId. After this, the Connect event is fired so that other parts of the program (such as the main form) can react accordingly.

The Read Loop

The read loop of the client is slightly different to the server’s one. This is actually more due to the fact that I found a part of the Socket.Recieve method that allowed me to peek (look at, without consuming) the incoming data.

if (m_Connection.Poll(100, SelectMode.SelectRead))
{
   bProcessed = true;
   bNextMessage = 0; // no message
   // keep looping until no more messages can be processed
   while (bProcessed)
   {
      bProcessed = false;

      if (m_Connection.Available > 0)
      {
         buffer = new byte[1];

         // peek at the invcoming data using SocketFlags.Peek
         m_Connection.Receive(buffer, 0, 1, SocketFlags.Peek);
         bNextMessage = buffer[0];
      }

      // check which message is being recieved and react accordinly
      switch ((ProtocolCommand)bNextMessage)
      {
         case ProtocolCommand.LOC_AcceptConnection:
            ...
         case ProtocolCommand.LOC_MobLocation:
            ...
      }
   }
}

The code above can be found in the MovementClient.Iterate method. Each time the Iterate method is called (about once every 500ms), this code checks if there is any data available on the socked using the Socket.Poll method, then attempts to peek at the first byte that’s available. The first byte should always be either a LOC_AcceptConnection message, or a LOC_MobLocation message.

Updating the Locations

There are two parts to the locations being updated. One is sending updates for your mob to the server, and the other is recieving the updates for all the other mobs.

The MovementClient.SendMobData method takes care of sending the updates to the server. It’s a simple package and send sort of function, similar to what you saw befor with the Connect method, but bigger.

public void SendMobData(MobData data)
{
   MemoryStream stream = new MemoryStream((int)LOC_UpdateMobLocationMessage.MessageSize);
   BinaryWriter writer = new BinaryWriter(stream);

   writer.Write(Convert.ToByte(ProtocolCommand.LOC_UpdateMobLocation));
   writer.Write(data.MobId);
   writer.Write(data.Location.X);
   writer.Write(data.Location.Y);
   writer.Write(data.Location.Z);
   writer.Write(data.UpVector.X);
   writer.Write(data.UpVector.Y);
   writer.Write(data.UpVector.Z);
   writer.Write(data.RightVector.X);
   writer.Write(data.RightVector.Y);
   writer.Write(data.RightVector.Z);
   writer.Write(data.Velocity.X);
   writer.Write(data.Velocity.Y);
   writer.Write(data.Velocity.Z);

   try
   {
      m_Connection.Send(stream.GetBuffer());
   }
   catch (Exception) { ;}
}

You have probably noticed that the Socket.Send function is enclosed in a try/catch routine. This is mainly because I’ve not bothered about good programming, and making sure that I actually have a good connection prior to sending. I’ve made it so that it just fails silently, not bothering me or anyone else in the process. This is actually a problem with my coding style, where I indend on getting back to it and fixing it, but in the mean time, I want it to work without bothering me :)… then it never bothers me again.. then something screws up.. then it takes many days to find where the problem is… when I can be bothered I’ll write some lines for my sins.. “I will use caution when programming. I will use caution when programming. I will use caution when programming. I will use caution when programming.”

The next bit handles incoming location updates from the other clients. You may have noticed the case ProtocolCommand.LOC_MobLocation in the read loop section. This is where we handle the incoming location updates. Again, it is a simple unpack / alert routine that you are seeing.

if (m_Connection.Available >= (LOC_UpdateMobLocationMessage.MessageSize))
{
   buffer = new byte[LOC_UpdateMobLocationMessage.MessageSize];
   m_Connection.Receive(buffer);

   reader = new BinaryReader(new MemoryStream(buffer));

   message = new LOC_UpdateMobLocationMessage();
   message.Command = reader.ReadByte();
   message.MobId = reader.ReadInt32();
   message.Location.X = reader.ReadDouble();
   message.Location.Y = reader.ReadDouble();
   message.Location.Z = reader.ReadDouble();
   message.UpVector.X = reader.ReadDouble();
   message.UpVector.Y = reader.ReadDouble();
   message.UpVector.Z = reader.ReadDouble();
   message.RightVector.X = reader.ReadDouble();
   message.RightVector.Y = reader.ReadDouble();
   message.RightVector.Z = reader.ReadDouble();
   message.Velocity.X = reader.ReadDouble();
   message.Velocity.Y = reader.ReadDouble();
   message.Velocity.Z = reader.ReadDouble();

   bNextMessage = 0;
   bProcessed = true;
   UpdateMobLocation(message);
}

Similar to the accept connection routine, it simply pulls the appropriate amount of data out of the socket using the Recieve method, then reads the information in the correct order, just like if we stored the data in a binary file. Finally the UpdateMobLocation event is fired so that the main form can keep track of the unit in question.

It’s probably worth having a look at the code from the main form in this circumstance, as some of the logic is also performed here. Below you will find the function that is called when the MovementClient.UpdateMobLocation event is fired.

void OnUpdateLocation(LOC_UpdateMobLocationMessage message)
{
   MobileUnit mob = null;
   Vector3d vUp, vRight, vLoc, vVel; 

   // only make changes if the mob is not the current client
   if (message.MobId != m_Client.MobId)
   {
      if (m_UnitList.ContainsKey(message.MobId))
      {
         mob = m_UnitList[message.MobId];
      }
      else
      {
         mob = new MobileUnit();
         m_UnitList.Add(message.MobId, mob);
      }

      // get the new vector sets
      vUp = new Vector3d(message.UpVector.X, message.UpVector.Y, message.UpVector.Z);
      vRight = new Vector3d(message.RightVector.X, message.RightVector.Y, message.RightVector.Z);
      vLoc = new Vector3d(message.Location.X, message.Location.Y, message.Location.Z);
      vVel = new Vector3d(message.Velocity.X, message.Velocity.Y, message.Velocity.Z);

      mob.UpdateMatrix(vUp, vRight, vLoc, vVel);
   }
}

Basically, I’m storing all of the mobs that aren’t the current client in a Dictionary object. This way I can pull a MobileUnit object, given a single key. The reason we dont want to store the current client’s info in this list, is that each client is actually the authority on where their own mobs are, so we would not want to overwrite this information with older or inaccurate data.

Please Stop! My Eyes Hurt!

So, we’ve come to the end of my little foray into the dephts of network programming. What’s next? you may ask. What could possibly make this code better?

There are a good number of things that have been left un-done, mainly due to laziness on my own part. Here’s a list of what could be done in the future, and you may notice that I’ve copied stuff from my last post (muy perezoso! wtf?! spanish?)

  • Handle Client disconnects better, rather than just ignoring them
  • Send a message to the clients when someone disconnects, so that they know to remove a mob (LOC_Disconnect)
  • Perform more checks on the api calls to ensure that there are no errors, such as socket already in use
  • Allow users to specify the port that they want to run the server on
  • Allow users to specify the port that they want to connect to the server on (from the client)
  • Add a Rotation vector to the LOC_MobLocation and LOC_UpdateMobLocation messages, so that we can track turning smoothly
  • Re-write the server so that is better, stronger, faster

Source Code: Download

Binaries: Download

Reporting Services - Matrix Control

Posted on March 6, 2008 by Chris at 10:37 am

When writing reports for my various work projects, I’ve always tried to work as fast as possible, and tend to ignore the things that don’t make sense strait off the bat. These things tend to take a lot more time to learn and figure out. I never really have a lot of time, so I find another way that is faster at the time.

One of the things that I never got to play with was the Matrix control in the Microsoft reporting services. It seemed logical enough, but every time I looked at using it, the damed thing never worked, so I went and used the List control instead. This time, I was writing a report that needed to have a different number of columns depending on the number weeks in the month being reported on. So, naturally, I thought that the Matrix control would work well.

Here’s an example of what the data that I started with looks like. I was able to pull this out of my tables with a (relatively) simple sql statement.

Next I threw the Matrix control on a new report, and using the report printer that I prepared earlier in Microsoft SQL Server Reporting Sevices, I got a blank report printing out to pdf (yay!).

After a little trial and error, which I had time for this time (accounts REALLY wants this report), I finally found the Groups tab on the Properties page of the Matrix control. This was the key to getting it to work correctly. After that, it was clear sailing.

As you can see, I’ve added two Row Groups, and 1 Column group. The row groups determine what is shown in each row, while the column groups determine how what is shown in the columns. I know, it sounds like I’m doing the dummies guide, but really I do remember thinking that exact line while actually doing the work, followed by “ooh, so that’s how it works”.. guess I really am a dummy.

Anyhoo, this is what the relevant part of the group edit screen looks like. Incedently, the row groups were on section name, then staff name.

Then I added the field values into the grid items that were available, and after a little trial and error, came out with this tiny, little thing.

Although it’s tiny, it has big results.. take a look.. ooh pretty…