Home > Tutorials > Displaying 3D Objects
1.2. Creating Your First Scene
In this tutorial you will continue to learn the basic aspects of BetaCell by creating a simple scene that rotates.



Click here to go to the forum discussion of this tutorial


 

1.2.1. Prerequisites
default.jpg
Before you start this tutorial make sure that you have:
  • Installed XNA Game Studio 3.1
  • Downloaded the starter project from the downloads section
  • Successfully run the starter project
   
 

1.2.2. The using statements
The using statements informs XNA that we are going to need some code; in this tutorial, this code is given by the following lines:

using BetaCell.Environment;
using BetaCell.Environment.Mesh;
using BetaCell.Environment.Mesh.Visitors;
using BetaCell.Behavior.Procedural;
using BetaCell.Util.Procedural;
using BetaCell.Common.Vertex
;


 

1.2.3. Game Attributes 
The attributes that come with the starter project are explained in tutorial section 1.1.4.

In this tutorial we are going to do an actual scene, not just some static spheres, in order to do that we are going to need some attributes:

BCMesh sphere;
BCMesh cylinder;
BCMesh plane;

BCProceduralRotator rotator;
 
The first three attributes are meshes [1.1.4], we are going to create just one of each shape and reuse it by drawing it many times to achieve the desired scene.

The fourth attribute is an object that encapsulates the information necessary to rotate a scene.

 

1.2.4. The Initialize method 
Besides the initialization work done in the starter project [1.1.6], this method creates the scene and initializes the rotator.

In the create scene method, we create an effect that will paint black the lines of the meshes [1.1.6] with the following call:

BCColorSet colorSet = new BCColorSet(Color.Black);
BCDynamicEffectVisitor colorSetter = new BCDynamicEffectVisitor(colorSet.getFunction("Shader.ColorSet"), colorSet, 0);

default.jpg
Then the plane is created using the ProceduralModelers [1.1.6], The call to create the plane is:

plane = ProceduralModelers.PLANE_MODELER.createPlane
    (4, 4, 25, 25, new Vector3(0, 0, 0),
        new BCVertexPosContainer(), 1);


Where the first parameter is the number of vertices in the X axis, the second parameter is the number of vertices in the Y axis, the third parameter is the length of each column, the fourth parameter is the height of each row, the fifth parameter is the center of the plane, the sixth parameter is the vertex container, which will be discussed later in the tutorials and the last parameter is the texture scale, which will also be discussed later. 

Then the sphere is created [1.1.6], after that the cylinder is created with the following call:

cylinder = ProceduralModelers.CYLINDER_MODELER.createCylinder(
                                    5, 6, 1, 1,
new BCVertexPosContainer());
 

default.jpg
The first parameter is the number of divisions in a log2(n) fashion, the second parameter is the length of the cylinder, the third parameter is the radix of one of the caps of the cylinder, the fourth parameter is the radix of the other cap, finally, the fifth parameter is the vertex container , which will be discussed on another tutorial.

default.jpg
Now we are going to elaborate further on the visit method of the mesh. A mesh doesn't have directly the vertices and faces that make the shape, instead, a mesh is a collection of parts, in the example image, the mesh can have 3 parts, the body part, the accessories part and the weapons part. Each part does have the vertices and faces.

If we want all the parts to have the same effect, we would need to iterate over the parts and assign them the effect, but what if we want all parts to have the same material?, ok we iterate over the parts and assign them the material, ok, now I want all the parts to have the same textures, etc.

A year later I have a lot of cycles in my applications and I discover that a hash map is a better way to represent the collection of parts in a mesh; what can I do? Basically you can kiss your hash map idea good bye. Designing software prevents this kind of issues, and the use of design patterns is a powerful tool in software design. In BetaCell, when we want to apply the same operation over all the the parts of a mesh, we use a visitor pattern. A visitor, as it's name implies, visits all the parts of a mesh taking the same action in each; for example, the dynamic effect visitor adds the specified effect part to the mesh's effect.

