#region Copyright
/*
--------------------------------------------------------------------------------
This source file is part of Xenocide
by Project Xenocide Team
For the latest info on Xenocide, see http://www.projectxenocide.com/
This work is licensed under the Creative Commons
Attribution-NonCommercial-ShareAlike 2.5 License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-nc-sa/2.5/
or send a letter to Creative Commons, 543 Howard Street, 5th Floor,
San Francisco, California, 94105, USA.
--------------------------------------------------------------------------------
*/
/*
* @file SphereMesh.cs
* @date Created: 2007/01/25
* @author File creator: dteviot
* @author Credits: none
*/
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using System.Collections;
using System.Diagnostics;
namespace ProjectXenocide.UI.Scenes.Geoscape
{
///
/// Generate a spherical mesh of VertexPositionNormalTexture vertexes
///
class SphereMesh
{
private int numStrips;
private int nextIndex;
private int nextVertex;
private short[] triangleListIndices;
private GlobeVertex[] vertexes;
///
/// construtor
/// Number of strips we sphere has between pole and equator
///
public SphereMesh(int numStrips)
{
this.numStrips = numStrips;
Debug.Assert(1 <= numStrips && numStrips < 20);
AllocateStorage();
ConstructMeshStructure();
// check calculations
Debug.Assert(TotalVertexes == nextVertex);
Debug.Assert(TotalIndexes == nextIndex * 2);
}
///
/// return a vertex buffer that can be used to draw the sphere
///
public VertexBuffer CreateVertexBuffer(GraphicsDevice device)
{
VertexBuffer vertexBuffer = new VertexBuffer(
device,
GlobeVertex.SizeInBytes * vertexes.Length,
BufferUsage.None
);
vertexBuffer.SetData(vertexes);
return vertexBuffer;
}
///
/// return indexed triangle list that can be used to draw the sphere.
///
public IndexBuffer CreateIndexBuffer(GraphicsDevice device)
{
IndexBuffer indexBuffer = new IndexBuffer(
device,
sizeof(short) * triangleListIndices.Length,
BufferUsage.None,
IndexElementSize.SixteenBits
);
indexBuffer.SetData(triangleListIndices);
return indexBuffer;
}
///
/// Based on number of strips, calculate number space
/// needed to store Vertexes and allocate it.
///
private void AllocateStorage()
{
nextIndex = 0;
nextVertex = 0;
int numFaces = 8 * numStrips * numStrips;
triangleListIndices = new short[numFaces * 3];
// nodes in a given strip = (4 x (n-1)) + 1
// and there are 2n - 1 strips. So:
int numVertexes = ((4 * (numStrips - 1)) + 10) * (numStrips - 1) + 7;
vertexes = new GlobeVertex[numVertexes];
}
private void ConstructMeshStructure()
{
AddFirstMeshStrip();
// first two vertexes in array are the poles, so first vertex of strip will be at [2]
int previousStripStart = 2;
for (int strip = 2; strip <= numStrips; ++strip)
{
previousStripStart = AddTriangleStrip(strip, previousStripStart);
}
}
///
/// first strip requires special handling, as all vertexes attach to the pole
///
private void AddFirstMeshStrip()
{
Vector3 position = new Vector3(0.0f, 1.0f, 0.0f);
Vector3 tangent = Vector3.Cross(position, Vector3.UnitX);
Vector3 binormal = Vector3.Cross(position, tangent);
// add north (and south) pole vertex
AddVertex(position, tangent, binormal, new Vector2(0.5f, 0.0f), false);
// first two vertexes in array are the poles, so first vertex of strip will be at [2]
int firstIndex = 2;
// if we're on the equator, then vertexes are adjacent, otherwise they're alternating
int stepSize = isEquator(1) ? 1 : 2;
// now compute the Vertices along this strip
int numVertex = CreateStripVertexes(1, isEquator(1));
// construct the faces
for (int i = 0; i < numVertex; ++i)
{
AddFace(0, firstIndex, firstIndex + stepSize);
firstIndex += stepSize;
}
}
///
/// create set of faces, between previous latitude and this one
/// Band on sphere we're rendering
/// Index into vertices to first vertex of pervious "strip"
///
private int AddTriangleStrip(int strip, int previousStripVertex)
{
// the vertices for this strip will start here
int thisStripVertex = nextVertex;
// create the strip
CreateStripVertexes(strip, isEquator(strip));
// if we're on the equator, then vertexes are adjacent, otherwise they're alternating
// with their southern hemisphere counterpart
int stepSize = isEquator(strip) ? 1 : 2;
// now do the faces between the strips
// construct the faces
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < strip - 1; ++j)
{
AddFace(previousStripVertex, thisStripVertex, thisStripVertex + stepSize);
thisStripVertex += stepSize;
AddFace(thisStripVertex, previousStripVertex + 2, previousStripVertex);
previousStripVertex += 2;
}
AddFace(previousStripVertex, thisStripVertex, thisStripVertex + stepSize);
thisStripVertex += stepSize;
}
// return pointer to first vectex in this strip
return previousStripVertex + 2;
}
///
/// compute the vertexes in this strip, and load into the array
/// Band on sphere we're rendering
/// Does this band touch the equator?
/// number of vertexes added
///
private int CreateStripVertexes(int strip, bool isOnEquator)
{
// we're going to cheat a bit and go from 0 to 90 degrees
double longitude = (Math.PI * 0.5 * strip) / numStrips;
float y = (float)Math.Cos(longitude);
double s = Math.Sin(longitude);
Vector3 position;
Vector3 tangent;
Vector3 binormal;
// number of vertexes in this strip
int numVertexes = (strip * 4);
for (int v = 0; v < numVertexes; ++v)
{
double latitudue = (Math.PI * 2.0 * v) / numVertexes;
float x = (float)(- s * Math.Cos(latitudue));
float z = (float)(s * Math.Sin(latitudue));
position = new Vector3(x, y, z);
tangent = Vector3.Cross(position, Vector3.UnitX);
binormal = Vector3.Cross(position, tangent);
AddVertex ( position,
tangent,
binormal,
new Vector2((float)(v) / numVertexes, (float)(strip) * 0.5f / numStrips),
isOnEquator);
}
position = new Vector3((float)-s, y, 0.0f);
tangent = Vector3.Cross(position, Vector3.UnitX);
binormal = Vector3.Cross(position, tangent);
// and we need to add one extra one, to let texture wrap around
AddVertex(position,
tangent,
binormal,
new Vector2(1.0f, (float)(strip) * 0.5f / numStrips),
isOnEquator);
return numVertexes;
}
///
/// add the vectorIndexes making up a northern face (and it's southern complement) to the index
///
private void AddFace(int vectorIndex1, int vectorIndex2, int vectorIndex3)
{
// northern face
AddIndexIntoBuffer(vectorIndex1);
AddIndexIntoBuffer(vectorIndex3);
AddIndexIntoBuffer(vectorIndex2);
}
///
/// add vectorIndex to end of northern indexes we've added so far
/// and add the matching southern hemisphere index as well
/// and update count of number
///
private void AddIndexIntoBuffer(int vectorIndex)
{
// as we need to add a southern face for every northen one
// we must not fill more than half the index array with northern vertex indices.
Debug.Assert(nextIndex < (triangleListIndices.Length / 2));
// yes, I know that the vectorIndex should be a short, but due to the nature of
// how it's called, its cleaner to put one cast in here than multiple elsewhere
triangleListIndices[nextIndex] = (short)vectorIndex;
// we put southern faces at end of list, in reverse order, because
// vertex order needs to change (because faces are inverted)
triangleListIndices[TotalIndexes - nextIndex - 1] = GetSouthernVertexIndex((short)vectorIndex);
++nextIndex;
}
///
/// return the index to where the matching vertex in southern hemisphere is
///
private short GetSouthernVertexIndex(short index)
{
// if we're on the equator then vertex is it's own complement
// otherwise, it's the next vertex in the array
if (index < TotalVertexes - ((4 * numStrips) + 1))
{
++index;
}
return index;
}
///
/// add VertexPositionNormalTexture to end of vertexes we've calculated so far
/// note that vertexes will be loaded into array as pairs, with the matching
/// southern hemisphere immediately after the northern one.
///
private void AddVertex(Vector3 position, Vector3 tangent, Vector3 binormal, Vector2 texture, bool isOnEquator)
{
vertexes[nextVertex] = new GlobeVertex(position, position, tangent, binormal, texture);
nextVertex++;
if (!isOnEquator)
{
vertexes[nextVertex] = CreateSouthernVertex(position, tangent, texture);
nextVertex++;
}
}
///
/// returns a VertexPositionNormalTexture that is the southern hemisphere's complement
/// to the supplied "Vertex"
///
private static GlobeVertex CreateSouthernVertex(Vector3 position, Vector3 tangent, Vector2 texture)
{
Vector3 southPosition = new Vector3(position.X, -position.Y, position.Z);
Vector3 southBinormal = Vector3.Cross(southPosition, tangent);
Vector2 southTexture = new Vector2(texture.X, 1.0f - texture.Y);
return new GlobeVertex(southPosition, southPosition, tangent, southBinormal, southTexture);
}
///
/// true if this strip is the "equator"
///
private bool isEquator(int strip)
{
return (strip == numStrips);
}
/*
///
/// Diagnostic output, dump the list of vertices
///
private void dumpVertexes()
{
foreach (VertexPositionNormalTexture v in vertexes)
{
Debug.WriteLine(v.ToString());
}
}
///
/// Diagnostic output, dump the vertex indexes making up the faces
///
private void dumpFaces()
{
for (int i = 0; i < TotalIndexes; i += 3)
{
Debug.WriteLine(
String.Format("( {0}, {1}, {2} )",
triangleListIndices[i],
triangleListIndices[i+1],
triangleListIndices[i+2]
) );
}
}
*/
public int TotalVertexes { get { return vertexes.Length; } }
public int TotalIndexes { get { return triangleListIndices.Length; } }
public int TotalFaces { get { return TotalIndexes / 3; } }
}
}