Ландшафт с Геометрией Clipmaps в XNA (Перевод) Часть 2

Оригинал: http://www.ziggyware.com/readarticle.php?article_id=220

Теперь время для конструктора. Удостоверьтесь, что конструктор принимает только те значения, которые допустимы для инициализации уровня. Мы выбросили исключения для плохих случаев.

#region Constructor / Initialization 
/// <summary> 
/// Creates a new clipmaplevel. 
/// </summary> 
/// <param name="L">Levelindex of the clipmap. If is 0 this will be the finest level</param> 
/// <param name="N">Number of vertices per clipside. Must be one less than power of two.</param> 
/// <param name="S">Maximum terrainheight and heightscale</param> 
/// <param name="fieldsize">Width and heightvalue of the heightfield</param> 
/// <param name="heightfield">Heightvalues with a range of 0.0f - 1.0f</param> 
/// <param name="device">The used Graphicsdevice</param> 
/// <exception cref="ArgumentException"></exception> 
/// <exception cref="ArgumentNullException"></exception> 
public Clipmap( 
 int L, int N, float S, int fieldsize, ref float[] heightfield, GraphicsDevice device) 
{ 
 // Check some exception cases first 
 if (L < 0) 
 { 
 throw new ArgumentException("L must be positive"); 
 } 
 if (N != 15 && 
 N != 31 && 
 N != 63 && 
 N != 127 && 
 N != 255) 
 { 
 // The check for "one less of power of two" would be a bit 
 // longer. We check only for some handy legal values. 
 throw new ArgumentException("N must be 15, 31, 63, 127 or 255"); 
 } 
 if (heightfield.Length != (fieldsize * fieldsize)) 
 { 
 throw new ArgumentException( 
 "Fieldsize must have same size like" + 
 " width and height of the heightfield." + 
 " Width and height of the heightfield must be equal."); 
 } 
 if (device == null) 
 { 
 throw new ArgumentNullException( 
 "GraphicsDevice is not allowed to be null."); 
 } 
 // Apply the values 
 this.device = device; 
 this.fieldsize = fieldsize; 
 this.heightfield = heightfield; 
 this.L = L; 
 this.S = S; 
 this.N = N; 
 this.G = (int)Math.Pow(2, L); 
 this.M = (N + 1) / 4; 
 this.G2 = G * 2; 
 this.Mm1G = (M - 1) * G; 
 this.clipRegion = new Rectangle(0, 0, (N - 1) * G, (N - 1) * G); 
 // Initialize the vertices 
 Initialize(); 
} 
/// <summary> 
/// Initializes the vertices and indices. 
/// </summary> 
private void Initialize() 
{ 
 // create new vertexdeclaration of our custom VertexPosition4 struct 
 vertexDeclaration = new VertexDeclaration(device, 
 VertexPosition4.VertexElements); 
 // 
 // Vertices 
 // 
 // N is the number of vertices per clipmapside, so number of all vertices is N * N 
 vertices = new VertexPosition4[N * N]; 
 // Gp through all vertices of the current clip and update their height 
 for (int z = 0; z < N; z++) 
 { 
 for (int x = 0; x < N; x++) 
 { 
 // x,z is the position of a vertex in the vertexarray 
 // of the current clipmap. We must scale that up to another 
 // position that this vertex would have if we had a big vertexarray 
 // for the whole terrain. Here we use the G value. 
 // It says that the current level only shows every G'th vertex 
 // of the terrainmesh. 
 // This results in a simple LOD system where every clips 
 // LOD differs by G from the finest cliplevel where G is 1 
 UpdateVertex(x * G, z * G); 
 } 
 } 
 // 
 // Indices 
 // 
 // Looks weird, but this results in the smallest indicesarray we can get. 
 // This comes from cutting a level into pieces and using dummyvertices 
 // in the trianglestrip, like it is described later in the code. 
 indices = new short[4 * (3 * M * M + (N * N) / 2 + 4 * M - 10)]; 
 // Go through all rows and fill them with vertexindices. 
 for (int z = 0; z < N - 1; z++) 
 { 
 FillRow(0, N - 1, z, z + 1); 
 } 
} 
#endregion 

