root/src/AIRendering/PrimitiveBatch.cs

User picture

Author: conkerjo

Revision: 30 («Previous)


File Size: 13.7 KB

(June 30, 2009 23:12 UTC) Almost 3 years ago

Initial Checkin

 
Show/hide line numbers
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace AIRendering
{
    public class PrimitiveBatch : IDisposable
    {
        public GraphicsDevice Device
        {
            get { return device; }
        }
        #region Constants and Fields

        // this constant controls how large the vertices buffer is. Larger buffers will
        // require flushing less often, which can increase performance. However, having
        // buffer that is unnecessarily large will waste memory.
        const int DefaultBufferSize = 500;

        // a block of vertices that calling AddVertex will fill. Flush will draw using
        // this array, and will determine how many primitives to draw from
        // positionInBuffer.
        VertexPositionColor[] vertices = new VertexPositionColor[DefaultBufferSize];

        // keeps track of how many vertices have been added. this value increases until
        // we run out of space in the buffer, at which time Flush is automatically
        // called.
        int positionInBuffer = 0;

        // the vertex declaration that will be set on the device for drawing. this is 
        // created automatically using VertexPositionColor's vertex elements.
        VertexDeclaration vertexDeclaration;

        // a basic effect, which contains the shaders that we will use to draw our
        // primitives.
        BasicEffect basicEffect;

        // the device that we will issue draw calls to.
        GraphicsDevice device;

        // this value is set by Begin, and is the type of primitives that we are
        // drawing.
        PrimitiveType primitiveType;

        // how many verts does each of these primitives take up? points are 1,
        // lines are 2, and triangles are 3.
        int numVertsPerPrimitive;

        // hasBegun is flipped to true once Begin is called, and is used to make
        // sure users don't call End before Begin is called.
        bool hasBegun = false;

        bool isDisposed = false;

        #endregion
        
        // the constructor creates a new PrimitiveBatch and sets up all of the internals
        // that PrimitiveBatch will need.
        public PrimitiveBatch(GraphicsDevice graphicsDevice)
        {
            if (graphicsDevice == null)
            {
                throw new ArgumentNullException("graphicsDevice");
            }
            device = graphicsDevice;

            // create a vertex declaration, which tells the graphics card what kind of
            // data to expect during a draw call. We're drawing using
            // VertexPositionColors, so we'll use those vertex elements.
            vertexDeclaration = new VertexDeclaration(graphicsDevice,
                VertexPositionColor.VertexElements);

            // set up a new basic effect, and enable vertex colors.
            basicEffect = new BasicEffect(graphicsDevice, null);
            basicEffect.VertexColorEnabled = true;

            // projection uses CreateOrthographicOffCenter to create 2d projection
            // matrix with 0,0 in the upper left.
            SetProjection();
        }

        private void SetProjection()
        {
            basicEffect.Projection = Matrix.CreateOrthographicOffCenter
                (position.X,
                position.X+(device.Viewport.Width * zoom),
                position.Y+(device.Viewport.Height * zoom),
                position.Y,
                0,
                1);
        }
        Vector2 position;
        public Vector2 CameraPosition 
        {
            get { return position; }

            set
            {
                position = value;
                SetProjection();
            }
        }

        float zoom=1;
        public float Zoom
        {
            get{return zoom;}
            set
            {
                zoom = value;
                SetProjection();
            }
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing && !isDisposed)
            {
                if (vertexDeclaration != null)
                    vertexDeclaration.Dispose();

                if (basicEffect != null)
                    basicEffect.Dispose();

                isDisposed = true;
            }
        }

        // Begin is called to tell the PrimitiveBatch what kind of primitives will be
        // drawn, and to prepare the graphics card to render those primitives.
        public void Begin(PrimitiveType primitiveType)
        {
            if (hasBegun)
            {
                throw new InvalidOperationException
                    ("End must be called before Begin can be called again.");
            }

            // these three types reuse vertices, so we can't flush properly without more
            // complex logic. Since that's a bit too complicated for this sample, we'll
            // simply disallow them.
            if (primitiveType == PrimitiveType.LineStrip ||
                primitiveType == PrimitiveType.TriangleFan ||
                primitiveType == PrimitiveType.TriangleStrip)
            {
                //throw new NotSupportedException
                //  ("The specified primitiveType is not supported by PrimitiveBatch.");
            }

            this.primitiveType = primitiveType;

            // how many verts will each of these primitives require?
            this.numVertsPerPrimitive = NumVertsPerPrimitive(primitiveType);

            // prepare the graphics device for drawing by setting the vertex declaration
            // and telling our basic effect to begin.
            device.VertexDeclaration = vertexDeclaration;
            basicEffect.Begin();
            basicEffect.CurrentTechnique.Passes[0].Begin();

            // flip the error checking boolean. It's now ok to call AddVertex, Flush,
            // and End.
            hasBegun = true;
        }

        // AddVertex is called to add another vertex to be rendered. To draw a point,
        // AddVertex must be called once. for lines, twice, and for triangles 3 times.
        // this function can only be called once begin has been called.
        // if there is not enough room in the vertices buffer, Flush is called
        // automatically.
        public void AddVertex(Vector2 vertex, Color color)
        {
            if (!hasBegun)
            {
                throw new InvalidOperationException
                    ("Begin must be called before AddVertex can be called.");
            }

            // are we starting a new primitive? if so, and there will not be enough room
            // for a whole primitive, flush.
            bool newPrimitive = ((positionInBuffer % numVertsPerPrimitive) == 0);

            if (newPrimitive &&
                (positionInBuffer + numVertsPerPrimitive) >= vertices.Length)
            {
                Flush();
            }

            // once we know there's enough room, set the vertex in the buffer,
            // and increase position.
            vertices[positionInBuffer].Position = new Vector3(vertex, 0);
            vertices[positionInBuffer].Color = color;

            positionInBuffer++;
        }

        // End is called once all the primitives have been drawn using AddVertex.
        // it will call Flush to actually submit the draw call to the graphics card, and
        // then tell the basic effect to end.
        public void End()
        {
            if (!hasBegun)
            {
                throw new InvalidOperationException
                    ("Begin must be called before End can be called.");
            }

            // Draw whatever the user wanted us to draw
            Flush();

            // and then tell basic effect that we're done.
            basicEffect.CurrentTechnique.Passes[0].End();
            basicEffect.End();
            hasBegun = false;
        }

        // Flush is called to issue the draw call to the graphics card. Once the draw
        // call is made, positionInBuffer is reset, so that AddVertex can start over
        // at the beginning. End will call this to draw the primitives that the user
        // requested, and AddVertex will call this if there is not enough room in the
        // buffer.
        private void Flush()
        {
            if (!hasBegun)
            {
                throw new InvalidOperationException
                    ("Begin must be called before Flush can be called.");
            }

            // no work to do
            if (positionInBuffer == 0)
            {
                return;
            }

            // how many primitives will we draw?
            int primitiveCount = positionInBuffer / numVertsPerPrimitive;

            // submit the draw call to the graphics card
            device.DrawUserPrimitives<VertexPositionColor>(primitiveType, vertices, 0,
                primitiveCount);

            // now that we've drawn, it's ok to reset positionInBuffer back to zero,
            // and write over any vertices that may have been set previously.
            positionInBuffer = 0;
        }

        #region Helper functions

        // NumVertsPerPrimitive is a boring helper function that tells how many vertices
        // it will take to draw each kind of primitive.
        static private int NumVertsPerPrimitive(PrimitiveType primitive)
        {
            int numVertsPerPrimitive;
            switch (primitive)
            {
                case PrimitiveType.PointList:
                    numVertsPerPrimitive = 1;
                    break;
                case PrimitiveType.LineList:
                    numVertsPerPrimitive = 2;
                    break;
                case PrimitiveType.TriangleList:
                    numVertsPerPrimitive = 3;
                    break;
                case PrimitiveType.LineStrip:
                    numVertsPerPrimitive = 2;
                    break;
                default:
                    throw new InvalidOperationException("primitive is not valid");
            }
            return numVertsPerPrimitive;
        }

        public void DrawLine(Vector2 point1, Vector2 point2, Color color)
        {

            // tell the primitive batch to start drawing lines
            Begin(PrimitiveType.LineList);

            // from the nose, down the left hand side
            AddVertex(point1, color);
            AddVertex(point2, color);

            // and we're done.
            End();
        }
        public void DrawBox(Vector2 topLeft, Vector2 bottomRight)
        {
            // tell the primitive batch to start drawing lines
            Begin(PrimitiveType.LineList);

            // from the nose, down the left hand side
            AddVertex(
                topLeft, Color.White);
            AddVertex(
                new Vector2(bottomRight.X, topLeft.Y), Color.White);

            AddVertex(
                new Vector2(bottomRight.X, topLeft.Y), Color.White);
            AddVertex(bottomRight, Color.White);

            AddVertex(bottomRight, Color.White);
            AddVertex(new Vector2(topLeft.X, bottomRight.Y), Color.White);

            AddVertex(new Vector2(topLeft.X, bottomRight.Y), Color.White);
            AddVertex(topLeft, Color.White);

            // and we're done.
            End();
        }


        public void DrawBox(Vector2 topLeft, Vector2 bottomRight, Color outline)
        {
            // tell the primitive batch to start drawing lines
            Begin(PrimitiveType.LineList);

            // from the nose, down the left hand side
            AddVertex(
                topLeft, outline);
            AddVertex(
                new Vector2(bottomRight.X, topLeft.Y), outline);

            AddVertex(
                new Vector2(bottomRight.X, topLeft.Y), outline);
            AddVertex(bottomRight, outline);

            AddVertex(bottomRight, outline);
            AddVertex(new Vector2(topLeft.X, bottomRight.Y), outline);

            AddVertex(new Vector2(topLeft.X, bottomRight.Y), outline);
            AddVertex(topLeft, outline);

            // and we're done.
            End();
        }
        int defaultPointCountForCircle = 24;
        public void DrawCircle(Vector2 center, float radius, Color color)
        {

            Begin(PrimitiveType.LineList);

            double angle = MathHelper.TwoPi / defaultPointCountForCircle;
            for (int i = 1; i <= defaultPointCountForCircle; i++)
            {
                AddVertex(center + new Vector2(
                                        (float)Math.Round(Math.Sin(angle * i), 4) * radius,
                                        (float)Math.Round(Math.Cos(angle * i), 4) * radius),
                                        color);

                AddVertex(center + new Vector2(
                                        (float)Math.Round(Math.Sin(angle * (i + 1)), 4) * radius,
                                        (float)Math.Round(Math.Cos(angle * (i + 1)), 4) * radius),
                                        color);
            }

            // and we're done.
            End();
        }
        public void DrawPoints(Vector2[] points)
        {

            Begin(PrimitiveType.PointList);


            for (int i = 1; i <= points.Length; i++)
            {
                AddVertex(points[i], Color.White);
            }

            // and we're done.
            End();
        }


        #endregion


    }

}