Google
 

Sunday, April 08, 2007

Engine Design - RCObject

As mentioned before my engine is based on the HazyMind XNA tutorials. I think Mike's idea for the tutorial was to give people a sense of how to go about creating an engine in XNA. The tutorials do not necessarily tell you how you should do it but rather point you in the right direction.

Tutorial 2 Scene Graph and Object Framework is where the deviation really starts. Mike starts by creating a basic Object class to be used to render our 3D models with along with some interfaces and a Scene object. The basic mechanics of this is pretty much unchanged (as of the time of writing this).

I have extended this basic object in a number if ways:

Fields
protected VertexPositionColor[] points;
protected short[] index;
protected BoundingBox myBounds;
protected Color myBoundsColor;
protected bool alwaysFacingCamera;
private bool myUseLeftHandedWorldCalc;
private bool myVisible;
public bool WireFrame;
protected bool myDrawBounds;
private string myName;

These fields all have a respective property, with the exception of WireFrame (should have one, but just not got round to doing it yet)

points & index
This is an array of VertexPositionColor and an array of short's used for drawing the objects bounding box. In the tutorial an array of type int is used, I have changed this to short as this allows quads to be rendered on my laptop. For some reason if they index was declared as an int array only half of the textured quads would render.

protected BoundingBox myBounds;
This field holds the objects bounding box. Its access property is a little different. Rather than just returning the filed myBounds the property returns the calculated bounding box plus the objects current position.


public BoundingBox ObjectsBoundingBox
{
    get
    {
        BoundingBox bb = new BoundingBox(myBounds.Min + myPosition, myBounds.Max + myPosition);
        return bb;
    }
}



protected Color myBoundsColor;
This field simply holds the color to be used to draw the bounding box.

protected bool alwaysFacingCamera;
This field allows me to inform the shader to create the World matrix for this object so that it has the object always facing the camera. This is of only any real use with Textured Quads.

private bool myUseLeftHandedWorldCalc;
Now this is a bit of a bodge to fix an issue I have with the odd shader or two. I found that when rotating objects with certain shaders that the object would revolve around it's position rather than rotate, so this method forces the shader to generate the World matrix in such a way as to compensate for this odd action. I will get the the bottom of why this happens but as yet I have no fix for it other than this.

private bool myVisible;
This field basically causes the object not to be drawn if set to false.

public bool WireFrame;
This field tells the draw method to use wire frame.

protected bool myDrawBounds;
If this field is set to true then (if possible) the objects Bounding Box is drawn.

private string myName;
Now I added this field so I can pull objects out of the scene by name.

Methods
public virtual void Rotate(Vector3 axis, float angle)
public void Translate(Vector3 distance)
protected void BuildBoxCorners()
protected virtual void DrawBounds(GraphicsDevice myDevice,Color col)
public Point Get2DCoords(GameWindow window)

public virtual void Rotate(Vector3 axis, float angle)
This has been taken from the Camera class, this allows me to rotate the models.
public void Translate(Vector3 distance)
Again this method was taken from the Camera class to allow translation of models.

protected void BuildBoxCorners()
This method was added so that the corners of the bounding box could be created ready to be used should the bounding box need to be drawn.


protected void BuildBoxCorners()
{
    points = new VertexPositionColor[8];
    Vector3[] corners = myBounds.GetCorners();

    points[0] = new VertexPositionColor(corners[1] , Color.Green);
    points[1] = new VertexPositionColor(corners[0] , Color.Green);
    points[2] = new VertexPositionColor(corners[2] , Color.Green);
    points[3] = new VertexPositionColor(corners[3] , Color.Green);
    points[4] = new VertexPositionColor(corners[5] , Color.Green);
    points[5] = new VertexPositionColor(corners[4] , Color.Green);
    points[6] = new VertexPositionColor(corners[6] , Color.Green);
    points[7] = new VertexPositionColor(corners[7] , Color.Green);

    short[] inds = {
        0, 1, 0, 2, 1, 3, 2, 3,
        4, 5, 4, 6, 5, 7, 6, 7,
        0, 4, 1, 5, 2, 6, 3, 7
        };

    index = inds;
}



protected virtual void DrawBounds(GraphicsDevice myDevice,Color col)
This is the method that actually draws the objects bounding box.


protected virtual void DrawBounds(GraphicsDevice myDevice,Color col)
{
    BuildBoxCorners();

    if (this is RCModel)
    {
        myDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.LineList, points, 0, 8, index, 0, 12);
    }
    else
    {
        BasicEffect shader = RCShaderManager.GetShader("BasicEffect").Effect as BasicEffect;
        shader.View = RCCameras.RCCameraManager.ActiveCamera.View;
        shader.Projection = RCCameras.RCCameraManager.ActiveCamera.Projection;
        shader.DiffuseColor = col.ToVector3();

        shader.Begin();
        for (int i = 0; i < shader.CurrentTechnique.Passes.Count; i++)
        {
            shader.CurrentTechnique.Passes[i].Begin();
            myDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.LineList, points, 0, 8, index, 0, 12);
            shader.CurrentTechnique.Passes[i].End();
        }
        shader.End();
    }
}


The check for RCModel is now redundant and will be removed as I now have multiple bounding box's in the mesh. Mesh's have maesh's in mesh's and so if the mesh format is a FBX file then it can have a bounding box per mesh in the mesh.

public Point Get2DCoords(GameWindow window)
This method returns the objects 2D screen co-ordinates, very useful for placing targeting glyph on an object in the scene.


public Point Get2DCoords(GameWindow window)
{
    RCCameras.RCCamera camera = RCCameras.RCCameraManager.ActiveCamera;
    Matrix ViewProjectionMatrix = camera.View * camera.Projection;

    Vector4 result4 = Vector4.Transform(myPosition, ViewProjectionMatrix);
    if (result4.W == 0)
        result4.W = RCHelper.Epsilon;

    Vector3 result = new Vector3(result4.X / result4.W, result4.Y / result4.W, result4.Z / result4.W);

    return new Point((int)Math.Round(+result.X * (window.ClientBounds.Width / 2)) + (window.ClientBounds.Width / 2), (int)Math.Round(-result.Y * (window.ClientBounds.Height / 2)) + (window.ClientBounds.Height / 2));
}


My next post will describe how I have altered the interfaces and what I have changed in the Scene class.

3 comments:

  1. Great stuff, but you could also add scaling for the bounding box, because if you scaled a model, the bounding box wouldn't


    get
    {
    BoundingBox bb = new BoundingBox();
    Matrix world = Matrix.CreateScale(Scaling) *
    Matrix.CreateTranslation(Position);

    bb.Max = Vector3.Transform(boundingBox.Max, world);
    bb.Min = Vector3.Transform(boundingBox.Min, world);
    return bb;
    }

    :)

    ReplyDelete
  2. Now, you know what, I am not getting that issue. But I guess if I do I can use your snippet to fix it :)

    Cool.

    ReplyDelete
  3. eclipse,

    Guess what, I got that issue :P
    Thanks for the heads up :)

    ReplyDelete

Note: Only a member of this blog may post a comment.