XNA/Monogame Icosphere-Creation

Started by
5 comments, last by JustJim 9 years, 7 months ago

Hey! While coding my way through my first little game, I came across fractal Terrain-Generation.

Would be nice as I do not want to built many Planet-Models and Skulp them etc. (I wanna make em random).

For this, it is prefered to use Icospheres as planets due to the static size of triangles.

I started to look around for Tutorials and read throug the Theory of a Icosphere-Creator.

I see it like this: Built a Icosphere with 12 Vertices and 20 Faces made by Indices.

For Refining:

Go through each Index, Calculate the Middle between Point 1 and 2, Point 2 and 3, Point 3 and 1 and add

the resulting vertices with the middle-coords to the Vertex-Stack and get the Indices of the Vertices (called a,b and c).

Make a new List of indices:

Add

Index of Point1, a, c

Index of Point2, b, a

Index of Point3, c, b

a,b,c

to the List

After the whole for-loop

replace indices-list with the new indices-list

BUT: This won't work, for some reasons I do not understand!

I tried to change order of the Indices as I came to the Idea, the vertices was drawn the wrong order but no good.

This is what I wrote:



            List<VertexPositionColor> vertices = new List<VertexPositionColor>();

            float t = (float)(1.0+Math.Sqrt(5.0f)) /2.0f;

            vertices.Add(new VertexPositionColor(new Vector3(-1, 0, t), Color.Gray));
            vertices.Add(new VertexPositionColor(new Vector3(1, 0, t), Color.Gray));
            vertices.Add(new VertexPositionColor(new Vector3(-1, 0, -t), Color.Gray));
            vertices.Add(new VertexPositionColor(new Vector3(1, 0, -t), Color.Gray));

            vertices.Add(new VertexPositionColor(new Vector3(0, t, 1), Color.Gray));
            vertices.Add(new VertexPositionColor(new Vector3(0, t, -1), Color.Gray));
            vertices.Add(new VertexPositionColor(new Vector3(0, -t, 1), Color.Gray));
            vertices.Add(new VertexPositionColor(new Vector3(0, -t, -1), Color.Gray));

            vertices.Add(new VertexPositionColor(new Vector3(t, 1, 0), Color.Gray));
            vertices.Add(new VertexPositionColor(new Vector3(-t, 1, 0), Color.Gray));
            vertices.Add(new VertexPositionColor(new Vector3(t, -1, 0), Color.Gray));
            vertices.Add(new VertexPositionColor(new Vector3(-t, -1, 0), Color.Gray));

            
            
            
            //short[] indices = new short[60];
            List<short> indices = new List<short>();
            
            indices.Add(0); indices.Add(6); indices.Add(1);
            indices.Add(0); indices.Add(11); indices.Add(6);
            indices.Add(1); indices.Add(4); indices.Add(0);
            indices.Add(1); indices.Add(8); indices.Add(4);
            indices.Add(1); indices.Add(10); indices.Add(8);
            indices.Add(2); indices.Add(5); indices.Add(3);
            indices.Add(2); indices.Add(9); indices.Add(5);
            indices.Add(2); indices.Add(11); indices.Add(9);
            indices.Add(3); indices.Add(7); indices.Add(2);
            indices.Add(3); indices.Add(10); indices.Add(7);
            indices.Add(4); indices.Add(8); indices.Add(5);
            indices.Add(4); indices.Add(9); indices.Add(0);
            indices.Add(5); indices.Add(8); indices.Add(3);
            indices.Add(5); indices.Add(9); indices.Add(4);
            indices.Add(6); indices.Add(10); indices.Add(1);
            indices.Add(6); indices.Add(11); indices.Add(7);
            indices.Add(7); indices.Add(10); indices.Add(6);
            indices.Add(7); indices.Add(11); indices.Add(2);
            indices.Add(8); indices.Add(10); indices.Add(3);
            indices.Add(9); indices.Add(11); indices.Add(0);

            List<short> indices2 = new List<short>();
            for (int i = 0; i < indices.Count; i++) 
            {
                Vector3 p1 = vertices[indices[i]].Position;
                short p1I = (short)i++;
                Vector3 p2 = vertices[indices[i]].Position;
                short p2I = (short)i++;
                Vector3 p3 = vertices[indices[i]].Position;
                short p3I = (short)i;

                short a = (short)vertices.Count;
                vertices.Add(new VertexPositionColor(new Vector3((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2, (p1.Z + p2.Z) / 2), Color.Black));
                short b = (short)vertices.Count;
                vertices.Add(new VertexPositionColor(new Vector3((p2.X + p3.X) / 2, (p2.Y + p3.Y) / 2, (p2.Z + p3.Z) / 2), Color.Black));
                short c = (short)vertices.Count;
                vertices.Add(new VertexPositionColor(new Vector3((p3.X + p1.X) / 2, (p3.Y + p1.Y) / 2, (p3.Z + p1.Z) / 2), Color.Black));

                indices2.Add(indices[p1I]); indices2.Add(a); indices2.Add(c);
                indices2.Add(indices[p2I]); indices2.Add(b); indices2.Add(a);
                indices2.Add(indices[p3I]); indices2.Add(c); indices2.Add(b);

                indices2.Add(a); indices2.Add(b); indices2.Add(c);

            }
            indices = indices2;
            
            vertexList = new VertexPositionColor[vertices.Count];
            for (int i = 0; i < vertices.Count; i++)
            {
                vertexList[i] = vertices[i];
            }

            short[] indexList = new short[indices.Count];
            for (int i = 0; i < indices.Count;i++ )
            {
                indexList[i] = indices[i];
            }

            vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor),12,BufferUsage.WriteOnly);
            vertexBuffer.SetData<VertexPositionColor>(vertexList);
            indexBuffer = new IndexBuffer(graphics.GraphicsDevice, typeof(short), indexList.Length, BufferUsage.WriteOnly);
            indexBuffer.SetData(indexList);

