Archive for October, 2005

Is it just me or…?

Saturday, October 29th, 2005

Anybody else get the feeling that somebody is stealing all of their good ideas? Ever since about the fourth grade I have been having some very random creative spark moments where I will get an awesome idea and tell a bunch of people about it, and then somehow two months later it shows up in a movie or a commercial or a video game and someone else is making a million from it. Now I am not saying that every idea I have ever had is an amazing one or that they are all being stolen, but I can remember quite a few (I won’t list them ’cause you won’t believe me anyway.)

The reason I ask is this: I am trying to someday become a game designer. I want to make games for a living, and not the way that every other goddamned Computer Science student in the world does, I am actually doing something about it (as some of you have already noticed from reading my 3D Engine tutorials.) Since I am trying so hard to get to the point of making games for a living, I spend much of my time thinking about different games I could design and how they would play out.

Tonight I was playing x-box and decided to come upstairs to do a little computer work, and just as I shut the game off and started to head to the microwave to grab my newly cooked bowl of beef ramen I got the greatest idea for a game. Wouldn’t it be great if there was a game in the *** genre where you *** for *** in the ***. Sweet idea huh? Exactly… I can’t say a damn word about it. I’m even afraid to type it in a document on my computer ’cause I don’t want another million dollar idea being taken. Especially now that I am actually capable of producing said idea and am not simply a 12 year old grade schooler.

Maybe I’ll tell you what it is when I have copyrighted it and gotten it half done. Maybe… maybe… Maybe not…

Making our Skybox act like a Skybox

Wednesday, October 19th, 2005

Now that we have a fully working camera (at least one we can use to move around and look at things) I have noticed that our skybox isn’t exactly acting like a real skybox should. A real skybox would follow the camera around so that it was always in the background of the scene being looked at, and it would also be much larger. These two changes are actually quite easy to implement.

Step 1: Making Our Skybox Bigger

For starters we can change the value in the skybox class for the size to be a much larger number since it has only been at 10 so far. Instead of changing the value of all of the vertices in the skybox class, we can more easily change the scaling value that is inherited from the HMObject class. This can be accomplished by adding the following code in the demo class:

sky1.SetScaling(new Vector3(100, 100, 100));

Step 2: Rendering at the Correct Position

Now, similar to the changes that we had to make to the Mesh class to take the transformation vectors into account, we must now add the functionality to do so to not only the skybox class, but to the VertexGroup class that it uses to render each of its sides. Here are the changes we have to make to the code to get all these transformations passed to their correct places and rendering correctly:

// In the HMObject class public virtual void SetPosition(Vector3 newPosition) { myPosition = newPosition; } public virtual void SetRotation(Vector3 newRotation) { myRotation = newRotation; } public virtual void SetScaling(Vector3 newScaling) { myScaling = newScaling; } // In the HMSkybox class public override void SetScaling(Vector3 newScaling) { foreach(HMVertexGroup vg in faces) { vg.SetScaling(newScaling); } } // In the HMVertexGroup class public override void Render(Device myDevice) { myDevice.Transform.World = Matrix.Scaling(myScaling) * Matrix.RotationYawPitchRoll(myRotation.Y, myRotation.X, myRotation.Z) * Matrix.Translation(myPosition); myDevice.VertexFormat = CustomVertex.PositionTextured.Format; myDevice.SetTexture(0, myTexture); myDevice.SetStreamSource(0, vb, 0); myDevice.Indices = ib; myDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, verts.Length, 0, inds.Length / 3); }

Step 3: Following the Camera Around

We will also need to make our skybox follow the camera around so it is always in the background and seems to be much farther away than it truly is. This is accomplished by changing the translation value for the skybox in the rendering function to the current position of the camera. To do this we will need to pass our camera into the Render function along with the device.

We will have to change the Render function for all of our object types and the call in our Scene class, but this is not that much trouble. Once we have gotten our camera passed on from the scene to each of our objects’ rendering functions, we can simply add a call to the skybox’s VertexGroup.SetPosition prior to each render so that it will adjust the boxes position when needed each frame. Here is what the code in HMSkybox looks like:

public override void Render(HMCamera myCamera, Device myDevice) { foreach(HMVertexGroup vg in faces) { vg.SetPosition(myCamera.Position); vg.Render(myCamera, myDevice); } }

Now our skybox is acting as it should, and if we need to change its size to accomodate a much larger world we can easily do so by changing one scaling value on the main skybox and it will automatically be adjusted for each of its parts and rendered correctly.