After the creation of each mesh, the mesh is initialized and the "paint it black" effect is set to all it's parts.

plane.visit(colorSetter);

Now, the rotator is just a class that holds and maintains rotation information over time, heres the line that creates it:

rotator = new BCProceduralRotator(0, 0, 0.1f, 0, 1000);

The first parameter is the radix of the rotator, the second, third and fourth are how much the rotator rotates over the x, y and z axises respectively, the last one is a modulator that makes the rotation slower.


 

Advertisement
 

 1.2.5. The Draw method
The important lines of the Draw method are:

Matrix rotMatrix = rotator.getRotationMatrix(gameTime);

This call obtains the rotation matrix from the procedural rotator, in BetaCell, translations, rotations, scaling and in general all transformations are handled in a matrix level. This doesn't mean that you'll have to learn how to construct a rotation matrix; XNA offers lots of methods to create them

The next step is drawing the scene; first the plane:

Besides the draw call, there's a line that looks a little fishy:

plane.transform = rotMatrix;

A mesh doesn't have a rotation, translation and scaling part; instead it has a more general form of defining transformation data; the mesh has a transformation matrix.

What the previous line states is that the transformation of the plane should be the one described by the rotator object.

Now we will discuss matrix composition.


default.jpg
When a cylinder is created, it's aligned with the Z axis and half of the cylinder is below the XZ plane. We need it pointing upwards and completely above the XZ axis, so we first rotate it PI / 2 (90 degrees).

We can create a rotation matrix with the following XNA code:

Matrix.CreateRotationX((float)Math.PI / 2)

The above rotates an object PI / 2 radians over the X axis.

default.jpg
If we just apply that rotation, the cylinder ends up pointing down. Since the cylinder is 6 units long [1.2.4], we need to translate it 6 units upward; the XNA code that creates a creates such translation is:

Matrix.CreateTranslation(0, 6, 0)

Now we need a way to apply both transforms at once; and it can be achieved by matrix multiplication, so the matrix we need is:

Matrix.CreateRotationX((float)Math.PI / 2) * Matrix.CreateTranslation(0, 6, 0)



The rest of the draw method is just algorithmics, nothing new, you can look at the comments of the drawCylinders and drawSpheres methods.

 

 1.2.6. Conclusion
The most important concept of this chapter is the application of the visitor pattern; if you want more information, take a look at the pattern at the GoF (Design Patterns) book; the application of the pattern isn't an exact copy the patterns are guidelines not rules.

Click here to go to the forum discussion of this tutorial

 

Advertisement
 

1.2.7. Complete source code
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using BetaCell.Debug;
using BetaCell.Util.GlobalInfo.Content;
using BetaCell.Environment.Camera;
using BetaCell.Util.GlobalInfo;

using BetaCell.Environment;
using BetaCell.Environment.Mesh;
using BetaCell.Environment.Mesh.Visitors;
using BetaCell.Behavior.Procedural;
using BetaCell.Util.Procedural;
using BetaCell.Common.Vertex;

namespace Starter
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        //Game attributes
        GraphicsDeviceManager graphics;
        GraphicsDevice device;
        ContentManager content;

        BCFirstPersonHumanCamera camera;
        //end game attributes

        //This tutorial's attributes
        BCMesh sphere;
        BCMesh cylinder;
        BCMesh plane;

        BCProceduralRotator rotator;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8;
            content = new ContentManager(Services);
#if(XBOX360)
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
#else
            graphics.PreferredBackBufferWidth = 852;
            graphics.PreferredBackBufferHeight = 480;