Could anyone see through the code and show me my obvious error?

Thanks!

Edit:

I tried it on a single Triangle. Result: It worked. But on the Ico, it won't :/ whats wrong with it?

Advertisement

Aight I got the Error. It was down in the SetVertexBuffer. There is a Magic "12" which i needed to replace.

Also there was an Error in the Draw-Methode where I assigned the Buffer to the one that should be used, where I had to enter the Number of Vertices

and Primitives, which I missed to do.

BUT! I got another error, which makes me crazy!

I made another for loop around the first one which smoothes the Ico. If i set it to 1 Iteration, everything is just fine, but with two, I run into an OutOfMemeoryException :/

Any Solution to that?

Well I actually just fixed this. Problem was: When i assigned indices = indices2 it was by reference making the whole thing infinite.

Another Question

When I starte the programm, it won't smoothin the ico rather than drawing the triangles on the surface of the bigger once, making the whole thing useless.

how could i change this?

Is Solved as I only add Normalized Vertices.

Last Question:

Lightning.

I have this code in the Draw Section:


protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.DarkSlateBlue);

            DrawPrimitive(vertexList);
            
            base.Draw(gameTime);
        }
protected void DrawPrimitive(VertexPositionColor[] vertices)
        {
            basicEffect.VertexColorEnabled = true;
            basicEffect.World = world;
            basicEffect.View = view;
            basicEffect.Projection = projection;
            GraphicsDevice.SetVertexBuffer(vertexBuffer);
            GraphicsDevice.Indices = indexBuffer;

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0,0,vertexList.Length,0,indexList.Length/3);
            }
        }

But it seems whenever I use something like

basicEffect.LightingEnabled = true;

I get a crash with following message:

HRESULT: [0x80070057], Module: [General], ApiCode: [E_INVALIDARG/Invalid Arguments], Message: Wrong Parameter.

In VS go to Project -> <project name>-properties -> Debugging and check "Enable unmanaged code debugging"

This will show you the underlying D3D error in the output window.

