Sunday, April 19, 2009

Boxes, Cylinders, and Spheres (Oh My!)



After deciding to use PhysX in combination with DirectX 11 last week, I thought about how I would go about drawing simple 3D shapes. In the samples and lessons, PhysX uses GLUT with has some shape drawing functions built-in. While DX9 has similar functions, neither DX10 nor XNA do. I believe it is safe to assume that DX11 will not have them either. I can understand why they were removed. They don't really make sense anymore in a programmable pipeline system. What is the vertex format of the shape created?

While most people these days get around it by creating a mesh in a 3D modeling application and then importing it into their game, I prefer to have my simple 3D shapes constructed programatically. This allows me to easily change the detail of the mesh without worrying about exporting/importing an entire new model.

Instead of implementing it in DX10 and C++ first, I decided to implement it in XNA. This allowed me to focus on the algorithms themselves and not have to worry about any C++ issues getting in the way.

My initial implementation was a static class that had static methods in it that built the shapes and returned the vertex buffer and index buffer. This worked well, but I thought it still placed too much burden on the user to remember how many vertices they had just created and how many triangles they were going to draw.

In order to make things simpler, I changed it to be a normal, instantiated class that contained the static methods that created an instance of the class. This allowed me to store the vertex count, triangle count, as well as the buffers in instance properties in the class. I even threw in a Draw method to make it incredibly simple to use the generated shape.

Because this is such a basic, fundamental class that can benefit several people ( have seen various people requesting something like this), I have decided to share it with the public.

You may download the C# file from here:
http://re-creationstudios.com/shared/Shape3D.cs

You can also download the entire test project here:
http://re-creationstudios.com/shared/ShapeTest.zip

To use, simply create a shape using one of the static methods:
Shape3D sphere = Shape3D.CreateSphere(GraphicsDevice, 1, 12, 12);

To draw, in your Effect block, call the Draw method on the shape:
sphere.Draw(GraphicsDevice);

The Create methods are made to follow the same structure as the DX9 shape drawing functions. You can read more about those here:
http://msdn.microsoft.com/en-us/library/bb172976(VS.85).aspx

8 comments:

Max said...

I'm having a problem tossing this in. The problem derives from the use of the graphicsdevice. I have instantiated a graphicsdevicemanager, and a graphicsdevice. However, when I throw the graphicsdevice that I have to your class, it throws an "A field initializer cannot reference the non-static field, method, or property "whatever.device". Any suggestions?

Patrick said...

Where are you calling the Create method? It sounds like your GraphicsDevice isn't fully initialized yet, so I'm guessing you are calling the Create in the Game constructor. You should be calling it in the LoadContent method. That way you can be certain that everything in terms of XNA is initialized.

Let me know if you have more issues.

Max said...

In the constructor I have
device = graphics.GraphicsDevice;

Then in the load routine I have


Shape3D cylinder = Shape3D.CreateCylinder(device, 1, 12, 12, 3, 3);

and it is throwing
"an object reference is required for the non-static field, method, or property '3dShape.CreateCylinder....etc"

Patrick said...

Well the problem is the GraphicsDevice property of the GraphicsDeviceManager is not yet initialized in the Game constructor. If you look at your device variable, you will probably see that it is null. Instead of saving it into your own variable, how about you use the public property GraphicsDevice on the Game class. Then, you don't need that line in your constructor and your line of code in the LoadContent() method would just be:

Shape3D cylinder = Shape3D.CreateCylinder(GraphicsDevice, 1, 12, 12, 3, 3);

Max said...

Thanks for your help. I tried to do what you said (which I'm sure is why you had it in the code in the first place), but for some reason with XNA 3.1, it isn't working. I don't know that it is the version, but when I try to do what you said to do, it again gives me the "an object reference is required for the non-static field, method or property'...CreateCylinder...'

I will keep trying to make it work, maybe i'm never initializing the public property.

Patrick said...

Well I wrote this against XNA 3.0. I'll install 3.1 and see if that changed anything with regards to the GraphicsDevice. That public property should be automatically initialized, at least it is in 3.0.

I post another comment when I have it working in 3.1.

Patrick said...

Well I installed XNA 3.1 and cobbled together a quick project that worked fine.

I decided zip up my test project that I used to render the images in the blog post and put it up on my website.

Download it here:
http://re-creationstudios.com/shared/ShapeTest.zip

You should be able to just open it up and run it. You can then see how all of the shapes are created and used.

Max said...

Thanks again for all the help. I really appreciate it. I'm sure I will be able to figure out what I'm doing wrong now. I have never seen such prompt response from a blogger before. Really appreciate it.