#endif
        }

        protected override void Initialize()
        {
            //base initialization
            device = graphics.GraphicsDevice;

            BCLogger.instance.init(device, content);
            BCInitializationManager.initialize(content);

            OnActivated(null, null);

            recreateScene();

            rotator = new BCProceduralRotator(0f, 0.0f, 0.1f, 0f, 1000.0f);

            base.Initialize();
        }

        private void recreateScene()
        {
            BCColorSet colorSet = new BCColorSet(Color.Black);
            BCDynamicEffectVisitor colorSetter =
                        new BCDynamicEffectVisitor(colorSet.getFunction("Shader.ColorSet"), colorSet, 0);

            //-------------
            //plane
            //-------------

            plane = ProceduralModelers.PLANE_MODELER.createPlane(4, 4, 25, 25, new Vector3(0, 0, 0),
                new BCVertexPosContainer(), 1);
            BCMeshGraphicUtil.init(plane, device);

            plane.visit(colorSetter);

            //-------------
            //sphere
            //-------------

            sphere = ProceduralModelers.SPHERE_MODELER.createSlicedSphere(5, 6, 1,
                new BCVertexPosContainer());
            BCMeshGraphicUtil.init(sphere, device);

            sphere.visit(colorSetter);

            //-------------
            //cylinder
            //-------------

            cylinder = ProceduralModelers.CYLINDER_MODELER.createCylinder(5, 6, 1, 1,
                new BCVertexPosContainer());
            BCMeshGraphicUtil.init(cylinder, device);

            cylinder.visit(colorSetter);
        }


        void buildViewMatrix()
        {
            Vector3 pos = new Vector3(30, 10, 30);
            Vector3 look = new Vector3(-3, -1, -3);
            look.Normalize();

            camera = new BCFirstPersonHumanCamera(look, pos,
                MathHelper.PiOver4,
                (float)this.Window.ClientBounds.Width / (float)this.Window.ClientBounds.Height,
                1f, 200);

            BCGlobalInfo.instance.setMatrix(
                BCGlobalInfo.VIEW_INDEX,
                camera.getViewMatrix(-1)
            );

            BCGlobalInfo.instance.setMatrix(
                BCGlobalInfo.PROJECTION_INDEX,
                camera.getProjectionMatrix(-1)
            );
        }

        protected override void OnActivated(object sender, EventArgs args)
        {
            buildViewMatrix();
            base.OnActivated(sender, args);
        }

        public void dispose()
        {
        }

        protected override void Update(GameTime gameTime)
        {
        }

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.White, 1.0f, 0);

            Matrix rotMatrix = rotator.getRotationMatrix(gameTime);

            device.RenderState.FillMode = FillMode.WireFrame;

            plane.transform = rotMatrix;
            plane.Draw(gameTime);
            drawCylinders(rotMatrix, gameTime);
            drawSpheres(rotMatrix, gameTime);

            device.RenderState.FillMode = FillMode.Solid;

            BCLogger.instance.printFPS(gameTime);
            BCLogger.instance.flush();

            base.Draw(gameTime);
        }

        public void drawCylinders(Matrix rotMatrix, GameTime gameTime)
        {
            Matrix r, t;
            //Creates the compose transformation described i the tutorial
            r = Matrix.CreateRotationX((float)Math.PI / 2) * Matrix.CreateTranslation(0, 6, 0);

            //Draws 12 the cylinders
            for (float z = -30; z <= 30; z += 10)
            {
                //Creates the positional translation of the actual cylinder
                t = Matrix.CreateTranslation(-10.0f, 0.0f, z);
                //Sets the cylinder transformation as the composed transformation described in the
                //tutorial multiplied by the
                //positional translation that was just calculated
                cylinder.transform = r * t * rotMatrix;
                cylinder.Draw(gameTime);

                t = Matrix.CreateTranslation(10.0f, 0.0f, z);
                cylinder.transform = r * t * rotMatrix;
                cylinder.Draw(gameTime);
            }
        }

        public void drawSpheres(Matrix rotMatrix, GameTime gameTime)
        {
            Matrix t;
            for (float z = -30; z <= 30; z += 10)
            {
                t = Matrix.CreateTranslation(-10.0f, 8.0f, z);
                sphere.transform = t * rotMatrix;
                sphere.Draw(gameTime);

                t = Matrix.CreateTranslation(10.0f, 8.0f, z);
                sphere.transform = t * rotMatrix;
                sphere.Draw(gameTime);
            }
        }
    }
}