Proper indexing from vertices

Hi.
We’ve been through some difficulties on rendering from a Tilemap, I’ve tried using TriangleStrip instead of TriangleList but the outcome is always a shattered render result.

The previous implementation of this code used OpenGL and managed to draw the vertices using PrimitiveType.Quads

    public sealed class TileRenderer : ITileRenderer
    {
        private const int MaxTextures = 4;

        /*private static readonly Dictionary<Renderer, TileRenderer> RendererDictionary =
            new Dictionary<Renderer, TileRenderer>();*/

        public static Colour ShadowColour;
        private readonly IGraphicsContext _graphicsContext;

        private TileBlendMode _tempLastBlendMode;

        //private readonly List<Vertex> _vertices = new List<Vertex>();
        private const int VerticesArrayLength = 256;
        private static Vertex[] _vertices = new Vertex[VerticesArrayLength];
        public int NumVertices;
        private static readonly Vector2[] VertexPositions = new Vector2[4];
        private static readonly Vector2[] VertexUVs = new Vector2[4];

        private Effect _effect;

        public TileRenderer(ContentManager content, IGraphicsContext graphicsContext)
        {

            _graphicsContext = graphicsContext;

            _effect = content.Load<Effect>("shaders/tile");

            // Set defaults
            ModelMatrix = Matrix4.Identity;
            Colour = Colours.White;
            ClipRectangle = new Rectangle(0, 0, 1920, 1080);
        }

        public Colour Colour { get; set; }

        // Input options
        public Matrix4 ModelMatrix { get; set; }
        public ITexture[] Textures { get; set; }
        public Rectangle ClipRectangle 
        { 
            get; 
            set; 
        }

        public int Filter { get; set; }
        public double FilterAmount { get; set; }

        public bool Rendering { get; private set; }

        public int NumTiles { get; private set; }

        public void Dispose()
        {
        }

        public void Deactivate() { }

        public void BeginRender()
        {
            if(Rendering)
                throw new InvalidOperationException("Already renderering.");
            Rendering = true;

            NumTiles = 0;
            NumVertices = 0;
        }

        public void AddTile(
            Rectanglei source,
            Rectanglei destination,
            int textureIndex,
            bool flipX = false,
            bool flipY = false,
            float opacity = 1.0f,
            TileBlendMode blend = TileBlendMode.Alpha
        )
        {
            if(!Rendering)
                throw new Exception("Not currently renderering");

            var texture = Textures[textureIndex];

        // Add the vertices
        // Renderer.GetVertices(destination, VertexPositions);
        Array.Copy(destination.Vertices, 0, VertexPositions, 0, 4);
            Renderer.GetTextureMappings(texture, source, VertexUVs, flipX, flipY);
            for(var i = 0; i < 4; i++)
            {
                _vertices[NumVertices].VertexPosition.X = (float) VertexPositions[i].X;
                _vertices[NumVertices].VertexPosition.Y = (float) VertexPositions[i].Y;
                _vertices[NumVertices].VertexTextureMapping.X = (float) VertexUVs[i].X;
                _vertices[NumVertices].VertexTextureMapping.Y = (float) VertexUVs[i].Y;
                _vertices[NumVertices].VertexTextureIndex = textureIndex;
                _vertices[NumVertices].VertexOpacity = opacity;

                NumVertices++;

                if(NumVertices >= _vertices.Length)
                {
                    var tempVertex = new Vertex[_vertices.Length + VerticesArrayLength];
                    Array.Copy(_vertices, tempVertex, _vertices.Length);
                    _vertices = tempVertex;
                }
            }

            NumTiles++;

            _tempLastBlendMode = blend;
        }

        public void SetUpEffect()
        {
            var graphicsContext = _graphicsContext;
            var graphics = (graphicsContext as MonoGameGraphicsContext);

            var GraphicsDevice = graphics.WindowContext.Graphics.GraphicsDevice;

            var viewport = GraphicsDevice.Viewport;

            //var World = SimpleRenderer._modelMatrix;

            var ortho = ModelMatrix;
            var World = new Microsoft.Xna.Framework.Matrix(
                new Microsoft.Xna.Framework.Vector4((float)ortho.Row1.X, (float)ortho.Row1.Y, (float)ortho.Row1.Z, (float)ortho.Row1.W),
                new Microsoft.Xna.Framework.Vector4((float)ortho.Row2.X, (float)ortho.Row2.Y, (float)ortho.Row2.Z, (float)ortho.Row2.W),
                new Microsoft.Xna.Framework.Vector4((float)ortho.Row3.X, (float)ortho.Row3.Y, (float)ortho.Row3.Z, (float)ortho.Row3.W),
                new Microsoft.Xna.Framework.Vector4((float)ortho.Row4.X, (float)ortho.Row4.Y, (float)ortho.Row4.Z, (float)ortho.Row4.W));


            var View = Microsoft.Xna.Framework.Matrix.CreateLookAt(new Microsoft.Xna.Framework.Vector3(0, 0, -1), new Microsoft.Xna.Framework.Vector3(0, 0, 0), new Microsoft.Xna.Framework.Vector3(0, -1, 0));
            //var Projection2 = Microsoft.Xna.Framework.Matrix.CreateScale(1, -1, 1) * Microsoft.Xna.Framework.Matrix.CreateOrthographicOffCenter(0, 1920, 1080, 0, 0, 1); // nans

            ortho = _graphicsContext.CurrentFrameBuffer.CreateOrthographic() * Matrix4.CreateScale(1, -1, 1);
            var Projection = new Microsoft.Xna.Framework.Matrix(
                new Microsoft.Xna.Framework.Vector4((float)ortho.Row1.X, (float)ortho.Row1.Y, (float)ortho.Row1.Z, (float)ortho.Row1.W),
                new Microsoft.Xna.Framework.Vector4((float)ortho.Row2.X, (float)ortho.Row2.Y, (float)ortho.Row2.Z, (float)ortho.Row2.W),
                new Microsoft.Xna.Framework.Vector4((float)ortho.Row3.X, (float)ortho.Row3.Y, (float)ortho.Row3.Z, (float)ortho.Row3.W),
                new Microsoft.Xna.Framework.Vector4((float)ortho.Row4.X, (float)ortho.Row4.Y, (float)ortho.Row4.Z, (float)ortho.Row4.W));

            var wvp = World * View * Projection;

            _effect.Parameters["World"].SetValue(World);
            _effect.Parameters["View"].SetValue(View);
            _effect.Parameters["Projection"].SetValue(Projection);

            for (var i = 0; i < Textures.Length; i++)
            {
                var texture = (Textures[i] as MonoGameTexture).Texture;
                _effect.Parameters["InputTexture" + i.ToString()].SetValue(texture);
            }

            _effect.Parameters["AlphaGrayScale"].SetValue(ShadowRenderer.IsShadowing ? 1.0f : 0.0f);

            _effect.Parameters["InputColour"].SetValue(new Microsoft.Xna.Framework.Vector4(Colour.Red / 255.0f, Colour.Green / 255.0f, Colour.Blue / 255.0f, Colour.Alpha / 255.0f));
            _effect.Parameters["ShadowColour"].SetValue(new Microsoft.Xna.Framework.Vector4(ShadowColour.Red / 255.0f, ShadowColour.Green / 255.0f, ShadowColour.Blue / 255.0f, ShadowColour.Alpha / 255.0f));
            _effect.Parameters["InputFilter"].SetValue(Filter);
            _effect.Parameters["InputFilterAmount"].SetValue((float)FilterAmount);

            _effect.Parameters["InputClipRectangle"].SetValue(new Microsoft.Xna.Framework.Vector4((float)ClipRectangle.X, (float)ClipRectangle.Y, (float)ClipRectangle.Right, (float)ClipRectangle.Bottom));

            //_effect.Parameters["FragmentTextureIndex"].SetValue(_vertices[0].VertexTextureIndex);
            //_effect.Parameters["FragmentOpacity"].SetValue(_vertices[0].VertexOpacity);

            _effect.Parameters["InputFilter"].SetValue(Filter);

        }

        private void DrawQuad(Vertex[] destination)
        {
            var graphicsContext = _graphicsContext;
            var graphics = (graphicsContext as MonoGameGraphicsContext);
            var GraphicsDevice = graphics.WindowContext.Graphics.GraphicsDevice;


            Vertex[] quad = destination;

            short[] indices = new short[(int)(destination.Length * 1.5)];

            for(int i = 0; i<indices.Length; i += 6)
            {
                if (GraphicsDevice.RasterizerState == RasterizerState.CullClockwise)
                {
                    indices[i+0] = (short)(i + 0); 
                    indices[i+1] = (short)(i + 1); 
                    indices[i+2] = (short)(i + 2);

                    indices[i+3] = (short)(i + 2); 
                    indices[i+4] = (short)(i + 3); 
                    indices[i+5] = (short)(i + 0);
                }
                else
                {
                    indices[i + 0] = (short)(i + 0); 
                    indices[i + 1] = (short)(i + 2); 
                    indices[i + 2] = (short)(i + 1);
                    
                    indices[i + 3] = (short)(i + 2); 
                    indices[i + 4] = (short)(i + 0); 
                    indices[i + 5] = (short)(i + 3);

                }
            }

            foreach (EffectPass pass in _effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.SetVertexBuffer(null);

                GraphicsDevice.DrawUserIndexedPrimitives(Microsoft.Xna.Framework.Graphics.PrimitiveType.TriangleList, quad, 0, quad.Length, indices, 0, (indices.Length / 3), Vertex.VertexDeclaration);
            }
        }


        public void SetStates()
        {
            var graphicsContext = _graphicsContext;
            var graphics = (graphicsContext as MonoGameGraphicsContext);

            var GraphicsDevice = graphics.WindowContext.Graphics.GraphicsDevice;

            // Alpha blending
            graphics.BlendMode = _tempLastBlendMode == TileBlendMode.Additive ? BlendMode.Additive : BlendMode.Alpha;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.SamplerStates[0] = SamplerState.PointWrap;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
        }

        public void EndRender()
        {
            if(!Rendering)
                throw new Exception("Not currently renderering");

            SetStates();

            // Prepare textures
            foreach(var texture in Textures)
                texture.Filtering = TextureFiltering.NearestNeighbour;

            SetUpEffect();
            DrawQuad(_vertices);

            Rendering = false;
        }

        private struct Vertex : IVertexType
        {
            public Microsoft.Xna.Framework.Vector3 VertexPosition;

            public Microsoft.Xna.Framework.Vector2 VertexTextureMapping;

            public float VertexTextureIndex;

            public float VertexOpacity;

            public static int currentByteSize = 0;
            public static int Offset(float n) { var s = sizeof(float); currentByteSize += s; return currentByteSize - s; }
            public static int Offset(Microsoft.Xna.Framework.Vector2 n) { var s = sizeof(float) * 2; currentByteSize += s; return currentByteSize - s; }
            public static int Offset(Microsoft.Xna.Framework.Color n) { var s = sizeof(int); currentByteSize += s; return currentByteSize - s; }
            public static int Offset(Microsoft.Xna.Framework.Vector3 n) { var s = sizeof(float) * 3; currentByteSize += s; return currentByteSize - s; }
            public static int Offset(Microsoft.Xna.Framework.Vector4 n) { var s = sizeof(float) * 4; currentByteSize += s; return currentByteSize - s; }

            public readonly static VertexDeclaration VertexDeclaration =
              new VertexDeclaration(new VertexElement(Offset(Microsoft.Xna.Framework.Vector3.Zero), VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                                    new VertexElement(Offset(Microsoft.Xna.Framework.Vector2.Zero), VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
                                    new VertexElement(Offset(0.0f), VertexElementFormat.Single, VertexElementUsage.TextureCoordinate, 1),
                                    new VertexElement(Offset(0.0f), VertexElementFormat.Single, VertexElementUsage.TextureCoordinate, 2));
            VertexDeclaration IVertexType.VertexDeclaration
            {
                get { return Vertex.VertexDeclaration; }
            }

        }


    }

The relevant code portion is the following:

  short[] indices = new short[(int)(destination.Length * 1.5)];

            for(int i = 0; i<indices.Length; i += 6)
            {
                if (GraphicsDevice.RasterizerState == RasterizerState.CullClockwise)
                {
                    indices[i+0] = (short)(i + 0); 
                    indices[i+1] = (short)(i + 1); 
                    indices[i+2] = (short)(i + 2);

                    indices[i+3] = (short)(i + 2); 
                    indices[i+4] = (short)(i + 3); 
                    indices[i+5] = (short)(i + 0);
                }
                else
                {
                    indices[i + 0] = (short)(i + 0); 
                    indices[i + 1] = (short)(i + 2); 
                    indices[i + 2] = (short)(i + 1);
                    
                    indices[i + 3] = (short)(i + 2); 
                    indices[i + 4] = (short)(i + 0); 
                    indices[i + 5] = (short)(i + 3);

                }
            }

It’s based on the idea that every quad has a size of 4 vertices and a TriangleList should contain 6 indices, however, I’m fairly defeated by the implementation, the entrance of vertices seem to be always 4096, how can we sort the indices correctly?

Thanks!

There really isn’t enough information shared clearly to help you here.
Setting your vertex buffer to null before drawing i imagine is a typo.

You say you have 4096 vertices but are trying to draw quads from them?
That would imply that this is already a laid out 2d grid of vertices or its already ready as 4096/4 quads but how exactly is this laid out?

Say that every 4 vertices is a quad that you load and you wish to index them?
In that case you loop like so.

int verticesInaQuad = 4;
int indicesInaQuad =6; 
int verticeCount = 4096;
int quadCount = verticeCount / verticesInaQuad;
int indicesCount = quadCount * indicesInaQuad ;

short[] indices = new short[indicesCount];

int indexIndicer =0;

// We do a for loop and increment thru all the vertexs.
// We are incrementing the offset by a quads worth of vertices each time (verticesInaQuad) or by 4 verts.

for(int verticeIndicer = 0; verticeIndicer < vertexs.Length; verticeIndicer += verticesInaQuad )
{
       indices[indexIndicer + 0] = (short)(verticeIndicer + 0); 
       indices[indexIndicer + 1] = (short)(verticeIndicer + 1); 
       indices[indexIndicer + 2] = (short)(verticeIndicer + 2);

       indices[indexIndicer + 3] = (short)(verticeIndicer + 2); 
       indices[indexIndicer + 4] = (short)(verticeIndicer + 3); 
       indices[indexIndicer + 5] = (short)(verticeIndicer + 0);

       // We initialized 6 indices, a quads worth (indicesInaQuad) so we increment our counter.
       indexIndicer += indicesInaQuad;

       // The for loop will continue till its accounted for all the vertices.
       // At which time we will have filled our index array.
}

There is like 3 separate examples or links to examples for how to tile map in this post if it helps.
I put 2 on there myself the second one is better then the first one i posted.

nkasts is excellent and probably the fastest overall, but might be a bit tough to understand.

It worked. So it should take in account each set of quads instead of each set of indices for storing the indices.

And yeah, the setting the vertex buffer to null is a typo, I was experimenting with vertex buffers previously since it was taking Vector4 as position to process inside the shader, and it had to be Vertex3, but I thought it could be related to the vertex buffer.

Thank you.