Picking Objects from 3D Space

Tuesday, October 18th, 2005

Now that we have the ability to put objects in space and move around them, it would also be nice to be able to choose which object we are focussed on. One method of doing this would be to click on the object on the screen and have the camera refocus itself around that object. This method of choosing an object from the screen with the mouse is called picking. We will implement it in a 3 step process.

Step 1: Setting up the Framework for Picking

The first thing we need to do is have some input from the mouse to play with and see if an object in our scene was clicked on so we will make some general functions for each of the 3 steps we are going to take. The first part of picking is simply getting the mouse clicks and sending them on to our scene. This code will go in the demo code and should look something like this:

// In the Main method demo1.MouseDown += new MouseEventHandler(demo1_MouseDown); // New function below Main static void demo1_MouseDown(object sender, MouseEventArgs e) { demo1.MyScene.Pick(e, demo1.MyCamera, demo1.MyDevice); }

Step 2: Getting the Scene to Pick all of our Objects

Logically this leads us to writing the next part of the picking function, converting the 2D point into a 3D ray by unprojecting it using an inverse matrix we will create by taking a few settings from our camera and video card. Here is the logic and code to accomplish what turns out to be a slightly lengthy group of actions:

public bool Pick(MouseEventArgs mouseEvent, HMCamera myCamera, Device myDevice) { // Get most of our placeholders for information ready Point mouseLoc = mouseEvent.Location; bool pickFound = false; IntersectInformation intInf; Vector3 rayStart, rayDirection; Matrix proj = myCamera.Projection;

Now we will convert the mouse click location into a 3D position in our world using the screen width and some simple algebra to move the center of reference to the middle of the screen instead of the top left corner where the original mouse point is taken from.

// Convert the mouse point into a 3D point Vector3 v; v.X = (((2.0f * mouseLoc.X) / myDevice.PresentationParameters.BackBufferWidth) – 1) / proj.M11; v.Y = -(((2.0f * mouseLoc.Y) / myDevice.PresentationParameters.BackBufferHeight) – 1) / proj.M22; v.Z = 1.0f;

Now, using the inverse of the matrix we normally use to project our 3D points, we will unproject our point to get a ray that shoots into the screen from the point we clicked and store it in two vectors, one for the ray’s initial point, and one for its direction

// Get the inverse of the composite view and world matrix Matrix m = myCamera.View * myCamera.World; m.Invert(); // Transform the screen space pick ray into 3D space rayDirection.X = v.X * m.M11 + v.Y * m.M21 + v.Z * m.M31; rayDirection.Y = v.X * m.M12 + v.Y * m.M22 + v.Z * m.M32; rayDirection.Z = v.X * m.M13 + v.Y * m.M23 + v.Z * m.M33; rayStart.X = m.M41; rayStart.Y = m.M42; rayStart.Z = m.M43;

Now comes the fun part. We must loop through all of the objects in our scene to find out which ones were clicked on (some may be behind others) and save the distances from the camera viewport so we know that the closer object was the one clicked on the screen, to store which object was clicked on, I created an instance variable at the top of our HMScene class: public HMObject activeObject;

This will allow us to access the object outside of the scene and manipulate some of our camera variables to change whenever a new object is clicked on. Here is what the code looks like for looping through our objects and saving the closest one:

float minDistance = float.MaxValue; for(int i = 0; i < WorldObjects.Count; i++) { HMObject o = (HMObject)WorldObjects[i]; if(o.Pick(rayStart, rayDirection, out intInf) && intInf.Dist < minDistance) { pickFound = true; activeObject = o; } } return pickFound; }

Now that we have our scene class looping through and calling our objects’ Pick method, we should probably write that method. For now we will just write the code for a mesh since a lot of the intersection math is handled by DirectX, but we will add other checks later so we can reuse our pick functionality to check things like GUI windows or other types of objects that might be added later on.

Step 3: Checking if an Object was Picked

The method for finding out whether an object was hit by the ray is much simpler to implement because DirectX does a lot of this for us. All we have to do is convert the ray into the local coordinates of the model we are checking (that is, unproject the ray using the model’s scaling, rotation, and translation matrices) and have the built in Mesh.Intersect function tell us whether we have hit home or not. Here is what this looks like:

public override bool Pick(Vector3 rayStart, Vector3 rayDirection, out IntersectInformation intInf) { // Convert ray to model space Matrix World = Matrix.Scaling(myScaling) * Matrix.RotationYawPitchRoll(myRotation.Y, myRotation.X, myRotation.Z) * Matrix.Translation(myPosition); Matrix inverseWorld = Matrix.Invert(World); Vector3 localStart = Vector3.TransformCoordinate(rayStart, inverseWorld); Vector3 localDirection = Vector3.TransformNormal(rayDirection, inverseWorld); localDirection.Normalize(); // Check for mesh intersection return myMesh.Intersect(localStart, localDirection, out intInf); }

That’s it! Now our engine will successfully test any mesh based objects that we load and report back with a true or false whether that object was clicked on or not. It will also set the clicked on object to be active in the scene so we can access it and play with other things (like camera focus) once we know what was clicked, which is how I will finish up this tutorial

Step 4: Putting our Picking to Good Use

Just so we can get an idea of how to put picking together with our current camera and see what we can do with just the few simple things we have learned and implemented so far, I decided to add a few meshes to the scene and allow the user to click a mesh and have the camera instantly focus on that mesh. To do this, we will only need to add a bit of code to our Demo class that will handle changing the camera’s radius so it is adjusted to the new object’s distance as well. We will also need to add a function in our camera to let us set a new focus point. Here is what the camera methods will look like:

public void SetTarget(Vector3 newTarget) { target = newTarget; } public void SetRadius(float newRadius){ radius = newRadius; } // Also remember to either make the location of HMObjects and the HMCamera public // or to make a property in each of these that will give you public access to them public Vector3 Position { get { return position; } }

The new code in the demo will look like this:

static void demo1_MouseDown(object sender, MouseEventArgs e) { if(demo1.MyScene.Pick(e, demo1.MyCamera, demo1.MyDevice)) { Vector3 objPosition = demo1.MyScene.activeObject.Position; Vector3 camPosition = demo1.MyCamera.Position; demo1.MyCamera.SetTarget(objPosition); demo1.MyCamera.SetRadius(Vector3.Length(objPosition – camPosition)); } } … HMMesh mesh1 = new HMMesh(“tiger.x”, new Vector3(0,0,0), new Vector3(), new Vector3(1, 1, 1), demo1.MyDevice); HMMesh mesh2 = new HMMesh(“tiger.x”, new Vector3(-5,0,5), new Vector3(), new Vector3(1, 1, 1), demo1.MyDevice); HMMesh mesh3 = new HMMesh(“tiger.x”, new Vector3(5,0,5), new Vector3(), new Vector3(1, 1, 1), demo1.MyDevice); demo1.MyScene.AddObject(mesh1); demo1.MyScene.AddObject(mesh2); demo1.MyScene.AddObject(mesh3);

Now if we run the demo we are able to click on any of our 3 tigers and have the camera fly around any of them. The motion of the camera is a bit choppy when changing targets, which is because of the way we update the camera’s position each frame. We will make this much smoother in the future when we change the camera’s motion with a scripting system that will be implemented in a later tutorial.

Converting the DirectX timer for easy use

Monday, October 17th, 2005

Almost every aspect of a video game uses a timer. Particle systems use them to calculate the motions and new positions of every one of the thousands of little sprites they contain, character animations user them to start and stop the motions and syncronize with other objects in the world. Camera scripts can use them to create video panning effects other cinemetography tricks. All in all a timer is probably one of the most important aspects of any game, and it is critical that th timer you use in your engine is versatile and easy to use. For this I have found it best to take the timer that comes with the DirectX SDK utllities and to simply convert it to be a class of its own so we can use as many instances of it as we need.

Step 1: Retreiving the code from dxmutmisc.cs

The first thing we need to do is find the file we are going to be taking our timer code from, it is ususally located in the managed samples that come with the SDK here: C:\Program Files\Microsoft DirectX 9.0 SDK (October 2005)\Samples\Managed\Common\dxmutmisc.cs. Open this file up and look around at all the wonderful utilities the people at Microsoft have provided us with by default. What we need is located between the #region Timer / #endregion. I am simply going to start by copying that section of code and pasting it into an empty HMUtility.cs file. The code will look like this:

#region Timer public class FrameworkTimer { #region Instance Data private static bool isUsingQPF; private static bool isTimerStopped; private static long ticksPerSecond; private static long stopTime; private static long lastElapsedTime; private static long baseTime; #endregion #region Creation private FrameworkTimer() { } // No creation /// <summary> /// Static creation routine /// </summary> static FrameworkTimer() { isTimerStopped = true; ticksPerSecond = 0; stopTime = 0; lastElapsedTime = 0; baseTime = 0; // Use QueryPerformanceFrequency to get frequency of the timer isUsingQPF = NativeMethods.QueryPerformanceFrequency(ref ticksPerSecond); } #endregion /// <summary> /// Resets the timer /// </summary> public static void Reset() { if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); baseTime = time; lastElapsedTime = time; stopTime = 0; isTimerStopped = false; } /// <summary> /// Starts the timer /// </summary> public static void Start() { if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); if(isTimerStopped) baseTime += (time – stopTime); stopTime = 0; lastElapsedTime = time; isTimerStopped = false; } /// <summary> /// Stop (or pause) the timer /// </summary> public static void Stop() { if(!isUsingQPF) return; // Nothing to do if(!isTimerStopped) { // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); stopTime = time; lastElapsedTime = time; isTimerStopped = true; } } /// <summary> /// Advance the timer a tenth of a second /// </summary> public static void Advance() { if(!isUsingQPF) return; // Nothing to do stopTime += ticksPerSecond / 10; } /// <summary> /// Get the absolute system time /// </summary> public static double GetAbsoluteTime() { if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); double absolueTime = time / (double)ticksPerSecond; return absolueTime; } /// <summary> /// Get the current time /// </summary> public static double GetTime() { if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); double appTime = (double)(time – baseTime) / (double)ticksPerSecond; return appTime; } /// <summary> /// get the time that elapsed between GetElapsedTime() calls /// </summary> public static double GetElapsedTime() { if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); double elapsedTime = (double)(time – lastElapsedTime) / (double)ticksPerSecond; lastElapsedTime = time; return elapsedTime; } /// <summary> /// Returns true if timer stopped /// </summary> public static bool IsStopped { get { return isTimerStopped; } } } #endregion

Now this looks a bit messy, but there really isn’t much we will want to do to clean it up as almost all of the code is required for the timer functions to work correctly. One thing we do need to do however is retrieve the NativeMethods.QueryPerformance(Counter/Frequency) function so we can actually compile the timer. This is in the same file the timer was in in the NativeMethods/Windows API Calls section, and it looks like this:

[System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency); [System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceCounter(ref long PerformanceCount);

Now if we remove the word NativeMethods from in front of each of these calls the class will compile successfully. You could simply leave the code as it is now and be able to use the timer exactly the same way that the DirectX SDK demos and samples do, but I prefer to have an instanceable class so I can more easily keep track of which timer I am using. Converting this timer to a non-static one is simply a matter of removing all of the static declarations in front of the methods and instance variables. I am also going to change the comment structure a bit to save some space here in the tutorial, but feel free to leave the XML formatted comments as they are if you would rather. Here is what the final converted timer looks like:

using System.Runtime.InteropServices; namespace HMEngine { public class FrameworkTimer { private bool isUsingQPF; private bool isTimerStopped; private long ticksPerSecond; private long stopTime; private long lastElapsedTime; private long baseTime; public bool IsStopped { get { return isTimerStopped; } } // Returns true if timer stopped [System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency); [System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceCounter(ref long PerformanceCount); public FrameworkTimer() { isTimerStopped = true; ticksPerSecond = 0; stopTime = 0; lastElapsedTime = 0; baseTime = 0; // Use QueryPerformanceFrequency to get frequency of the timer isUsingQPF = QueryPerformanceFrequency(ref ticksPerSecond); } public void Reset() { // Resets the timer if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); baseTime = time; lastElapsedTime = time; stopTime = 0; isTimerStopped = false; } public void Start() { // Starts the timer if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); if(isTimerStopped) baseTime += (time – stopTime); stopTime = 0; lastElapsedTime = time; isTimerStopped = false; } public void Stop() { // Stop (or pause) the timer if(!isUsingQPF) return; // Nothing to do if(!isTimerStopped) { // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); stopTime = time; lastElapsedTime = time; isTimerStopped = true; } } public void Advance() { // Advance the timer a tenth of a second if(!isUsingQPF) return; // Nothing to do stopTime += ticksPerSecond / 10; } public double GetAbsoluteTime() { // Get the absolute system time if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); double absolueTime = time / (double)ticksPerSecond; return absolueTime; } public double GetTime() { // Get the current time if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); double appTime = (double)(time – baseTime) / (double)ticksPerSecond; return appTime; } public double GetElapsedTime() { // Get time elapsed between GetElapsedTime() calls if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); double elapsedTime = (double)(time – lastElapsedTime) / (double)ticksPerSecond; lastElapsedTime = time; return elapsedTime; } } }

A bit cleaner wouldn’t you say? Well, maybe not, but I sure think it is more readable this way. Anyway, now that we have a timer that we can use, why don’t we get to using it? I will put together a simple frames-per-second counter now just so you can see the timer in action.

Step 2: Using our Timer for FPS

The first thing we will need to do is create a new instance of our timer in the HMSystem code so we can use it to time the seconds that elapse between frames. This code is pretty simple, but I’ll show you where I put it anyway:

private Device myDevice; private HMCamera myCamera; private HMScene myScene; private FrameworkTimer myTimer; // Variables for FPS calculations/drawing private Microsoft.DirectX.Direct3D.Font frameFont; private int framesPassed; private float timeElapsed; private float fps; private int frameResetValue = 10; … if(InitializeGraphics()) { myCamera = new HMCamera(this.Width, this.Height); myScene = new HMScene(); myTimer = new FrameworkTimer(); myTimer.Start(); frameFont = new Microsoft.DirectX.Direct3D.Font( myDevice, new System.Drawing.Font(“Courier New”, 16, FontStyle.Bold) ); }

We are also going to need a function that acutally calculates this FPS value. There are many ways to do this, but I have found that one of the easiest and least buggy methods is to simply keep track of a number of frames and the amount of time that has passed since they have gone by and divide them. We also need to have the video card draw our FPS on the screen, so we will write a small function for that as well. Here are the two functions:

private void CalculateFPS() { framesPassed++; timeElapsed += (float)myTimer.GetElapsedTime(); if(framesPassed > frameResetValue) { fps = (float)framesPassed / (float)timeElapsed; framesPassed = 0; timeElapsed = 0; } } private void ShowFPS() { frameFont.DrawText(null, “FPS: “+fps.ToString(), new Point(0, 0), Color.White); }

Now we just need to add the calls to our new functions in the Render method and we will be able to see what speed our video card is rendering frames at. Here is what the last modification looks like:

private void Render(object sender, PaintEventArgs e) { SetDeviceCamera(); CalculateFPS(); myDevice.BeginScene(); myDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.CornflowerBlue, 1.0f, 0); myScene.Render(myDevice); ShowFPS(); myDevice.EndScene(); myDevice.Present(); this.Invalidate(); }

Exciting events in the world of music in the BOZ

Thursday, October 13th, 2005

Well, the concerts went well, if well means I didn’t get any permanent injuries. I only had to tackle/call in arrests on a few people at Danzig and Chevelle, and the Motley Crue concert was fairly laid back since I was just working the smoking area; although, I did see about 10 people get thrown out/escorted to a police car by four of our outstanding Bozeman, MT policemen.

There was only 1 minor personal encounter at Motley Crue and it was just stopping a guy who was bolting to the door from the 4 cops escorting him down the hallway, which wasn’t hard because he was drunk off his ass and he damn near fell down the second something solid got in his way.

All in all it was a very interesting few days. Hopefully the B.B. King concert is a bit more laid back so I can actually enjoy the music in stead of worrying about being chokes by a spiky belt or stabbed by a deer skull.

Those aren’t the kinds of people that will be attending a B.B King concert anyway, so I’m sure I’ll be fine.

Silver Sharpies and B.B. King

Tuesday, October 4th, 2005

My friends Nick and Heather were over at my appartment for dinner the other night, and Heather brought up that she needed people to work security at the Valley Ice Gardens and Brick Breeden fieldhouse for some concerts and I told her I could so it, so this Sunday I will be starting with my first job there working at the Chevelle concert.

Now I am not exactly Chevelle’s biggest fan, nor do I usually listen to their genre of music all that often (I do have slightly odd taste in music after all) but I’m sure it will be a good concert at any rate. The one I am really excited about is the B.B. King concert coming up on October 21st, where I will without a doubt be having the time of my life. I am hoping to be able to go backstage (or work back there) and meet B.B. and possibly get him to slap a big signature on my guitar with a silver sharpie.

Guess we’ll have to wait and see how lucky I am that day. Hope it goes as planned. I’ll definately post some pictures on here within a few days after the concert.