Теперь начинается захватывающая часть. Логика обновления. Сначала мы покажем, как вершины обновляются. Помните? мы хотим держать в массиве некоторые данные, которые не изменились, отбросить часть данных, которые были изменены, и заменить их новыми. Но прежде, чем мы сделаем это, давайте посмотрим, как слои будут вести себя, перемещаясь, т.е., когда они «действительно» двигаются. Это продемонстрировано на следующей картине.

Уровни никогда не двигаются одновременно. Когда центральная точка сдвинулась, мелкий уровень движется в следующее положение, где он сможет вписаться во внешний крупный слой. На следующем шаге крупный слой движется, а более мелкий уровень ждет его. Таким образом, мы должны соблюдать шаг сетки следующего более крупного уровня. Прочитайте комментарии к коду и увидите, как это сделано.

#region Update Vertices 
/// <summary> 
/// Updates the vertices and indices of the clipmap. 
/// </summary> 
/// <param name="center">The center of the clipmap</param> 
public void UpdateVertices(Vector3 center) 
{ 
 // this method is visible to public. Just call a private updatemethod 
 // with prefered parameters. 
 UpdateVertices((int)center.X, (int)center.Z); 
} 
/// <summary> 
/// Updates all vertices depend on the given clipmap centerposition. 
/// </summary> 
/// <param name="cx">clipmapcenter x-coordinate</param> 
/// <param name="cz">clipmapcenter y-coordinate</param> 
private void UpdateVertices(int cx, int cz) 
{ 
 // Store the old position to be able to recover it if needed 
 int oldX = clipRegion.X; 
 int oldZ = clipRegion.Y; 
 // Calculate the new position 
 clipRegion.X = cx - ((N + 1) * G / 2); 
 clipRegion.Y = cz - ((N + 1) * G / 2); 
 // Calculate the modulo to G2 of the new position. 
 // This makes sure that the current level always fits in the hole of the 
 // coarser level. The gridspacing of the coarser level is G * 2, so here G2. 
 int modX = clipRegion.X % G2; 
 int modY = clipRegion.Y % G2; 
 modX += modX < 0 ? G2 : 0; 
 modY += modY < 0 ? G2 : 0; 
 clipRegion.X += G2 - modX; 
 clipRegion.Y += G2 - modY; 
 // Calculate the moving distance 
 int dx = (clipRegion.X - oldX); 
 int dz = (clipRegion.Y - oldZ); 
 // Create some better readable variables. 
 // This are just the bounds of the current level (the new region). 
 int xmin = clipRegion.Left; 
 int xmax = clipRegion.Right; 
 int zmin = clipRegion.Top; 
 int zmax = clipRegion.Bottom; 
 // Update now the L shaped region. 
 // This replaces the old data with the new one. 
 if (dz > 0) 
 { 
 // Center moved in positive z direction. 
 for (int z = zmin; z <= zmax - dz; z += G) 
 { 
 if (dx > 0) 
 { 
 // Center moved in positive x direction. 
 // Update the right part of the L shaped region. 
 for (int x = xmax - dx + G; x <= xmax; x += G) 
 { 
 UpdateVertex(x, z); 
 } 
 } 
 else if (dx < 0) 
 { 
 // Center moved in negative x direction. 
 // Update the left part of the L shaped region. 
 for (int x = xmin; x <= xmin - dx - G; x += G) 
 { 
 UpdateVertex(x, z); 
 } 
 } 
 } 
 for (int z = zmax - dz + G; z <= zmax; z += G) 
 { 
 // Update the bottom part of the L shaped region. 
 for (int x = xmin; x <= xmax; x += G) 
 { 
 UpdateVertex(x, z); 
 } 
 } 
 } 
 else 
 { 
 // Center moved in negative z direction. 
 for (int z = zmin; z <= zmin - dz - G; z += G) 
 { 
 // Update the top part of the L shaped region. 
 for (int x = xmin; x <= xmax; x += G) 
 { 
 UpdateVertex(x, z); 
 } 
 } 
 for (int z = zmin - dz; z <= zmax; z += G) 
 { 
 if (dx > 0) 
 { 
 // Center moved in poistive x direction. 
 // Update the right part of the L shaped region. 
 for (int x = xmax - dx + G; x <= xmax; x += G) 
 { 
 UpdateVertex(x, z); 
 } 
 } 
 else if (dx < 0) 
 { 
 // Center moved in negative x direction. 
 // Update the left part of the L shaped region. 
 for (int x = xmin; x <= xmin - dx - G; x += G) 
 { 
 UpdateVertex(x, z); 
 } 
 } 
 } 
 } 
} 

