15 Replies  2359 Views  Last Post: 28 August 2013  10:31 AM
#1
Vanishing Vertices And Triangles
Posted 12 August 2013  05:40 PM
I've ventured into stitching multiple height maps together in order to get a rather massive landscape. I have everything pretty much figured out, except for one strange anomaly.
When I debug and look at both the start and end positions of my vertices for three of the four height map sections, it shows it starts exactly where it should (1000000, 1000000) and ends where it should, at (0,0) but when I run it, an entire row and column of triangles never render, clipping the height map section by that amount, essentially giving a start of (1000000, 1000000) and an end of (2000, 2000).
Only one height map section correctly renders, all the others, however, are affected by this anomaly to some degree, some more than others.
While I've patched it with some interpolation, collision is affected by this, and overall, I'm not satisfied in not knowing the root cause.
As I am not entirely sure where to even begin looking, I can't post much code, so if you can point me in the right direction, I'll provide code as requested.
Wishing you all well,
H2012
Replies To: Vanishing Vertices And Triangles
#2
Re: Vanishing Vertices And Triangles
Posted 12 August 2013  08:03 PM
Look for ways to break the problem into smaller problems to isolate it. My understanding is that you have 4 grids, one draws correctly (you believe), but the other three do not. Sometimes when you believe that one is working correctly that may be an illusion. Don't entirely rule out the possibility that you may be misperceiving things.
But on the assumption that one works and the other three do not. What if you draw one of the other 3 in isolation by itself? Does that change things? Perhaps focus on getting that one to draw correctly. Somehow you basically need to step through the process in the debugger. Perhaps simplify that grid.
Instead of working with 4 massive grids, work with 4 tinie tiny grids so that you can mentally comprehend what is going on. Do four 5 by 5 grids for example. Perhaps when you can visually count the rows the problem will become obvious. Perhaps even make a separate "test" program to just test this to avoid hacking up your main program. If you can reproduce the problem in the test program, especially with 5 by 5 grids, then you can understand it and fix it in the main program.
Short of that, I would probably have to have your code to work on it.
#3
Re: Vanishing Vertices And Triangles
Posted 12 August 2013  11:59 PM
I was a bit hesitant to give out code, as this project isn't entirely my own, however with your mention of creating a test program, I feel more comfortable giving it out. Thank you for mentioning that.
Test Program Code
I'm not entirely sure why that never occurred to me to do that.
Let's see, a rundown of what I've tried so far after your post.
1) Instead of an offset in vertex creation, I instead transformed a matrix and applied that to the height map sections, no effect.
2) Applied this same transformation to the inverse transpose, no effect.
3) Removed extraneous bits and bobs, no effect.
4) Drew all height map sections in isolation, no effect.
5) Messed a bit with the near and far clipping planes, no effect.
I'm not quite done messing around, but I figured that I would at least post a reply with a download link as I did not provide any code earlier.
Wishing you well,
H2012
#4
Re: Vanishing Vertices And Triangles
Posted 13 August 2013  04:28 AM
An update is needed as I've narrowed down the problem.
First, a link to the updated project files.
Updated Test Program Code
After adding a grid and removing all heights, the problem was made very clear.
It's simply not drawing a column on the far right side of each height map, and not drawing a row on the very bottom of each height map. This affects not only the ones I suspected, but also the one I thought was correct.
This explains why I could overlay the height maps with the supposedly correct one and appear to get the correct size.
I have as yet to discover the solution, however, but the problem has definitely been discovered.
Also, I'm not sure if "drawing" is the right term, it may instead not be somehow getting around to storing those vertices and/or indices.
In either case, I'll continue searching for clues and will update as things progress.
Wishing you well,
H2012
This post has been edited by H2012: 13 August 2013  04:40 AM
#5
Re: Vanishing Vertices And Triangles
Posted 13 August 2013  05:39 AM
I'm so sorry for posting so much, but it would appear I have limited editing capabilities.
Regardless, if you're still willing to help me, I want to provide as much information as I can, so here's another post with some new information.
In the constructor, there is this section.
nIndices = (width  1) * (length  1) * 6;
Out of curiosity, I modified it to be subtracted by ten, not one, and the results are as follows.
To replicate this, you have to go down to the void CreateMultipleIndices() and modify the for loops to be subtacted by the same amount
Removing the subtraction, however, which I suspect will at least create different results, results in an error on the following lines in the genMultipleNormals() void.
for (int i = 0; i < nIndices; i += 3) { //Find the position of each corner of the triangle Vector3 v1_TL = vertices_TL[indices_TL[i]].Position; Vector3 v2_TL = vertices_TL[indices_TL[i + 1]].Position; Vector3 v3_TL = vertices_TL[indices_TL[i + 2]].Position; //Cross the vectors between the corners to get the normal Vector3 normal_TL = Vector3.Cross(v1_TL  v2_TL, v1_TL  v3_TL); normal_TL.Normalize(); //Add the influence of the normal to each vertex in the triangle vertices_TL[indices_TL[i]].Normal += normal_TL; vertices_TL[indices_TL[i + 1]].Normal += normal_TL; vertices_TL[indices_TL[i + 2]].Normal += normal_TL; }
This is remedied by increasing the size of nVertices in the constructor, and the result is as follows.
Right now may be a good time to mention that this code isn't all my own. I've added and subtracted bits, namely the parts that allow for multiple height maps to be loaded and drawn, but overall, it is in its original format, and I can't say I understand completely what goes on under the hood.
Hopefully I'm not bothering you too much with all these posts, and if so, my deepest apologies.
Wishing you well,
H2012
#6
Re: Vanishing Vertices And Triangles
Posted 13 August 2013  12:43 PM
Man, this took a seriously long time to find.
//For each pixel in the image for (int z = 0; z < length; z++) { for (int x = 0; x < width; x++) { #region Positions and vertex creation for map one //Find position based on grid coordinates and height in heightmap //height_TL[x, z] //Vector3 position_TL = new Vector3(x * cellSize, 0, z * cellSize) + // new Vector3(((((float)width / 2.0f) * cellSize) * 2), 0, ((((float)length / 2.0f) * cellSize) * 2)); //BB Vector3 position_TL = new Vector3(x * cellSize, 500, z * cellSize) + new Vector3(((((float)(width1) / 2.0f) * cellSize) * 2), 0, ((((float)(length1) / 2.0f) * cellSize) * 2)); //UV coordinates range from (0, 0) at grid location (0, 0) to //(1, 1) at grid location (width, length) Vector2 uv1 = new Vector2((float)x / width, (float)z / length); //Create the vertex vertices_TL[z * width + x] = new VertexPositionNormalTexture(position_TL, Vector3.Zero, uv1); #endregion if (z * width + x == 249999) { Vector2 uvCheck = uv1; uvCheck = uvCheck; } }
Here's the problem:
//Vector3 position_TL = new Vector3(x * cellSize, 0, z * cellSize) + // new Vector3(((((float)width / 2.0f) * cellSize) * 2), 0, ((((float)length / 2.0f) * cellSize) * 2)); //BB Vector3 position_TL = new Vector3(x * cellSize, 500, z * cellSize) + new Vector3(((((float)(width1) / 2.0f) * cellSize) * 2), 0, ((((float)(length1) / 2.0f) * cellSize) * 2));
Subtracting 1 from the width and length offset mostly does the trick. I believe that you may not be considering that width is 1 to width whereas your position is 0 to width minus one. At least I think that's the problem.
I also bumped cellSize up to 2004 which in my mind means there is also an issue with that. Bumping cellSize up gets the size right, whereas doing the 1 on length and width gets the alignment right. This has to be changed for 3 of the 4 grids (one of them does not do this calculation and therefore is not affected by it).
#7
Re: Vanishing Vertices And Triangles
Posted 13 August 2013  04:36 PM
Thank you very much for looking things over. I must admit, though, I can't quite replicate your results. I can get two of the four maps rendering correctly, but the other two are offset along the Z axis rather severely.
I'm not sure how to phrase this next part, especially with how you took time out to look at things, but...
I'm looking at some numbers, recalculating, and overall revamping my thought process, and with the numbers I'm seeing, I'm not entirely sure this, repositioning and rescaling, is an absolute fix, if that makes sense.
The more I look at the numbers, the more I mess around, the more I'm coming to the conclusion that it's simply not calculating the vertices and indices for the last column and row.
I suppose it's for the best that I provide some evidence to back this up, I don't want you to be mad at me or think I'm just disregarding your work, I really do appreciate the time you've put into trying to help me out.
Basically, I've removed all offsets and translations, gotten them all back to basically where their default position is, which is with their top left corner being at the coordinates 0x and 0z.
Shown above is the "default" settings and the outcome, also of note is that nVertices, which defines the vertex array size, is equal to width * length.
For a while, I've been wondering about why the last value in a row is one short than what I would expect, and why the value is one cell size less than expected.
I suppose, summing it up, I took this as just a part of array size with it starting at zero and disregarded it, up until I rethought some things a short while ago.
Looking at these values, they stop exactly at what is shown at render time, which is to say exactly one cell size short of a full row.
This is the last value stored in this array, stopping short, yet again, of a full row, and not even getting to the very last row, and this is exactly what is shown at render time.
Now, this next bit I have no render screenshots as I have not yet got this resolved, but I do have numbers, and I suppose for now, numbers will have to do.
Altering nVertices a bit, adding one to each value, and then altering the for loops to have one added to the length and width, shows exactly what I would expect to show up on screen, which is to say an entire 1,000,000 x 1,000,000 rendered height map, however, as mentioned previously, this is limited to just numbers right now.
I was curious, though, to see if this problem existed before I added in all the height map loading and such.
To my surprise, this is a preexisting problem. It's in the original height map class, and not just in the one I've altered for multiple height maps.
I've never noticed it until I tried to stitch multiple height maps together, when the problem became obvious.
Now, the question I have is how to do the same to the indices that I did to the vertices.
I'm making progress, and in case you're wondering, I'm updating this as I go along.
In either case, now almost the entire bottom row is rendered!
How I achieved this is as follows.
Previously, the code was this.
nIndices = (width  1) * (length  1) * 6; //For each cell for (int x = 0; x < width  1; x++) for (int z = 0; z < length  1; z++) { #region Indices for the first map //Find the indices of the corners int upperLeft1 = z * width + x; int upperRight1 = upperLeft1 + 1; int lowerLeft1 = upperLeft1 + width; int lowerRight1 = lowerLeft1 + 1; //Specify upper triangle indices_TL[i++] = upperLeft1; indices_TL[i++] = upperRight1; indices_TL[i++] = lowerLeft1; //Specify lower triangle indices_TL[i++] = lowerLeft1; indices_TL[i++] = upperRight1; indices_TL[i++] = lowerRight1; #endregion }
However, I altered it with the vertex alteration in mind to be as such
nIndices = (width + 1) * (length + 1) * 6; //For each cell for (int x = 0; x < width; x++) for (int z = 0; z < length; z++) { #region Indices for the first map //Find the indices of the corners int upperLeft1 = z * width + x; int upperRight1 = upperLeft1 + 1; int lowerLeft1 = upperLeft1 + width; int lowerRight1 = lowerLeft1 + 1; //Specify upper triangle indices_TL[i++] = upperLeft1; indices_TL[i++] = upperRight1; indices_TL[i++] = lowerLeft1; //Specify lower triangle indices_TL[i++] = lowerLeft1; indices_TL[i++] = upperRight1; indices_TL[i++] = lowerRight1; #endregion }
Now, to see if I can get that final column to show up.
I can't quite get that last column to work for me.
However, I am fairly certain that the alterations to nVertices and the for loops that create them is accurate.
At this point, I'll let this post go and continue working on this.
You've definitely helped me start to unravel the mystery, and thank you very much for that.
If you have any more ideas, I'm more than open to them.
Wishing you well,
H2012
#8
Re: Vanishing Vertices And Triangles
Posted 13 August 2013  07:23 PM
Honestly, even I recognized that the solution that I was giving did not fully answer the question of what went wrong. I spent a lot of time on it and got it working, but that's not exactly the same as explaining why it did not work in the first place.
But let's figure out where the difference is. I was able to alter the code to make it the four grids fit "almost" perfectly into the white grid lines and so declared victory. :)
I'm enclosing the full source code of what I had that I believe solved the problem. Now originally I made extensive modifications to this code testing verious theories as to what went wrong. Eventually, I discovered that I was not making progress and did a Cntrl Z to undo all my changes. Possibly this did not completely undo every change I made.
Maybe you can take my code here and copy and paste and see if it produces the results you are looking for.
It actually is possible that I made another change beyond what I described.
Take a look at this and let me know what you think and I'll take a look at it tomorrow after hearing what you think of this.
Here is the complete source code listing of what I used that brought the corners of all four grids together at 0,0,0.
And know that I haven't taken offense to anything you have said. ;)
using System; using System.Collections.Generic; using System.Linq; 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.Media; namespace Test { public class Heightmap_Generation { public bool hasBeenDeclared; //keeps us from redeclaring shit and it messing up public VertexPositionNormalTexture[] vertices; //Vertex array public int[] indices; //Index array public float[,] heights; //Array of vertex heights public float height; //Maximum height of terrain public float cellSize; //Distance between vertices on x and z axes public int width, length; //Number of vertices on x and z axes int nVertices, nIndices; //Number of vertices and indices public Effect effect; //Effect used for rendering GraphicsDevice GraphicsDevice; //Graphics device to draw with //For multiple height maps Texture2D heightMap1, heightMap2, heightMap3, heightMap4; public float[,] height_TL, height_TR, height_BR, height_BL; public float[,] TotalHeights = new float[1000, 1000]; public VertexPositionNormalTexture[] vertices_TL, vertices_TR, vertices_BR, vertices_BL; int[] indices_TL, indices_TR, indices_BR, indices_BL; VertexBuffer vertexBuffer_TL, vertexBuffer_TR, vertexBuffer_BR, vertexBuffer_BL; IndexBuffer indexBuffer_TL, indexBuffer_TR, indexBuffer_BR, indexBuffer_BL; public Texture2D WeightMap1, WeightMap2, WeightMap3, WeightMap4; Texture2D baseTexture; float textureTiling; public Texture2D DetailTexture; public float DetailDistance = 20000; public float DetailTextureTiling = 1000; Vector3 lightDirection; Vector3 lightColor; Vector4 ambientLightColor; Vector4 diffuseLightColor; Vector2 theseHeights { get; set; } public Texture2D RTexture, BTexture, GTexture, WeightMap; void notes() { /* * z *  * x  x * T *  *  * z C * * So, without the offset, the topleft corner (T) of the map will be at the world's origin of 0,0,0 * The center (C) will be at the very last coordinate of the grid. * The rest, 3/4, of the map lies outside of the grid shown above * * T z *  * x  x * C *  *  * z * * With the default offset, however, provided with the book, it shifts the topleft corner (T) to the world's topleft corner, * and the center aligns with the world's center * * Now to take advantage of this... * * * Idea, four land grids centered around the world grid * * Z *  * z  z *    * x  xx  x * ** *    *    * z  z *XCX * z  z *    * x  xx  x * ** *    *    * z  z *  * Z * * * = land piece center * C = world center * * In the case of the topleft land piece, only the bottom right corner would be at the world's center * In the case of the topright land piece, only the bottom left would be at the world's center * Bottomleft, top right would be at world's center * Bottomright, top left would be at world's center * * each grid is, in this case, 500000,500000 x 500000,500000 * giving us a world grid of.... 1000000, 1000000 x 1000000, 1000000 * * now, how to actually implement this. * * THING TO KEEP IN MIND: * arrays start at zero, so your maximum of 250000 is actually 249999 * * the topleft piece. * the coordinate at vertices[249999] has to be at 0,0 world grid * * the topright piece * the coordinate at vertices[249500] has to be at 0,0 world grid * so the coordinate at vertices[250000] will be at 0, 500000 land grid * in world grid, it will be 1000000, 0 * * the bottomleft piece * the coorindate at vertices[500] has to be at 0,0 world grid * so the coordinate at vertices[249999] will be at 500000, 500000 land grid * in world grid, it will be 0, 1000000 * * the bottomright piece * the coordinate at vertices[0] has to be at 0,0 world grid * so the coordinate at vertices[249999] will be at 500000, 500000 land grid * in world grid, it will be 1000000, 1000000 * * * now, to dissect the default offset * * ((float)width / 2.0f) * cellSize * our x value * it takes the width, in this case, 500, divides it by 2 in order to get a total of 250, then multiplies this by the cellsize * which is 2,000 in this case, giving us a total of 500000 * why? * well, the default grid is all positive, that is to say the heights stored in the heights array starts at 0,0 and ends at 500, 500 * so it needs to convert this into a grid that aligns to the world axis that starts at a negative and ends at a positive * we have to divide by two so that we don't get something beyond the bounds of the actual grid. * our grid, when multiplied by the cellsize, comes out to 500000, 500000 x 500000, 500000 * so everything has to be within this grid space. * if you didn't divide by two, you would get double the size of the actual grid * * this same logic applies to the z because our grid is even and at even intervals * * as for why its end vector3 is negative... * our forward is a negative z, our left is a negative x, our right is a positive x, and our backward is a positive z * so basically by negating the vector3, you're moving it left and up by 500000, giving your topleft corner a position of 500000, 500000 * and giving your center a value of 0, 0, * thus centering every single point around the world's center. * * this is all in application to one heightmap, centered to one grid: the world grid * now to apply it to multiple, four, grids, around the one world grid * * Z *  * z  z *    * x  xx  x * ** *    *    * z  z *XCX * z  z *    * x  xx  x * ** *    *    * z  z *  * Z * * * = land piece center * C = world center * * so, thinking it in terms of what we're trying to do. * every heightmap, at this point, will be centered to (C) the world's center. * whereas we need to shift things further, I suppose double the amount, and apply that to each piece, * using positives and negatives as needed for each piece * every heightmap will start out with its topleft corner at the world's center * this is very important * every heightmap will start out with its topleft corner at the center of the world grid without any offset * * so now we have to figure out how to shift everything over * * the offset seems to work like this: * it takes the last coordinate and shifts it over by, in this case, 500000,500000. * without it, the last coordinate, in this case, would be 1000000, 1000000 * but with it, it cuts that in half getting everything centered around the world's center. * * we need to think in terms of the last coordinate and how the offset will affect it * * the topleft piece: * starts with the topleft corner at the center * we need to shift it over by... * double the default offset I think * works * * the topright piece * starts with the topleft corner at the center * it needs shifted up by... no x movement, only z movement... * double the z? zero out the x? * * the bottomleft piece * starts with the topleft corner at the center * it needs shifted... * over by double, but not up any * * the bottom right piece * starts with the topleft corner at the center, * so... I think it's fine asis with no offset * * * Important observation * * * without any offset, the max point is not 1000000, 1000000 as I would expect, but 980000, 980000 * basically, the values will be accurate if it looks like it has another row to get to * I'm not sure why at this point, but that's just what the numbers show * * * with all the math concerning that out of the way, need a system to actually draw this and give accurate collision detection * * first thought is a new heights[,] multidimensional array with a size double its original, so [width * 2, length * 2] * not 100% sure about that, but it seems like it will work because if you think about it * you have 1000 total points on the x and z axis, multiply that by the cellsize, which is 2000 * you get... 2 million... So that won't work... Nevermind... * * look at our default size, which is heights[500, 500] * wait... * Hm. * * Let's look at things a bit, gridwise * *  X *  *  *  *  *  *  *  * Z * * This is the grid, aligned to our world view, of the heights array * *  X (500) *  *  *  *  *  *  *  * Z * (500) * * Each axis has a total of 500 points * how this originally works is that this is divided by two and shifted over to the left and up to get to what our world alignment is * * (250) * z *  * x  x * (250) C(250) *  *  * z * (250) * * This is then multiplied by the cellsize to get our final, correctly sized, grid, with our land centered with the world center * * (500000) * z *  * x  x * (500000) C(500000) *  *  * z * (500000) * * So, looking at our slightly modified grid * * Z *  * z  z *    * x  xx  x * ** *    *    * z  z *XCX * z  z *    * x  xx  x * ** *    *    * z  z *  * Z * * We have to account for each land mass being in one quadrant of the world's axes * so, this seems to imply that we need to double how many points we have to fit all of our land into one heights array * basically, heights[width * 2, length * 2] to get, in this case, an array of 1000x1000 points, as opposed to just 500x500 * * in conclusion to this, if we do a new heights array, we need to double it, and I think that it's a start to make a new one * * now to actually implementing this * we will have four heights[,] arrays, one for each landmass * now the question is how to populate one heights[,] array with all the data from those four heights[,] arrays * * think of how one is populated * it starts at the top left, moving to the right, then jumps back down to the far left, over to the right, etc. * until the entire array is populated * * four of these, basically * *  X (500) *  *  *  *  *  *  *  * Z * (500) * * Well, it seems like we just do the exact same thing, only on a larger scale and with which land mass is which. * * *  X (1000) * Z Z *   *   *   *   *   *   *   *   *   *   *  X X * Z Z *   *   *   *   *   *   *   *   *   *   *  X X * Z * (1000) * * top left corner is for the top left landmass and its height array * top right for that landmass' height array * bottom left, bottom left land height array * bottom right, bottom right land height array * * * this new heights[,] array will be used for collision * * * * As for actually using all four heightmaps * I'm thinking a bool system * or possibly an enum * * so an enum system works great and everything is now drawing correctly * as an aside, an offset was needed to make them all line up perfectly * now the only issue is they don't like up perfectly, heightwise * * so I can see a for loop with interpolation, checking the bounds' height with the heights of the bounds next to it * using interpolation to make them stitch together * * * now, we can't alter them outside of this void * so we need a system that allows altering as each map is loaded * and checks for each border to tell it which to check * * this means the first map loaded is untouchable, it cannot be altered after it's loaded * so we have to do everything based on the current map after the first map * we can use the enum to tell us which is which * so TopLeft is untouchable, * TopRight has to check its left border against TopLeft's right border * BottomRight has to check its top border with TopRight's bottom border * BottomLeft has to check its top border against the bottom of TopLeft, and its right border with BottomRight's left border * * our load order is now very important * TopLeft > TopRight > BottomRight > BottomLeft * * we need to store our heights in something else for each map so we can check the new heights against this and adjust accordingly * basically, this class now has to keep track of which map is loaded. * * turns out the above was a little inaccurate, * what I needed to do was be able to keep track of all the heights for each height map, not really load them into anything else * using an array of height maps didn't work because it would load the information, but seperate from the other arrays * what I ended up doing was creating an overload that allows for up to four height maps to be loaded at once. * this allows all the possible data I could ever need for the heightmap to be accessible for each height map. * * also, an offset was needed to center everything, only (cellsize / 2) though. * * * our load order is still important, though, I think * * now the next part: figuring out exactly which values we need for each map. * simple enough in theory. * for the second height map, we need access to the height information of the right border of the first height map * third height map needs information for the bottom border of the second heightmap * fourth needs access to the information for the left border of the third, and bottom of the first * * and we'll need to run through each value, checking each one for inconsistencies, * then adjusting the height of the vertex in question * using interpolation to the height of the border it's being checked against * I think using an enum will work out great for this * * now to test if this will even work * it will indeed work * * now to figure out the details of how to get the proper heights * * the second map needs the right border's heights of the first map * * First thing to keep in mind is how things are positioned according to world space * * Z *  * z  z *    * x  xx  x * ** *    *    * z  z *XCX * z  z *    * x  xx  x * ** *    *    * z  z *  * Z * * So our right border will be at 0 X, with 0  500 being our Z range * * * each time our z changes and x is, in this case, 1000 * we need to log the height, check it, and change if needed * * * with the height maps stitched together, it's time for collision * * we absolutely have to have all the heights in one array so we don't have multiple voids checking for collision all at once * * the questions are as follows: * how to load them in, and does load order matter? * * Personally, I like the thought of reading them in as one giant grid, not as individual pieces. * so that means we have to read in the first row of height array one, then when that is down, we go over to height array two, * continuing on like that until we've reached the end * I'm not sure how to do that, though, as I've not really worked with multidimensional arrays before. * *  X (500) *  *  *  *  *  *  *  * Z * (500) * *  X (1000) *  *  *  *  *  *  *  * Z * (1000) * * we have an x going up by one, and a z going up by one * * new thought: we have one loop going through the X values automatically, and then the Z values are added "manually," I.E. z++; * basically, we say "if the stored z equals the current z, we're on the same row, so only populate the x values" * "if the stored z doesn't equal the current z, add to the z and continue on." * * Let's look at it, instead, as just two one dimensional arrays. * for the first 500 X values in one array, and the first 500 values in the second array, * we want to populate one array with that string of values * * with that accomplished, now it's on to applying this to collision * */ } //Overload for up to four heightmaps public Heightmap_Generation(Texture2D HeightMap1, Texture2D HeightMap2, Texture2D HeightMap3, Texture2D HeightMap4, float CellSize, float Height, Texture2D BaseTexture, float TextureTiling, GraphicsDevice GraphicsDevice, ContentManager Content) { this.heightMap1 = HeightMap1; this.heightMap2 = HeightMap2; this.heightMap3 = HeightMap3; this.heightMap4 = HeightMap4; this.width = HeightMap1.Width; this.length = HeightMap1.Height; this.cellSize = CellSize; this.height = Height; this.baseTexture = BaseTexture; this.textureTiling = TextureTiling; this.GraphicsDevice = GraphicsDevice; effect = Content.Load<Effect>("Land Effect"); //1 vertex per pixel nVertices = width * length; //(Width1) * (Length1) cells, 2 triangles per cell, 3 indices per triangle nIndices = (width  1) * (length  1) * 6; vertexBuffer_TL = new VertexBuffer(GraphicsDevice, typeof(VertexPositionNormalTexture), nVertices, BufferUsage.WriteOnly); vertexBuffer_TR = new VertexBuffer(GraphicsDevice, typeof(VertexPositionNormalTexture), nVertices, BufferUsage.WriteOnly); vertexBuffer_BR = new VertexBuffer(GraphicsDevice, typeof(VertexPositionNormalTexture), nVertices, BufferUsage.WriteOnly); vertexBuffer_BL = new VertexBuffer(GraphicsDevice, typeof(VertexPositionNormalTexture), nVertices, BufferUsage.WriteOnly); indexBuffer_TL = new IndexBuffer(GraphicsDevice, IndexElementSize.ThirtyTwoBits, nIndices, BufferUsage.WriteOnly); indexBuffer_TR = new IndexBuffer(GraphicsDevice, IndexElementSize.ThirtyTwoBits, nIndices, BufferUsage.WriteOnly); indexBuffer_BR = new IndexBuffer(GraphicsDevice, IndexElementSize.ThirtyTwoBits, nIndices, BufferUsage.WriteOnly); indexBuffer_BL = new IndexBuffer(GraphicsDevice, IndexElementSize.ThirtyTwoBits, nIndices, BufferUsage.WriteOnly); getMultipleHeights(); createMultipleVertices(); createMultipleIndices(); genMultipleNormals(); createFullHeightsArray(1); vertexBuffer_TL.SetData<VertexPositionNormalTexture>(vertices_TL); vertexBuffer_TR.SetData<VertexPositionNormalTexture>(vertices_TR); vertexBuffer_BR.SetData<VertexPositionNormalTexture>(vertices_BR); vertexBuffer_BL.SetData<VertexPositionNormalTexture>(vertices_BL); indexBuffer_TL.SetData<int>(indices_TL); indexBuffer_TR.SetData<int>(indices_TR); indexBuffer_BR.SetData<int>(indices_BR); indexBuffer_BL.SetData<int>(indices_BL); } public void updateHeightmap_Generation(GameTime gameTime, Vector3 ourLightDirection, Vector3 ourLightColor, Vector4 ourAmbientColor, Vector4 ourDiffuseColor) { this.lightDirection = ourLightDirection; this.lightColor = new Vector3(MathHelper.Clamp(ourLightColor.X, 0, 1), MathHelper.Clamp(ourLightColor.Y, 0, 1), MathHelper.Clamp(ourLightColor.Z, 0, 1)); this.ambientLightColor = new Vector4(MathHelper.Clamp(ourAmbientColor.X, 0, 1), MathHelper.Clamp(ourAmbientColor.Y, 0, 1), MathHelper.Clamp(ourAmbientColor.Z, 0, 1), MathHelper.Clamp(ourAmbientColor.W, 0, 1)); this.diffuseLightColor = new Vector4(MathHelper.Clamp(ourDiffuseColor.X, 0, 1), MathHelper.Clamp(ourDiffuseColor.Y, 0, 1), MathHelper.Clamp(ourDiffuseColor.Z, 0, 1), MathHelper.Clamp(ourDiffuseColor.W, 0, 1)); } #region For up to four height maps void createFullHeightsArray(int startAtCase) { //Half of the values are centered correctly, half of the values are not // int Z = 0; //Switch/case statement for easily going back and forth between loops //Three cases for the operations we want to accomplish //First and second one, our X values on the "left" side //Third and fourth one, our X values on the "right" side //Fifth one, our Z value increases switch (startAtCase) { case 1: //Take care of the first heights array, topleft for (int X = 0; X < 500; X++) { TotalHeights[X, Z] = height_TL[X, Z]; if (X == 499) { //We've reached the end of this row, go to the topright array goto case 3; } } break; case 2: //Take care of the fourth heights array, bottomleft for (int X = 0; X < 500; X++) { TotalHeights[X, Z] = height_BL[X, Z  500]; if (X == 499) { //We've reached the end of this row, go to the bottomright array goto case 4; } } break; case 3: //Take care of the second heights array, topright for (int X = 0; X < 500; X++) { //Make sure we start at 500 on our TotalHeights array TotalHeights[X + 500, Z] = height_TR[X, Z]; if (X == 499) { //We've reached the end of the row, go to case five so we can change to our row and keep populating our array goto case 5; } } break; case 4: //Take care of the third heights array, bottomright for (int X = 0; X < 500; X++) { TotalHeights[X + 500, Z] = height_BR[X, Z  500]; if (X == 499) { //We've reached the end of this row, go to the fifth case to change our Z goto case 5; } } break; case 5: //We've reached the end of a row, time to move on to the next row Z++; //If we're still on the top sections if (Z <= 499) { goto case 1; } //If we've finished the top, time to move on to the bottom sections if (Z > 499 && Z < 1000) { goto case 2; } break; default: break; } } private void getMultipleHeights() { //Extract pixel data Color[] heightMapData1 = new Color[width * length]; heightMap1.GetData<Color>(heightMapData1); Color[] heightMapData2 = new Color[width * length]; heightMap2.GetData<Color>(heightMapData2); Color[] heightMapData3 = new Color[width * length]; heightMap3.GetData<Color>(heightMapData3); Color[] heightMapData4 = new Color[width * length]; heightMap4.GetData<Color>(heightMapData4); //Create heights_(Location)[,] array height_TL = new float[width, length]; height_TR = new float[width, length]; height_BR = new float[width, length]; height_BL = new float[width, length]; //For each pixel for (int y = 0; y < length; y++) { for (int x = 0; x < width; x++) { #region Heights for the first map //Get color value (0  255) float amt1 = heightMapData1[y * width + x].R; //Scale to (0  1) amt1 /= 255.0f; //Multiply by max height to get final height height_TL[x, y] = amt1 * height; #endregion } } for (int y = 0; y < length; y++) { for (int x = 0; x < width; x++) { #region Heights for the second map //Get color value (0  255) float amt2 = heightMapData2[y * width + x].R; //Scale to (0  1) amt2 /= 255.0f; //Multiply by max height to get final height height_TR[x, y] = amt2 * height; #endregion } } for (int y = 0; y < length; y++) { for (int x = 0; x < width; x++) { #region Heights for the third map //Get color value (0  255) float amt3 = heightMapData3[y * width + x].R; //Scale to (0  1) amt3 /= 255.0f; //Multiply by max height to get final height height_BR[x, y] = amt3 * height; #endregion } } for (int y = 0; y < length; y++) { for (int x = 0; x < width; x++) { #region Heights for the fourth map //Get color value (0  255) float amt4 = heightMapData4[y * width + x].R; //Scale to (0  1) amt4 /= 255.0f; //Multiply by max height to get final height height_BL[x, y] = amt4 * height; #endregion } } } private void createMultipleVertices() { #region Declarations and intialization vertices_TL = new VertexPositionNormalTexture[nVertices]; vertices_TR = new VertexPositionNormalTexture[nVertices]; vertices_BR = new VertexPositionNormalTexture[nVertices]; vertices_BL = new VertexPositionNormalTexture[nVertices]; //Left array: number of map sections, right array: number of vertices in each //Two for each map for multiple checks //0 = X border of map one //1 = Z border of map two //2 = X border of map one //3 = Z border of map two Vector3[,] thesePositions = new Vector3[10, 501]; //Which map out multidimensional array is on //Two for each map for multiple checks int[] whichMap = new int[10]; //Our desired X, one variable for each map float[] desiredX = new float[10]; float[] desiredZ = new float[10]; //The first map's bottom border's Z desiredZ[0] = cellSize; //The second map's bottom border's Z desiredZ[1] = cellSize; //The third map's top border's Z desiredZ[2] = 0; //The fourth map's top border's Z desiredZ[3] = 0; //The first map's right border's X desiredX[0] = cellSize; //The second map's left border's X desiredX[1] = 0; //The third map's left border's X desiredX[2] = 0; //Thr fourth map's right border's X desiredX[3] = cellSize; #endregion #region Vertex creation //For each pixel in the image for (int z = 0; z < length; z++) { for (int x = 0; x < width; x++) { #region Positions and vertex creation for map one //Find position based on grid coordinates and height in heightmap //height_TL[x, z] //Vector3 position_TL = new Vector3(x * cellSize, 0, z * cellSize) + // new Vector3(((((float)width / 2.0f) * cellSize) * 2), 0, ((((float)length / 2.0f) * cellSize) * 2)); //BB Vector3 position_TL = new Vector3(x * cellSize, 500, z * cellSize) + new Vector3(((((float)(width1) / 2.0f) * cellSize) * 2), 0, ((((float)(length1) / 2.0f) * cellSize) * 2)); //UV coordinates range from (0, 0) at grid location (0, 0) to //(1, 1) at grid location (width, length) Vector2 uv1 = new Vector2((float)x / width, (float)z / length); //Create the vertex vertices_TL[z * width + x] = new VertexPositionNormalTexture(position_TL, Vector3.Zero, uv1); #endregion if (z * width + x == 249999) { Vector2 uvCheck = uv1; uvCheck = uvCheck; } } } for (int z = 0; z < length; z++) { for (int x = 0; x < width; x++) { #region Positions and vertex creation/manipulation for map two //Find position based on grid coordinates and height in heightmap //height_TR[x, z] Vector3 position_TR = new Vector3(x * cellSize, 0, z * cellSize) + new Vector3(0, 0, ((((float)(length1) / 2.0f) * cellSize) * 2)); //UV coordinates range from (0, 0) at grid location (0, 0) to //(1, 1) at grid location (width, length) Vector2 uv2 = new Vector2((float)x / (width * 2), (float)z / (length * 2)); //Create the vertex vertices_TR[z * width + x] = new VertexPositionNormalTexture(position_TR, Vector3.Zero, uv2); #endregion } } for (int z = 0; z < length; z++) { for (int x = 0; x < width; x++) { #region Positions and vertex creation/manipulation for map three //Find position based on grid coordinates and height in heightmap //height_BR[x, z] Vector3 position_BR = new Vector3(x * cellSize, 0, z * cellSize) + new Vector3(0, 0, 0); //UV coordinates range from (0, 0) at grid location (0, 0) to //(1, 1) at grid location (width, length) Vector2 uv3 = new Vector2((float)x / width, (float)z / length); //Create the vertex vertices_BR[z * width + x] = new VertexPositionNormalTexture(position_BR, Vector3.Zero, uv3); #endregion } } for (int z = 0; z < length; z++) { for (int x = 0; x < width; x++) { #region Positions and vertex creation/manipulation for map four //Find position based on grid coordinates and height in heightmap //height_BL[x, z] Vector3 position_BL = new Vector3(x * cellSize, 0, z * cellSize) + new Vector3(((((float)(width 1) / 2.0f) * cellSize) * 2), 0, 0); //UV coordinates range from (0, 0) at grid location (0, 0) to //(1, 1) at grid location (width, length) Vector2 uv4 = new Vector2((float)x / width, (float)z / length); //Create the vertex vertices_BL[z * width + x] = new VertexPositionNormalTexture(position_BL, Vector3.Zero, uv4); #endregion } } #endregion } private void createMultipleIndices() { indices_TL = new int[nIndices]; indices_TR = new int[nIndices]; indices_BR = new int[nIndices]; indices_BL = new int[nIndices]; int i = 0; //For each cell for (int x = 0; x < width  1; x++) for (int z = 0; z < length  1; z++) { #region Indices for the first map //Find the indices of the corners int upperLeft1 = z * width + x; int upperRight1 = upperLeft1 + 1; int lowerLeft1 = upperLeft1 + width; int lowerRight1 = lowerLeft1 + 1; //Specify upper triangle indices_TL[i++] = upperLeft1; indices_TL[i++] = upperRight1; indices_TL[i++] = lowerLeft1; //Specify lower triangle indices_TL[i++] = lowerLeft1; indices_TL[i++] = upperRight1; indices_TL[i++] = lowerRight1; #endregion } i = 0; //For each cell for (int x = 0; x < width  1; x++) for (int z = 0; z < length  1; z++) { #region Indices for the second map //Find the indices of the corners int upperLeft2 = z * width + x; int upperRight2 = upperLeft2 + 1; int lowerLeft2 = upperLeft2 + width; int lowerRight2 = lowerLeft2 + 1; //Specify upper triangle indices_TR[i++] = upperLeft2; indices_TR[i++] = upperRight2; indices_TR[i++] = lowerLeft2; //Specify lower triangle indices_TR[i++] = lowerLeft2; indices_TR[i++] = upperRight2; indices_TR[i++] = lowerRight2; #endregion } i = 0; //For each cell for (int x = 0; x < width  1; x++) for (int z = 0; z < length  1; z++) { #region Indices for the third map //Find the indices of the corners int upperLeft3 = z * width + x; int upperRight3 = upperLeft3 + 1; int lowerLeft3 = upperLeft3 + width; int lowerRight3 = lowerLeft3 + 1; //Specify upper triangle indices_BR[i++] = upperLeft3; indices_BR[i++] = upperRight3; indices_BR[i++] = lowerLeft3; //Specify lower triangle indices_BR[i++] = lowerLeft3; indices_BR[i++] = upperRight3; indices_BR[i++] = lowerRight3; #endregion } i = 0; for (int x = 0; x < width  1; x++) for (int z = 0; z < length  1; z++) { #region Indices for the fourth map //Find the indices of the corners int upperLeft4 = z * width + x; int upperRight4 = upperLeft4 + 1; int lowerLeft4 = upperLeft4 + width; int lowerRight4 = lowerLeft4 + 1; //Specify upper triangle indices_BL[i++] = upperLeft4; indices_BL[i++] = upperRight4; indices_BL[i++] = lowerLeft4; //Specify lower triangle indices_BL[i++] = lowerLeft4; indices_BL[i++] = upperRight4; indices_BL[i++] = lowerRight4; #endregion } } private void genMultipleNormals() { //For each triangle for (int i = 0; i < nIndices; i += 3) { #region Normals for the first map //Find the position of each corner of the triangle Vector3 v1_TL = vertices_TL[indices_TL[i]].Position; Vector3 v2_TL = vertices_TL[indices_TL[i + 1]].Position; Vector3 v3_TL = vertices_TL[indices_TL[i + 2]].Position; //Cross the vectors between the corners to get the normal Vector3 normal_TL = Vector3.Cross(v1_TL  v2_TL, v1_TL  v3_TL); normal_TL.Normalize(); //Add the influence of the normal to each vertex in the triangle vertices_TL[indices_TL[i]].Normal += normal_TL; vertices_TL[indices_TL[i + 1]].Normal += normal_TL; vertices_TL[indices_TL[i + 2]].Normal += normal_TL; #endregion #region Normals for the second map //Find the position of each corner of the triangle Vector3 v1_TR = vertices_TR[indices_TR[i]].Position; Vector3 v2_TR = vertices_TR[indices_TR[i + 1]].Position; Vector3 v3_TR = vertices_TR[indices_TR[i + 2]].Position; //Cross the vectors between the corners to get the normal Vector3 normal_TR = Vector3.Cross(v1_TR  v2_TR, v1_TR  v3_TR); normal_TR.Normalize(); //Add the influence of the normal to each vertex in the triangle vertices_TR[indices_TR[i]].Normal += normal_TR; vertices_TR[indices_TR[i + 1]].Normal += normal_TR; vertices_TR[indices_TR[i + 2]].Normal += normal_TR; #endregion #region Normals for the third map //Find the position of each corner of the triangle Vector3 v1_BR = vertices_BR[indices_BR[i]].Position; Vector3 v2_BR = vertices_BR[indices_BR[i + 1]].Position; Vector3 v3_BR = vertices_BR[indices_BR[i + 2]].Position; //Cross the vectors between the corners to get the normal Vector3 normal_BR = Vector3.Cross(v1_BR  v2_BR, v1_BR  v3_BR); normal_BR.Normalize(); //Add the influence of the normal to each vertex in the triangle vertices_BR[indices_BR[i]].Normal += normal_BR; vertices_BR[indices_BR[i + 1]].Normal += normal_BR; vertices_BR[indices_BR[i + 2]].Normal += normal_BR; #endregion #region Normals for the fourth map //Find the position of each corner of the triangle Vector3 v1_BL = vertices_BL[indices_BL[i]].Position; Vector3 v2_BL = vertices_BL[indices_BL[i + 1]].Position; Vector3 v3_BL = vertices_BL[indices_BL[i + 2]].Position; //Cross the vectors between the corners to get the normal Vector3 normal_BL = Vector3.Cross(v1_BL  v2_BL, v1_BL  v3_BL); normal_BL.Normalize(); //Add the influence of the normal to each vertex in the triangle vertices_BL[indices_BL[i]].Normal += normal_BL; vertices_BL[indices_BL[i + 1]].Normal += normal_BL; vertices_BL[indices_BL[i + 2]].Normal += normal_BL; #endregion } //Average the influences of the triangles touching each vertex for (int i = 0; i < nVertices; i++) { vertices_TL[i].Normal.Normalize(); vertices_TR[i].Normal.Normalize(); vertices_BR[i].Normal.Normalize(); vertices_BL[i].Normal.Normalize(); } } #endregion public float GetAngleAtPosition(float X, float Z, out float Steepness) { //Clamp coordinates to locations on the terrain X = MathHelper.Clamp(X, (width / 2) * cellSize, (width / 2) * cellSize); Z = MathHelper.Clamp(Z, (length / 2) * cellSize, (length / 2) * cellSize); //Map from (Width/2 > Width/2, Length.2 > Length/2) to (0 > Width, 0 > Length) X += (width / 2f) * cellSize; Z += (length / 2f) * cellSize; //Map to cell coordinates X /= cellSize; Z /= cellSize; //Truncate coordinates to get coordinates of top left cell vertex int x1 = (int)X; int z1 = (int)Z; //Try to get coordinates of bottom right cell vertex int x2 = x1 + 1 == width ? x1 : x1 + 1; int z2 = z1 + 1 == length ? z1 : z1 + 1; //Get the heights at the two corners of the cell float h1 = heights[x1, z1]; float h2 = heights[x2, z2]; //Determine steepness, angle between higher and lower vertex cell Steepness = (float)Math.Atan(Math.Abs((h1  h2)) / (cellSize * Math.Sqrt(2))); //Find the average of the amounts lost from coordinates during truncation float leftOver = ((X  x1) + (Z  z1)) / 2f; //Interpolate between the height of the corner vertices. return MathHelper.Lerp(h1, h2, leftOver); } public void Draw(Matrix View, Matrix Projection, Vector3 cameraPosition) { Matrix WorldInverseTranspose_Matrix = Matrix.Identity; GraphicsDevice.SetVertexBuffer(vertexBuffer_TL); GraphicsDevice.Indices = indexBuffer_TL; effect.Parameters["View"].SetValue(View); effect.Parameters["Projection"].SetValue(Projection); effect.Parameters["BaseTexture"].SetValue(baseTexture); effect.Parameters["TextureTiling"].SetValue(textureTiling); effect.Parameters["DiffuseLightDirection"].SetValue(Vector3.One); effect.Parameters["DiffuseColor"].SetValue(new Vector4(1, 1, 1, 1)); effect.Parameters["DiffuseIntensity"].SetValue(1); effect.Parameters["AmbientColor"].SetValue(new Vector4(0.5f, 0.5f, 0.5f, 1f)); effect.Parameters["AmbientIntensity"].SetValue(1); Matrix World1 = Matrix.CreateTranslation(1000000, 0, 1000000); Matrix Normalize1 = World1 * Matrix.CreateTranslation(Vector3.Normalize(new Vector3(1000000, 0, 1000000))); effect.Parameters["WorldInverseTranspose"].SetValue(Matrix.Identity); effect.Parameters["RTexture"].SetValue(RTexture); effect.Parameters["GTexture"].SetValue(GTexture); effect.Parameters["BTexture"].SetValue(BTexture); effect.Parameters["WeightMap"].SetValue(WeightMap1); effect.Parameters["DetailTexture"].SetValue(DetailTexture); effect.Parameters["DetailDistance"].SetValue(DetailDistance); effect.Parameters["DetailTextureTiling"].SetValue(DetailTextureTiling); effect.Parameters["wvp"].SetValue(Matrix.Identity * View * Projection); effect.Techniques[0].Passes[0].Apply(); GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, nVertices, 0, nIndices / 3); GraphicsDevice.SetVertexBuffer(vertexBuffer_TR); GraphicsDevice.Indices = indexBuffer_TR; effect.Parameters["View"].SetValue(View); effect.Parameters["Projection"].SetValue(Projection); effect.Parameters["BaseTexture"].SetValue(baseTexture); effect.Parameters["TextureTiling"].SetValue(textureTiling); effect.Parameters["DiffuseLightDirection"].SetValue(Vector3.One); effect.Parameters["DiffuseColor"].SetValue(new Vector4(1, 1, 1, 1)); effect.Parameters["DiffuseIntensity"].SetValue(1); effect.Parameters["AmbientColor"].SetValue(new Vector4(0.5f, 0.5f, 0.5f, 1f)); effect.Parameters["AmbientIntensity"].SetValue(1); Matrix World2 = Matrix.CreateTranslation(0, 0, 1000000); Matrix Normalize2 = World2 * Matrix.CreateTranslation(Vector3.Normalize(new Vector3(00, 0, 1000000))); effect.Parameters["WorldInverseTranspose"].SetValue(Matrix.Identity); effect.Parameters["RTexture"].SetValue(RTexture); effect.Parameters["GTexture"].SetValue(GTexture); effect.Parameters["BTexture"].SetValue(BTexture); effect.Parameters["WeightMap"].SetValue(WeightMap2); effect.Parameters["DetailTexture"].SetValue(DetailTexture); effect.Parameters["DetailDistance"].SetValue(DetailDistance); effect.Parameters["DetailTextureTiling"].SetValue(DetailTextureTiling); effect.Parameters["wvp"].SetValue(Matrix.Identity * View * Projection); effect.Techniques[0].Passes[0].Apply(); GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, nVertices, 0, nIndices / 3); GraphicsDevice.SetVertexBuffer(vertexBuffer_BR); GraphicsDevice.Indices = indexBuffer_BR; effect.Parameters["View"].SetValue(View); effect.Parameters["Projection"].SetValue(Projection); effect.Parameters["BaseTexture"].SetValue(baseTexture); effect.Parameters["TextureTiling"].SetValue(textureTiling); effect.Parameters["DiffuseLightDirection"].SetValue(Vector3.One); effect.Parameters["DiffuseColor"].SetValue(new Vector4(1, 1, 1, 1)); effect.Parameters["DiffuseIntensity"].SetValue(1); effect.Parameters["AmbientColor"].SetValue(new Vector4(0.5f, 0.5f, 0.5f, 1f)); effect.Parameters["AmbientIntensity"].SetValue(1); Matrix World3 = Matrix.CreateTranslation(0, 0, 0); Matrix Normalize3 = World3 * Matrix.CreateTranslation(Vector3.Normalize(new Vector3(0, 0, 0))); effect.Parameters["WorldInverseTranspose"].SetValue(Matrix.Identity); effect.Parameters["RTexture"].SetValue(RTexture); effect.Parameters["GTexture"].SetValue(GTexture); effect.Parameters["BTexture"].SetValue(BTexture); effect.Parameters["WeightMap"].SetValue(WeightMap3); effect.Parameters["DetailTexture"].SetValue(DetailTexture); effect.Parameters["DetailDistance"].SetValue(DetailDistance); effect.Parameters["DetailTextureTiling"].SetValue(DetailTextureTiling); effect.Parameters["wvp"].SetValue(Matrix.Identity * View * Projection); effect.Techniques[0].Passes[0].Apply(); GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, nVertices, 0, nIndices / 3); GraphicsDevice.SetVertexBuffer(vertexBuffer_BL); GraphicsDevice.Indices = indexBuffer_BL; effect.Parameters["View"].SetValue(View); effect.Parameters["Projection"].SetValue(Projection); effect.Parameters["BaseTexture"].SetValue(baseTexture); effect.Parameters["TextureTiling"].SetValue(textureTiling); effect.Parameters["DiffuseLightDirection"].SetValue(Vector3.One); effect.Parameters["DiffuseColor"].SetValue(new Vector4(1, 1, 1, 1)); effect.Parameters["DiffuseIntensity"].SetValue(1); effect.Parameters["AmbientColor"].SetValue(new Vector4(0.5f, 0.5f, 0.5f, 1f)); effect.Parameters["AmbientIntensity"].SetValue(1); Matrix World4 = Matrix.CreateTranslation(1000000, 0, 0); Matrix Normalize4 = World4 * Matrix.CreateTranslation(Vector3.Normalize(new Vector3(1000000, 0, 0))); effect.Parameters["WorldInverseTranspose"].SetValue(Matrix.Identity); effect.Parameters["RTexture"].SetValue(RTexture); effect.Parameters["GTexture"].SetValue(GTexture); effect.Parameters["BTexture"].SetValue(BTexture); effect.Parameters["WeightMap"].SetValue(WeightMap4); effect.Parameters["DetailTexture"].SetValue(DetailTexture); effect.Parameters["DetailDistance"].SetValue(DetailDistance); effect.Parameters["DetailTextureTiling"].SetValue(DetailTextureTiling); effect.Parameters["wvp"].SetValue(Matrix.Identity * View * Projection); effect.Techniques[0].Passes[0].Apply(); GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, nVertices, 0, nIndices / 3); } } public abstract class Camera { Matrix view; Matrix projection; public Matrix Projection { get { return projection; } protected set { projection = value; generateFrustum(); } } public Matrix View { get { return view; } protected set { view = value; generateFrustum(); } } public BoundingFrustum Frustum { get; private set; } protected GraphicsDevice GraphicsDevice { get; set; } public Camera(GraphicsDevice graphicsDevice) { this.GraphicsDevice = graphicsDevice; generatePerspectiveProjectionMatrix(MathHelper.PiOver4); } private void generatePerspectiveProjectionMatrix(float FieldOfView) { PresentationParameters pp = GraphicsDevice.PresentationParameters; float aspectRatio = (float)pp.BackBufferWidth / (float)pp.BackBufferHeight; this.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), aspectRatio, 1f, 1000000000000000000000000.0f); } public virtual void Update() { } private void generateFrustum() { Matrix viewProjection = View * Projection; Frustum = new BoundingFrustum(viewProjection); } public bool BoundingVolumeIsInView(BoundingSphere sphere) { return (Frustum.Contains(sphere) != ContainmentType.Disjoint); } public bool BoundingVolumeIsInView(BoundingBox box) { return (Frustum.Contains(box) != ContainmentType.Disjoint); } } public class FreeCamera : Camera { public float Yaw { get; set; } public float Pitch { get; set; } public Vector3 Position { get; set; } public Vector3 Target { get; private set; } private Vector3 translation; public FreeCamera(Vector3 Position, float Yaw, float Pitch, GraphicsDevice graphicsDevice) : base(graphicsDevice) { this.Position = Position; this.Yaw = Yaw; this.Pitch = Pitch; translation = Vector3.Zero; } public void Rotate(float YawChange, float PitchChange) { this.Yaw += YawChange; this.Pitch += PitchChange; } public void Move(Vector3 Translation) { this.translation += Translation; } public override void Update() { // Calculate the rotation matrix Matrix rotation = Matrix.CreateFromYawPitchRoll(Yaw, Pitch, 0); // Offset the position and reset the translation translation = Vector3.Transform(translation, rotation); Position += translation; translation = Vector3.Zero; // Calculate the new target Vector3 forward = Vector3.Transform(Vector3.Forward, rotation); Target = Position + forward; // Calculate the up vector Vector3 up = Vector3.Transform(Vector3.Up, rotation); // Calculate the view matrix View = Matrix.CreateLookAt(Position, Target, up); } } public class SampleGrid : IDisposable { #region Fields private int gridSize; private float gridScale; private Color gridColor; private bool isDisposed; // Rendering private VertexBuffer vertexBuffer; private int vertexCount; private int primitiveCount; private BasicEffect effect; private Matrix projection, view, world; private GraphicsDevice device; #endregion #region Public Properties public Color GridColor { get { return gridColor; } set { gridColor = value; } } public int GridSize { get { return gridSize; } set { gridSize = value; } } public float GridScale { get { return gridScale; } set { gridScale = value; } } public Matrix ProjectionMatrix { get { return projection; } set { projection = value; } } public Matrix WorldMatrix { get { return world; } set { world = value; } } public Matrix ViewMatrix { get { return view; } set { view = value; } } #endregion #region Constructors and Loading public SampleGrid() { gridSize = 16; gridScale = 32f; gridColor = new Color(0xFF, 0xFF, 0xFF, 0xFF); world = Matrix.Identity; view = Matrix.Identity; projection = Matrix.Identity; } public void UnloadGraphicsContent() { if (this.vertexBuffer != null) { vertexBuffer.Dispose(); vertexBuffer = null; } if (effect != null) { effect.Dispose(); effect = null; } } public void LoadGraphicsContent(GraphicsDevice graphicsDevice) { device = graphicsDevice; effect = new BasicEffect(device); int gridSize1 = this.gridSize + 1; this.primitiveCount = gridSize1 * 2; this.vertexCount = this.primitiveCount * 2; VertexPositionColor[] vertices = new VertexPositionColor[this.vertexCount]; float length = (float)gridSize * gridScale; float halfLength = length * 0.5f; int index = 0; for (int i = 0; i < gridSize1; ++i) { vertices[index++] = new VertexPositionColor(new Vector3( halfLength, 0.0f, i * this.gridScale  halfLength), this.gridColor); vertices[index++] = new VertexPositionColor(new Vector3( halfLength, 0.0f, i * this.gridScale  halfLength), this.gridColor); vertices[index++] = new VertexPositionColor(new Vector3( i * this.gridScale  halfLength, 0.0f, halfLength), this.gridColor); vertices[index++] = new VertexPositionColor(new Vector3( i * this.gridScale  halfLength, 0.0f, halfLength), this.gridColor); } this.vertexBuffer = new VertexBuffer(device, typeof(VertexPositionColor), this.vertexCount, BufferUsage.WriteOnly); this.vertexBuffer.SetData<VertexPositionColor>(vertices); } ~SampleGrid() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!isDisposed) { if (disposing) { //if we're manually disposing, //then managed content should be unloaded UnloadGraphicsContent(); } isDisposed = true; } } #endregion #region Drawing public void Draw() { effect.World = world; effect.View = view; effect.Projection = projection; effect.VertexColorEnabled = true; effect.LightingEnabled = false; device.SetVertexBuffer(this.vertexBuffer); for (int i = 0; i < this.effect.CurrentTechnique.Passes.Count; ++i) { this.effect.CurrentTechnique.Passes[i].Apply(); device.DrawPrimitives(PrimitiveType.LineList, 0, this.primitiveCount); } } #endregion } public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Heightmap_Generation heightmap; FreeCamera camera; MouseState lastMouseState; RasterizerState state; SampleGrid grid; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } protected override void Initialize() { base.Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); heightmap = new Heightmap_Generation(Content.Load<Texture2D>("TL HM"), Content.Load<Texture2D>("TR HM"), Content.Load<Texture2D>("BR HM"), Content.Load<Texture2D>("BL HM"), 2004, 150000, Content.Load<Texture2D>("grass1"), 100, GraphicsDevice, Content); heightmap.DetailTexture = Content.Load<Texture2D>("Detailmap1"); heightmap.WeightMap1 = Content.Load<Texture2D>("Weightmap1"); heightmap.WeightMap2 = Content.Load<Texture2D>("Weightmap2"); heightmap.WeightMap3 = Content.Load<Texture2D>("Weightmap1"); heightmap.WeightMap4 = Content.Load<Texture2D>("Weightmap2"); heightmap.RTexture = Content.Load<Texture2D>("grass4"); heightmap.GTexture = Content.Load<Texture2D>("grass2"); heightmap.BTexture = Content.Load<Texture2D>("grass3"); camera = new FreeCamera(new Vector3(0, 10000, 0), MathHelper.ToRadians(180), MathHelper.ToRadians(180), GraphicsDevice); grid = new SampleGrid(); grid.GridSize = 2; grid.GridScale = 1000000; grid.LoadGraphicsContent(GraphicsDevice); state = new RasterizerState(); state.FillMode = FillMode.WireFrame; } void updateCamera(GameTime gameTime) { // Get the new keyboard and mouse state MouseState mouseState = Mouse.GetState(); KeyboardState keyState = Keyboard.GetState(); // Determine how much the camera should turn float deltaX = (float)lastMouseState.X  (float)mouseState.X; float deltaY = (float)lastMouseState.Y  (float)mouseState.Y; // Rotate the camera ((FreeCamera)camera).Rotate(deltaX * .01f, deltaY * .01f); Vector3 translation = Vector3.Zero; // Determine in which direction to move the camera if (keyState.IsKeyDown(Keys.W)) translation += Vector3.Forward; if (keyState.IsKeyDown(Keys.S)) translation += Vector3.Backward; if (keyState.IsKeyDown(Keys.A)) translation += Vector3.Left; if (keyState.IsKeyDown(Keys.D)) translation += Vector3.Right; if (keyState.IsKeyDown(Keys.LeftShift)) translation *= 50f; // Move 3 units per millisecond, independent of frame rate //translation *= 300 * (float)gameTime.ElapsedGameTime.TotalMilliseconds; translation *= 3 * (float)gameTime.ElapsedGameTime.TotalMilliseconds; //BB // Move the camera ((FreeCamera)camera).Move(translation); // Update the camera camera.Update(); // Update the mouse state lastMouseState = mouseState; } protected override void Update(GameTime gameTime) { updateCamera(gameTime); grid.ViewMatrix = camera.View; grid.ProjectionMatrix = camera.Projection; grid.WorldMatrix = Matrix.Identity; base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); GraphicsDevice.BlendState = BlendState.Opaque; GraphicsDevice.DepthStencilState = DepthStencilState.Default; //GraphicsDevice.RasterizerState = state; grid.Draw(); heightmap.Draw(camera.View, camera.Projection, ((FreeCamera)camera).Position); base.Draw(gameTime); } } }
This post has been edited by BBeck: 14 August 2013  05:18 AM
#9
Re: Vanishing Vertices And Triangles
Posted 14 August 2013  05:21 AM
I'm glad to read that you're not offended. I'm always concerned, especially online, with how my words may be perceived.
Now, to your code, thank you very much for providing that, I appreciate it.
After removing all the height information from all vertices (so I could easily check all height maps with the grid,) I can confirm that the changes you made do indeed correctly align them, which is wonderful.
Unfortunately, collision is affected by these changes.
I've altered the project files to include the collision I'm using, as well as a model to test it out.
Basically, at the heart of the issue is collision between a model and the height maps. Trying to get the maps at the right coordinates spawned from that.
Also, with the heights in place, you'll notice that there are gaps in between the height maps, don't worry about that as I already have a solution for that, which is just interpolation between the maps to stitch them up. I would include that, but I tried, albeit briefly, and it would need a bit of work to properly work with the adjustments made (I would spend more time fixing that aspect up, but I don't feel it's of value at this point. If you disagree, though, I'll add that and reupload.)
Regardless, you should be able to get the idea. Also, collision seems most heavily affected on the height maps to the left of the world axis, something I noticed when I previously tried to reposition the maps.
Updated Project Code
Again, I'm glad you're not offended whatsoever.
Wishing you well,
H2012
#10
Re: Vanishing Vertices And Triangles
Posted 14 August 2013  12:41 PM
switch (startAtCase) { case 1: //Take care of the first heights array, topleft for (int X = 0; X < 500; X++) { TotalHeights[X+1, Z+1] = height_TL[X, Z]; //BB Changed from TotalHeights[X,Z] //BB if (X == 499) { //We've reached the end of this row, go to the topright array goto case 3; } } break; case 2: //Take care of the fourth heights array, bottomleft for (int X = 0; X < 500; X++) { TotalHeights[X+1, Z] = height_BL[X, Z  500]; //BB Changed from TotalHeights[X,Z] if (X == 499) { //We've reached the end of this row, go to the bottomright array goto case 4; } } break; case 3:
#11
Re: Vanishing Vertices And Triangles
Posted 14 August 2013  07:31 PM
*Breaths a sigh of relief*
It works beautifully!
I can't thank you enough for all your help and all the time you've spent trying to help.
I really appreciate it.
I must address something, though, which is what I originally thought was the problem.
I spent a good deal of time with my friend, the one that's my partner with this project, and we realized something after a few hours: the grid was more than likely off by the exact amount I thought the height maps were off, and that the height maps themselves were drawing all 500 points, whereas I thought they were only drawing 499.
Not only did I position them incorrectly, but I saw a problem that never even existed in the first place.
Today is one of those days where I feel... Less than smart.
Silly assumptions and their consequences.
I've fully integrated the changes you've worked to achieve into the main project, and it all runs beautifully. Again, I can't thank you enough. =)
Wishing you all the best,
H2012
#12
Re: Vanishing Vertices And Triangles
Posted 19 August 2013  10:54 AM
Hopefully this type of post is allowed. As this concerns the original post, I felt the best place to mention this would be here.
I had a major revelation this morning, I've been trying to break the height maps down so I could apply octreestyled culling to them for the past few days.
I've been under the assumption that you need multiple vertex and index buffers to make it work. I finally realized that the indices simply weren't getting access to the vertices they needed to draw correctly. Then it hit me: this makes perfect sense as a vertex buffer is simply allotting a certain amount of vertices, and the indices will only have access to whichever arrays happen to be in that buffer.
I'll add a picture to show this more clearly.
Now, I suppose you could say the major part of my revelation is as follows. As an aside, this is mainly limited to theory and numbers and, most importantly to me, smallscale observation (largely based on tests such as shown in the above picture.)
My original post was concerning alignment, and what appeared to be missing triangles.
Addressing you, BBeck, I cannot thank you enough, again, for helping me out. I really appreciate it greatly.
As I mentioned previously, I'm always concerned with how I appear online, and the last thing I want to give off is an air of ungratefulness.
I had honestly written off and called this issue done until just a few moments ago when I was telling my friend about this, when it hit me what appears to really be going on.
In my original code, I'm using four separate vertex buffers, four separate index buffers: one for each height map.
Realigning the maps, adjusting collision code, and using interpolation to stitch the maps together worked beautifully.
But after this morning, seeing what I've seen, I feel that this is just a patch, and not a fix.
I suppose, getting to the heart of the matter, it appears that there never technically were missing triangles. The indices simply weren't being assigned to the correct array slots. There also seems, based on my observations, like there never was an alignment issue, either.
It all seems to stem back to the amount of vertex buffers I've been using, and not correctly understanding exactly how they work. Coupled with that was a lack of understanding of index buffers and how they operate, as well.
I'm currently working on implementing this with the entire map. As it's on a much larger scale than the small pieces I've been working with (25x25 versus 1000x1000,) I don't know how long the implementation will take (which is partly why I'm posting before having fully implemented it.)
Regardless, once I get it fully implemented, I'll post again on the results, whether for or against this hypothesis.
Wishing you all the best,
H2012
#13
Re: Vanishing Vertices And Triangles
Posted 19 August 2013  12:28 PM
I can't go looking for it right now, but there is a series of videos on Mip Mapped terrain. I think you should check that out and think about it. Honestly, I don't know exactly how they intend for it to work, but it has gotten me thinking. And I hear Mip Mapped terrains are better on modern graphics cards than ROAM terrains.
You may also want to check out ROAM as well.
And maybe give some thought to view frustrum culling.
Although, I myself have just gone with the standard terrain.
I am currently in the process of writing a standard terrain tutorial. I worked on it this past weekend. I started out using Triangle Lists, because it is much easier when you are starting out to understand how that works, because you simply draw two triangles and you have a grid square. Easy enough.
But this weekend, I implemented a terrain using Triangle Strips and it ran noticably faster. I should be posting the code for this on my website in the next week or two.
Now the problem of making large terrains is a challenging one. The maximum number of primitives per Draw call is 1,048,575 using the HiDef profile. And the maximum texture size is 4,096.
Now, I believe "primitives" either means a line or a triangle depending on whether you are drawing lines (like a LineStrip) or triangles (like a TriangleStrip which may be drawn in wireframe mode and LOOK like lines even though its not).
http://msdn.microsof...studio.40).aspx
So, that means you have a maximum limit of 1,048,575 triangles per mesh. And our terrain is usually a single mesh. Then consider that it takes two triangles to form a grid square and then you realize that you have a hard limit of 524,262 grid quads(squares) in your terrain. The square root of 524,262 is 724 rounded down. So, you basically have an absolute hard limit of a 724 by 724 terrain. Try to draw more triangles in a single mesh than that and you will crash the program and it will tell you about the limit. I know because I crashed mine last night.
Now, also consider that your terrain has to match a heigh map, which is a photo. And with resources like textures and height maps you want to always keep your dimensions as a power of 2. The reason for that is because the computer is controlled by bits (not bytes). The bits have only 2 possible values. Arrange 8 of them together and you get a byte. But its still 8 bits. Because it is 8 bits it can hold a maximum of 256 unique values. But it takes 7 bits to store 128 unique values. So, if you need 129 unique values you have to use 8 bits and you are wasting 127 unique possible values if you don't use them.
So, to sum that up: If you are going to use 8 bits, rather than 7, than you might as well use all 256 values because if you don't you're still using 8 bits and you're just wasting those unused values. So, you hit boundaries like that every power of 2.
2,4,8,16,32,64,128,256,512,1024,2048,4096, etc.
For example, 16=2^4=2*2*2*2
So, if you make a texture size, or heightmap that isn't sized to one of those values both horizontally and vertically you wind up wasting computer memory that you have already paid for. (You use the same amount of memory even though you are not putting all of that memory to work.)
So, your texture width and height both need to be a number that is one of those powers of two. And the largest power of 2 that will fit is 512 by 512.
So, 512 by 512 is your maximum grid size in XNA.
Now you can stretch your grid quads (squares) to make them larger while maintaining the same number of them. And I do often. But you get less control over your terrain shape the more you stretch it. So, that's kind of limited on how much of that you can do. Still, if your terrain is 512 by 512 and each quad is 2 meters wide, then that's a kilometer. Stretch it 4 times as wide and that's 2 kilometers or roughly a square mile. That's not too shabby. And 512 by 512 is a mesh with 524,288 triangles in it (half a million polygons). I estimated the vertices and indices to make those triangles might be about 40 bytes per vertex. So, that's 21MB of video memory eaten up just on this one object not including the memory to store the height map or especially the terrain texture (and heaven forbid that the terrain is multitextured).
Still, we all love Skyrim and want the ubber infinate terrain. So we look for other ways.
Take a look at those Mip Mapped terrains and see if that gives you any ideas.
I've kind of been floating around an idea based off of that. The idea was to have the primary grid that might be 10 meters per quad, then 4 sub grids that might be 5 meters per quad, and then each of those would have 4 sub grids that might be 2.5 meters per quad, and then have those sub grids would have 4 sub grids that are 1.25 meters per quad. All of those grids would overlay one another similar to what you see in those videos.
Anyway, you could likewise have a heightmap for each. So, as the terrain becomes more detailed in the sub grids you have a more detailed height map. These heightmaps would all overlay one another proving more and more detail.
So, in the closest 4 sub grids at 1.5 meters, you could use those with their detailed height maps, and then use the next level up to define the grids next to them, and then the next level up to display the grids next to those.
Basically, use the detailed information for grid quads that are close and use the lower resolution information for those that are far. That way you could use substantially fewer quads over all.
Also, on a massive terrain, I've thought about spawning the next terrain as you approach the edge of the current terrain from about the middle. And than you despawn it and spawn the terrain on the other side when headed in the opposite direction. So, you never have more than 3 terrains spawned out of the 8 terrains surrounding the terrain you are on. Using this approach you could go for an almost infinate terrain in overall size even though at any moment there are only 4 512x512 terrains spawned at any time. Heck, you might be even able to make it 4 256 by 256 terrains since the overall size is near infinate at that point. The hard part is generating a truely massive (infinate) height map and then breaking it up into all those sub terrains.
#14
Re: Vanishing Vertices And Triangles
Posted 21 August 2013  09:54 AM
http://www.terrain.dk/
#15
Re: Vanishing Vertices And Triangles
Posted 23 August 2013  07:27 AM
I was not aware of the "power of two" when it came to texture size, that's certainly something I'll look into.
I've been looking into mipmapping a bit, and I have to admit, I've been so busy exploring vertex and index buffers that I haven't looked into it beyond a cursory glance. Regardless, from what I gather, I think it's certainly the next step once I have things broken down into manageable chunks.
Concerning that, and what you've mentioned as far as a video series, do you remember whereabouts those videos were stored? Were they on Youtube, or some other site? I can't seem to locate much of anything beyond demonstration videos.
Also, I came across this, which may be of some use to you, I'm not sure.
Geomipmapped Terrain
As far as frustum culling is concerned, I had something like that in mind (if I understand the concept correctly) with breaking up the height maps. Basically, my idea is to break the maps down into more sizable chunks, apply bounding boxes to them, and basically have them draw if they're in view.
My end goal is to apply this not only to the height maps, but billboards, as well, on each chunk, so I can have thick forests and such without a huge amount of lag.
Combined with that, if I can manage all this, I want to give back a bit to this community and make a tutorial on it all.
All in due time, I suppose.
You've certainly given me quite a lot to think about, and I appreciate it. It has expanded my mental horizons a bit, which is, in my opinion, a very good thing.
Now, I feel an update is in order on what I posted about earlier in the week.
I successfully created a full vertex buffer for all four height maps, and even got one of them to all draw correctly to the bounds of the grid (the other three I never worked with at this stage. I took your advice earlier on getting at least one to work, and so felt working with the one would allow me to hammer out the details.)
However, I soon realized that this success was not because of the single vertex buffer.
In order to get one height map to fully draw to the borders of the next map, I had to create more vertices with proper texture coordinates. Otherwise, it would draw, but the textures would be skewed horribly.
Essentially, I created overlapping vertices with different texture coordinates for proper texture mapping, which probably is not the most optimized way of doing things, but it works, and for now, that will have to do.
It seems that one vertex buffer is not really needed for the four height map sections, however it seems like it will certainly be needed once I get back into dividing them up into smaller chunks.
It still would appear I need to define multiple index buffers, though. From my understanding, they do the heavy lifting when it comes to actually drawing things, or at least telling the computer how to draw things.
From what I gather, the important thing, at least when it comes to what I've been doing, is how the texture coordinates are defined. If you're within one map or one section that has its own texture map, then you need just the one vertex buffer to allow the indices to "talk" to all the needed vertices that have the appropriate texture data.
Here's the results of the overlapping vertices, albeit without any height data so you can see the result with a grid.
To reinstate height data, I had to increase the array I was using for heights, raise the Y value of the position of the overlapping vertices to match what was actually there, and then, for collision purposes, recalculate the height data of those points.
The result is... Well, it's beyond what I expected, to be honest. It's seamless, matches the grid, and collision works perfectly.
I would say I'm 95% satisfied with this result, lacking 5% because of the extra vertices I had to do and being uncertain if that is the way to go about it.
As always, I'm open to new and better ways to accomplish this end result, which is something I'll look into after I've worked on making things into the aforementioned chunks.
For now, it will suffice, though, and gives me a good base of working knowledge to operate on.
Wishing you all the best,
H2012