I assume that the BasicEffect shader is expecting a parameter which is not being provided by your code/content. I had a similar problem but with TextureEnabled and missing texture coordinates

Yeah I searched that up. I guess it's due the fact that my Vertices offers no Normals, so there can't be any light calculation.

How could be if there weren't Vectors that gives the direction in which the light reflects. Correct me if I'm wrong

On a quick visit to google I found this tutorial on generating Normals:

http://www.riemers.net/eng/Tutorials/XNA/Csharp/ShortTuts/Normal_generation.php

Maybe this helps you. I haven't tried it out because I dont need this functionality (Only using models myself)

Oh thanks for that link. I already found out how exactly to do it.

I'll post it here, so anyone who needs that solution will find it.

As I needed Normals along with Color and Position (XNA/Monogame only offers them with Position or PositionTexture but not color), I

made an own structure:


public struct VertexPositionColorNormal: IVertexType
        {
            public Vector3 Position;
            public Color Color;
            public Vector3 Normal;

            public VertexPositionColorNormal(Vector3 vector, Color color, Vector3 normal)
            {
                Position = vector;
                Color = color;
                Normal = normal;
            }
            public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
            (
                new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
                new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
            );

            VertexDeclaration IVertexType.VertexDeclaration
            {
                get { return VertexPositionColorNormal.VertexDeclaration; 
            }
            public float length()
            {
                return (float)Math.Sqrt(Position.X * Position.X + Position.Y * Position.Y + Position.Z * Position.Z)
            }
              

To use it with VertexBuffer, I needed to make it inherit from IVertexType, otherwhise it won't work. Also needed is the VertexDeclaration along with it's get Methode, so it is readable and usable for this purpose. length() is not required but I needed it to test some cases with it.

I chose to work with Lists for Vertices and Indices so I do not need to realloc memory all the time on arrays.

As I needed normalized Vectors for the whole smoothing-process (in any other case there will only be the Ico with subdivided Triangles but no sphere at all)

I also made a little function for Adding Vectors and not caring if they're already Normalized.


protected List<VertexPositionColorNormal> AddNormalizedVertex(List<VertexPositionColorNormal> vertices, Vector3 vector, Color color, Vector3 Normal)
        {
            vector.Normalize();
            vertices.Add(new VertexPositionColorNormal(vector, color, Normal));
            return vertices;
        }

This code takes the List as it is as a parameter to add the VertexPositionColorNormal and returns it (As I still didn't check if it's getting done by refference haha).

Not much else to say here.

I packed the whole creation-algorithm into the Initialize()-Methode.

First of all, as I work with Lists, I needed to change them into Arrays at the end to get this whole thing to work, so there are member-variables in the gameclass along with the vertexBuffer and indexBuffer


        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;
        VertexPositionColorNormal[] vertexList;
        int[] indexList;

Don't mind the fact they're not private or protected as this was just a simple test. I WILL capsule the whole procedure in its own class along with its own world Matrix.

Next (back in the Init()-Methode) I create a List for VertexPositionColorNormals and add the 12 Start-Vertices


List<VertexPositionColorNormal> vertices = new List<VertexPositionColorNormal>();
           
            vertices = AddNormalizedVertex(vertices, new Vector3(-1, 0, 1), Color.Gray,new Vector3(0,0,0));
            vertices = AddNormalizedVertex(vertices, new Vector3(1, 0, 1), Color.Gray, new Vector3(0, 0, 0));
            vertices = AddNormalizedVertex(vertices, new Vector3(-1, 0, -1), Color.Gray, new Vector3(0, 0, 0));
            vertices = AddNormalizedVertex(vertices, new Vector3(1, 0, -1), Color.Gray, new Vector3(0, 0, 0));
            

            vertices = AddNormalizedVertex(vertices, new Vector3(0, 1, 1), Color.Gray, new Vector3(0, 0, 0));
            vertices = AddNormalizedVertex(vertices, new Vector3(0, 1, -1), Color.Gray, new Vector3(0, 0, 0));
            vertices = AddNormalizedVertex(vertices, new Vector3(0, -1, 1), Color.Gray, new Vector3(0, 0, 0));
            vertices = AddNormalizedVertex(vertices, new Vector3(0, -1, -1), Color.Gray, new Vector3(0, 0, 0));


            vertices = AddNormalizedVertex(vertices, new Vector3(1, 1, 0), Color.Gray, new Vector3(0, 0, 0));
            vertices = AddNormalizedVertex(vertices, new Vector3(-1, 1, 0), Color.Gray, new Vector3(0, 0, 0));
            vertices = AddNormalizedVertex(vertices, new Vector3(1, -1, 0), Color.Gray, new Vector3(0, 0, 0));
            vertices = AddNormalizedVertex(vertices, new Vector3(-1, -1, 0), Color.Gray, new Vector3(0, 0, 0));

After that, I needed to give the indices.


List<int> indices = new List<int>();

            indices.Add(0); indices.Add(6); indices.Add(1);
            indices.Add(0); indices.Add(11); indices.Add(6);
            indices.Add(1); indices.Add(4); indices.Add(0);
            indices.Add(1); indices.Add(8); indices.Add(4);
            indices.Add(1); indices.Add(10); indices.Add(8);

            indices.Add(2); indices.Add(5); indices.Add(3);
            indices.Add(2); indices.Add(9); indices.Add(5);
            indices.Add(2); indices.Add(11); indices.Add(9);
            indices.Add(3); indices.Add(7); indices.Add(2);
            indices.Add(3); indices.Add(10); indices.Add(7);

            indices.Add(4); indices.Add(8); indices.Add(5);
            indices.Add(4); indices.Add(9); indices.Add(0);
            indices.Add(5); indices.Add(8); indices.Add(3);
            indices.Add(5); indices.Add(9); indices.Add(4);
            indices.Add(6); indices.Add(10); indices.Add(1);

            indices.Add(6); indices.Add(11); indices.Add(7);
            indices.Add(7); indices.Add(10); indices.Add(6);
            indices.Add(7); indices.Add(11); indices.Add(2);
            indices.Add(8); indices.Add(10); indices.Add(3);
            indices.Add(9); indices.Add(11); indices.Add(0);

Okay. This alone will create the Ico at its Basic State, positioned at 0,0,0 with the radius of the Corners of 1

Now comes the refining (subdividing/smoothing, whatever you call it). Note that I, when I'll pack it into its own Class, I will make the Refining-Grade an choosable option, so there will not be number in the first for-loop anymore. This code is a little bit more, so I give the explaination the the comments:


//First I need a new List to store the Vertices as I need them from the first List and do not want to overwrite them
            List<int> indices2 = new List<int>();
            for (int j = 0; j <3 ; j++) //Number of Iteration for making it even smoother
            {
                for (int i = 0; i < indices.Count; i++) //Go through all Indices
                {
                    //Here I get the Vertices responsible for ONE Triangle and store their index
                    Vector3 p1 = vertices[indices[i]].Position;
                    int p1I = (int)i++;
                    Vector3 p2 = vertices[indices[i]].Position;
                    int p2I = (int)i++;
                    Vector3 p3 = vertices[indices[i]].Position;
                    int p3I = (int)i;

                    //Now I get the Index of the Vertex I want to add (as vertices.Count gives the actual Number, this will be
                    //correct (counting from 1 to x+1 instead of 0 to x)
                    //Next I need to get the middle between to vertices ( in this case from vertice p1 to p2) and add it as a new Vertex
                    int a = (int)vertices.Count;
                    vertices = AddNormalizedVertex(vertices, new Vector3((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2, (p1.Z + p2.Z) / 2), Color.Gray, new Vector3(0, 0, 0));
                   
                    //Same here with p2 to p3
                    int b = (int)vertices.Count;
                    vertices = AddNormalizedVertex(vertices, new Vector3((p2.X + p3.X) / 2, (p2.Y + p3.Y) / 2, (p2.Z + p3.Z) / 2), Color.Gray, new Vector3(0, 0, 0));
                    
                    //And again with p1 to p3
                    int c = (int)vertices.Count;
                    vertices = AddNormalizedVertex(vertices, new Vector3(((p1.X + p3.X) / 2), ((p1.Y + p3.Y) / 2), ((p1.Z + p3.Z) / 2)), Color.Gray, new Vector3(0, 0, 0));
                   
                    //Now reassaign the Indices to make them right (carefull at the order)
                    indices2.Add(indices[p1I]); indices2.Add(a); indices2.Add(c);
                    indices2.Add(indices[p2I]); indices2.Add(b); indices2.Add(a);
                    indices2.Add(indices[p3I]); indices2.Add(c); indices2.Add(b);
                    indices2.Add(a); indices2.Add(b); indices2.Add(c);

                }
                //The first List now gets cleared and after that it gets all the indices from the second list so it stays up-to-date with
                //the correct order
                indices.Clear();
                foreach (int index in indices2)
                {
                    indices.Add(index);
                }
            }

Aight. The lists now contains the vertices and the correct order of indices. Everything is fine till here.

Now I need to convert them to the arrays that we already created at the start:


            vertexList = new VertexPositionColorNormal[vertices.Count];
            for (int i = 0; i < vertices.Count; i++)
            {
                vertexList[i] = vertices[i];
            }

            indexList = new int[indices.Count];
            for (int i = 0; i < indices.Count; i++)
            {
                indexList[i] = indices[i];
            }

We still need to determine the Normals to make Lighting successfull


            for (int i = 0; i < indexList.Length; i++)
            {
                int index1 = indexList[i++];
                int index2 = indexList[i++];
                int index3 = indexList[i];

                Vector3 side1 = vertexList[index1].Position - vertexList[index3].Position;
                Vector3 side2 = vertexList[index1].Position - vertexList[index2].Position;
                Vector3 normal = Vector3.Cross(side1, side2);

                vertexList[index1].Normal += normal;
                vertexList[index2].Normal += normal;
                vertexList[index3].Normal += normal;
            }
            for(int i = 0; i < vertxList.Length; i++)
                vertexList[i].Normal.Normalize();

What I've done here is to go through all indices. Every cycle I store three vertices that are responsible for a triangle and determine the Vector

between them by substracting each two of them.

I only needed to do this with two of them. For the Normal, I just build the Cross-Product and add them to all three Vertices.

At the End they get Normalized so they all have the length of 1

The end of the story is to store them into the responsible buffers


            vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColorNormal), vertexList.Length, BufferUsage.WriteOnly);
            vertexBuffer.SetData<VertexPositionColorNormal>(vertexList);
            indexBuffer = new IndexBuffer(graphics.GraphicsDevice, typeof(int), indexList.Length, BufferUsage.WriteOnly);
            indexBuffer.SetData(indexList);

For the drawing I made up an own function, so I can only draw this stuff with it.


protected void DrawPrimitive(VertexPositionColorNormal[] vertices)
        {
            basicEffect.VertexColorEnabled = true;
            basicEffect.World = world;
            basicEffect.View = view;
            basicEffect.Projection = projection;
           
            GraphicsDevice.SetVertexBuffer(vertexBuffer);
            GraphicsDevice.Indices = indexBuffer;

            RasterizerState rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.None;
            GraphicsDevice.RasterizerState = rasterizerState;
            
            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                basicEffect.LightingEnabled = true;
                basicEffect.DirectionalLight0.DiffuseColor = new Vector3(1f, 0.8f, 0.5f);
                basicEffect.DirectionalLight0.Direction = new Vector3(1, 0.5f, 0);
                basicEffect.DirectionalLight0.Enabled = true;
                pass.Apply();

                GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertexList.Length, 0, indexList.Length / 3);
            }
        }

This is all what I've done.

Next aim is to get random Terrain on it.

After that I want to make the atmosphere (which will be the hard part as I can not use the normal shader-language cuz of Monogame uses MGFX-Files)

This topic is closed to new replies.

Advertisement