Затем мы выполняем метод UpdateVertex, который мы использовали в предыдущем алгоритме. Он только обновляет вершину в заданном положении, которое указывает на место ландшафта. Мы должны отобразить его на наш массив положений прежде, чем мы сможем обновить высоту в нем. Имейте в виду, что каждое положение ландшафта будет всегда соответствовать тому же положению в массиве. Даже если мы прокрутим целую карту до бесконечности и вернемся. Для этого мы снова используем некоторые операции модуля. Но теперь мы обращаемся к значению N, размеру уровня. Так каждое N-е положение на карте соответствует такому же положению в массиве. Если положение на карте указывает на какую-нибудь точку за пределами heightmap, мы должны выбрать операцию, как мы будем обновлять эту высоту. Я предпочитаю отсекать все то, что снаружи и соотносить это высоте = 0. Вы можете придумать что-то другое в соответствие с вашей картой. Это дало бы безграничный ландшафт.

Здесь мы также получаем второе значение высоты, которое позволит нам избежать трещин в ландшафте. Вершина не нуждается в таком значении, если она подходит к сетке следующего более крупного уровня. Если она не подходит, то мы получаем высоты соседних вершин, которые действительно вписываются в более крупную сетку, и вычисляем их медиану. Это скроет трещину. Вершины, которые не нуждаются во втором значении высоты, просто получают правильную высоту два раза. Их медиана сохраняет высоту.

Вершины, вызывающие трещины(красные), должны быть подняты на величину медианы соседних вершин(синие)

/// <summary> 
/// Updates the height of a vertex at the specified position. 
/// The coordinates may be some values, even outside the map. 
/// </summary> 
/// <param name="x">x-coordinate</param> 
/// <param name="z">z-coordinate</param> 
private void UpdateVertex(int x, int z) 
{ 
 // Map the terraincoordinates to arraycoordinates. 
 // Use modulo to N 
 int posx = (x / G) % N; 
 int posy = (z / G) % N; 
 posx += posx < 0 ? N : 0; 
 posy += posy < 0 ? N : 0; 
 // Set both heightvalues to zero first. 
 int index = posx + posy * N; 
 vertices[index].Position = new Vector4(x, 0, z, 0); 
 if (x > 0 && x < fieldsize - 1 && 
 z > 0 && z < fieldsize - 1) 
 { 
 // If the position is inside the map, calculate the indices 
 // where we can get our heightvalues from. 
 // Index to the heightvalue of the x z coordinate. 
 int k = x + z * fieldsize; 
 // indices of heightvalues we can use 
 // for the second height to avoid cracks 
 int j; 
 int l; 
 if ((x % G2) == 0) 
 { 
 if ((z % G2) == 0) 
 { 
 // Coordinates fit in coarser grid. 
 // Dont need additional heightvalue. 
 j = k; 
 l = k; 
 } 
 else 
 { 
 // Z value is not regular, so dont fit in coarser grid. 
 // Get indices from higher and lower vertex. 
 j = x + (z - G) * fieldsize; 
 l = x + (z + G) * fieldsize; 
 } 
 } 
 else 
 { 
 if ((z % G2) == 0) 
 { 
 // Z value is not regular, so dont fit in coarser grid. 
 // Get indices from higher and lower vertex. 
 j = (x - G) + z * fieldsize; 
 l = (x + G) + z * fieldsize; 
 } 
 else 
 { 
 // X value is not regular, so dont fit in coarser grid. 
 // Get indices from left and right vertex. 
 j = (x - G) + (z + G) * fieldsize; 
 l = (x + G) + (z - G) * fieldsize; 
 } 
 } 
 // Get the height of current coordinates 
 // and set both heightvalues to that height. 
 float height = heightfield[k] * S; 
 vertices[index].Position.Y = height; 
 vertices[index].Position.W = height; 
 if (l >= 0 && 
 l < heightfield.Length && 
 j >= 0 && 
 j < heightfield.Length) 
 { 
 // If we can get the additional height, get the two values, 
 // and apply the median of it to the W value 
 float coarser1 = heightfield[j] * S; 
 float coarser2 = heightfield[l] * S; 
 vertices[index].Position.W = (coarser2 + coarser1) * 0.5f; 
 } 
 } 
} 
#endregion 
Реклама
Запись опубликована в рубрике Uncategorized. Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s