PageRenderTime 49ms CodeModel.GetById 21ms app.highlight 21ms RepoModel.GetById 2ms app.codeStats 0ms

/Rendering/Models/GeometryHelper.cs

#
C# | 382 lines | 219 code | 28 blank | 135 comment | 9 complexity | 84ce793b3fde845f32245c0f7e070ddf MD5 | raw file
  1using System;
  2using Delta.ContentSystem.Rendering;
  3using Delta.Utilities.Graphics;
  4using Delta.Utilities.Datatypes;
  5using Delta.Utilities.Helpers;
  6
  7namespace Delta.Rendering.Models
  8{
  9	/// <summary>
 10	/// Helper class to generate geometry based on the GeometryData class.
 11	/// This class is derived from GeometryData and can be used as well, it just
 12	/// adds a few more static Create methods.
 13	/// <para />
 14	/// Note: That class has only static method but can't be a static class
 15	/// because it's derived from the GeometryData to extend its create
 16	/// functionality.
 17	/// </summary>
 18	public class GeometryHelper : GeometryData
 19	{
 20		#region CreatePlane
 21		/// <summary>
 22		/// Create plane
 23		/// </summary>
 24		/// <param name="vertexFormat">The vertex format.</param>
 25		/// <param name="setWidth">Width of the set.</param>
 26		/// <param name="setHeight">Height of the set.</param>
 27		/// <returns></returns>
 28		public static GeometryData CreatePlane(VertexFormat vertexFormat,
 29			float setWidth, float setHeight)
 30		{
 31			return CreatePlane(vertexFormat, setWidth, setHeight, Color.White);
 32		} // CreatePlane(vertexFormat, setWidth, setHeight)
 33
 34		/// <summary>
 35		/// Creates an XY plane in the specified vertex format (you can use
 36		/// one of the predefined vertex formats from VertexData).
 37		/// </summary>
 38		/// <param name="vertexFormat">The vertex format.</param>
 39		/// <param name="setWidth">Set width.</param>
 40		/// <param name="setHeight">Set Height.</param>
 41		/// <param name="setColor">Set Color.</param>
 42		/// <returns></returns>
 43		public static GeometryData CreatePlane(VertexFormat vertexFormat,
 44			float setWidth, float setHeight, Color setColor)
 45		{
 46			float halfWidth = setWidth * 0.5f;
 47			float halfDepth = setHeight * 0.5f;
 48			float height = 0;
 49
 50			// We use 4 vertices with 6 indices (0, 1, 2, 2, 1, 3):
 51			// 0 - 1
 52			// | / |
 53			// 2 - 3
 54			GeometryData geometry = new GeometryData(
 55				"<GeneratedPlane(" + setWidth + "x" + setHeight + ")>",
 56				4, vertexFormat, 6, true, false);
 57			Vector planeNormal = new Vector(0, 0, 1);
 58			Vector planeTangent = new Vector(1, 0, 0);
 59			// Front left
 60			geometry.SetVertexData(0,
 61				new Vector(-halfWidth, halfDepth, height),
 62				new Point(0, 0), setColor, planeNormal, planeTangent);
 63			// Front right
 64			geometry.SetVertexData(1,
 65				new Vector(halfWidth, halfDepth, height),
 66				new Point(1, 0), setColor, planeNormal, planeTangent);
 67			// Rear left
 68			geometry.SetVertexData(2,
 69				new Vector(-halfWidth, -halfDepth, height),
 70				new Point(0, 1), setColor, planeNormal, planeTangent);
 71			// Rear right
 72			geometry.SetVertexData(3,
 73				new Vector(halfWidth, -halfDepth, height),
 74				new Point(1, 1), setColor, planeNormal, planeTangent);
 75
 76			// Top left polygon (Note: Counter-Clockwise is front)
 77			geometry.Indices[0] = 0;
 78			geometry.Indices[1] = 2;
 79			geometry.Indices[2] = 1;
 80			// Bottom right polygon
 81			geometry.Indices[3] = 2;
 82			geometry.Indices[4] = 3;
 83			geometry.Indices[5] = 1;
 84
 85			return geometry;
 86		} // CreatePlane(vertexFormat, setWidth, setHeight)
 87		#endregion
 88
 89		#region CreateSegmentedPlane
 90		/// <summary>
 91		/// Create plane
 92		/// </summary>
 93		/// <param name="vertexFormat">The vertex format.</param>
 94		/// <param name="setWidth">Width of the set.</param>
 95		/// <param name="setHeight">Height of the set.</param>
 96		/// <param name="segments">The segments.</param>
 97		/// <returns></returns>
 98		public static GeometryData CreateSegmentedPlane(VertexFormat vertexFormat,
 99			float setWidth, float setHeight, int segments)
100		{
101			return CreateSegmentedPlane(vertexFormat, setWidth, setHeight, segments,
102				Color.White);
103		} // CreatePlane(vertexFormat, setWidth, setHeight)
104
105		/// <summary>
106		/// Creates an XY plane in the specified vertex format (you can use
107		/// one of the predefined vertex formats from VertexData).
108		/// </summary>
109		/// <param name="vertexFormat">The vertex format.</param>
110		/// <param name="setWidth">Sets Width.</param>
111		/// <param name="setHeight">Sets Height.</param>
112		/// <param name="segments">The segments.</param>
113		/// <param name="setColor">Sets Color.</param>
114		/// <returns>
115		/// Geometry Data
116		/// </returns>
117		public static GeometryData CreateSegmentedPlane(VertexFormat vertexFormat,
118			float setWidth, float setHeight, int segments, Color setColor)
119		{
120			if (segments < 1)
121			{
122				throw new InvalidOperationException("You need at least one segment " +
123					"for CreateSegmentedPlane!");
124			}
125
126			float halfWidth = setWidth * 0.5f;
127			float halfDepth = setHeight * 0.5f;
128			float height = 0;
129
130			// We use 4 vertices with 6 indices (0, 1, 2, 2, 1, 3):
131			// 0 - 1
132			// | / |
133			// 2 - 3
134			// But duplicate this many times for as many segments in each direction.
135			int totalSegments = segments * segments;
136			int segmentStride = segments + 1;
137			int totalPoints = segmentStride * segmentStride;
138			GeometryData geometry = new GeometryData(
139				"<GeneratedSegmentedPlane(" + setWidth + "x" + setHeight + ")>",
140				totalPoints, vertexFormat, 6 * totalSegments, true, false);
141			Vector planeNormal = new Vector(0, 0, 1);
142			Vector planeTangent = new Vector(1, 0, 0);
143			// For each segment + 1, build the points
144			int pointIndex = 0;
145			for (int ySegment = 0; ySegment < segments + 1; ySegment++)
146			{
147				for (int xSegment = 0; xSegment < segments + 1; xSegment++)
148				{
149					float xFactor = (float)xSegment / (float)segments;
150					float yFactor = (float)ySegment / (float)segments;
151					geometry.SetVertexData(pointIndex++,
152						new Vector(-halfWidth + xFactor * setWidth,
153						halfDepth - yFactor * setHeight, height),
154						new Point(xFactor, yFactor), setColor, planeNormal, planeTangent);
155				} // for
156			} // for
157
158			// For each segment, build up the 2 triangles
159			int index = 0;
160			for (int ySegment = 0; ySegment < segments; ySegment++)
161			{
162				for (int xSegment = 0; xSegment < segments; xSegment++)
163				{
164					// Top left polygon (Note: Counter-Clockwise is front)
165					geometry.Indices[index++] = //0,0
166						(ushort)(xSegment + 0 + (ySegment + 0) * segmentStride);
167					geometry.Indices[index++] = //0,1
168						(ushort)(xSegment + 0 + (ySegment + 1) * segmentStride);
169					geometry.Indices[index++] = //1,0
170						(ushort)(xSegment + 1 + (ySegment + 0) * segmentStride);
171					// Bottom right polygon
172					geometry.Indices[index++] = //0,1
173						(ushort)(xSegment + 0 + (ySegment + 1) * segmentStride);
174					geometry.Indices[index++] = //1,1
175						(ushort)(xSegment + 1 + (ySegment + 1) * segmentStride);
176					geometry.Indices[index++] = //1,0
177						(ushort)(xSegment + 1 + (ySegment + 0) * segmentStride);
178				} // for
179			} // for
180
181			return geometry;
182		} // CreateSegmentedPlane(vertexFormat, setWidth, setHeight)
183		#endregion
184
185		#region CreateCube
186		/// <summary>
187		/// Generates a cube mesh with the given sizes and a material. Cube meshes
188		/// have their pivot point at Vector.Zero (to make them easier to place).
189		/// An inverted box with fewer vertices than the normal box.
190		/// </summary>
191		/// <param name="vertexFormat">The vertex format.</param>
192		/// <param name="width">The width in the X.</param>
193		/// <param name="depth">The depth in the Z.</param>
194		/// <param name="height">The height in the Y.</param>
195		/// <returns>
196		/// Geometry Data
197		/// </returns>
198		public static GeometryData CreateCube(VertexFormat vertexFormat,
199			float width, float depth, float height)
200		{
201			return CreateCube(vertexFormat, width, depth, height,
202				"<GeneratedBox(" + width + ", " + depth + ", " + height + ")>",
203				height / 2, Color.White);
204		} // CreateBox(vertexFormat, width, depth)
205
206		/// <summary>
207		/// Generates a Cube mesh with a given pivot point height (usually used
208		/// to generate the box at Vector.Zero, see other overloads).
209		/// An inverted box with fewer vertices than the normal box.
210		/// </summary>
211		/// <param name="vertexFormat">The vertex format.</param>
212		/// <param name="width">The width.</param>
213		/// <param name="depth">The depth.</param>
214		/// <param name="height">The height.</param>
215		/// <param name="meshName">Name of the mesh.</param>
216		/// <param name="heightOffset">The height offset.</param>
217		/// <param name="setColor">Color of the set.</param>
218		/// <returns>
219		/// Geometry Data
220		/// </returns>
221		public static GeometryData CreateCube(VertexFormat vertexFormat,
222			float width, float depth, float height, string meshName,
223			float heightOffset, Color setColor)
224		{
225			float right = width / 2.0f;
226			float back = depth / 2.0f;
227			float top = height / 2.0f;
228			float bottom = -height / 2.0f;
229
230			GeometryData geometry = new GeometryData(meshName, 8, vertexFormat,
231				6 * 6, true, false);
232
233			// First go to the start position for this vertex
234			geometry.Format.Elements[0].SaveData(geometry.writer,
235				new Vector(-right, -back, top));
236			geometry.Format.Elements[0].SaveData(geometry.writer,
237				new Vector(right, -back, top));
238			geometry.Format.Elements[0].SaveData(geometry.writer,
239				new Vector(-right, -back, bottom));
240			geometry.Format.Elements[0].SaveData(geometry.writer,
241				new Vector(right, -back, bottom));
242			geometry.Format.Elements[0].SaveData(geometry.writer,
243				new Vector(right, back, top));
244			geometry.Format.Elements[0].SaveData(geometry.writer,
245				new Vector(-right, back, top));
246			geometry.Format.Elements[0].SaveData(geometry.writer,
247				new Vector(right, back, bottom));
248			geometry.Format.Elements[0].SaveData(geometry.writer,
249				new Vector(-right, back, bottom));
250
251			// Now go through all the faces and generate the indices (see comments)
252			// Indices are reversed so that the Cube is visible when you are inside it.
253			geometry.Indices = new ushort[]
254			{
255				// Front face quad (0, 1, 2, 2, 1, 3)
256				0, 1, 2, 2, 1, 3,
257				// Back face quad (4, 5, 6, 6, 5, 7)
258				4, 5, 6, 6, 5, 7,
259				// Left face quad (5, 0, 7, 7, 0, 2)
260				5, 0, 7, 7, 0, 2,
261				// Right face quad (1, 4, 3, 3, 4, 6)
262				1, 4, 3, 3, 4, 6,
263				// Upper face quad (5, 4, 0, 0, 4, 1,)
264				5, 4, 0, 0, 4, 1,
265				// Lower face quad (6, 7, 3, 3, 7, 2,)
266				6, 7, 3, 3, 7, 2,
267			};
268
269			return geometry;
270		} // CreateCube(vertexFormat, width, depth)
271		#endregion
272
273		#region CreateSphere
274		/// <summary>
275		/// Creates a sphere.
276		/// </summary>
277		/// <param name="vertexFormat">The vertex format.</param>
278		/// <param name="radius">The radius.</param>
279		/// <param name="setColor">Color of the set.</param>
280		/// <returns></returns>
281		public static GeometryData CreateSphere(VertexFormat vertexFormat,
282			float radius, Color setColor)
283		{
284			// Set up the subdivision in which we build the geometry of the sphere.
285			// The tessellation indicates in how many segments the sphere is
286			// interpolated, smaller spheres have less tessellation, bigger spheres
287			// use more tessellation, but we keep it between 4 and 32.
288			int tessellation = (int)(MathHelper.Sqrt(radius) * 6.0f);
289			tessellation = MathHelper.Clamp(tessellation, 6, 32);
290			// Make multiple of 3 for better fitting texturing
291			tessellation = (tessellation / 3) * 3;
292			int verticalSegments = tessellation;
293			// Add one horizontal segment to fit around a circle, good for UVs
294			int horizontalSegments = tessellation * 2 + 1;
295
296			// Create rings of vertices at progressively higher latitudes.
297			GeometryData geometry = new GeometryData(
298				"<GeneratedSphere(" + radius + ")>",
299				verticalSegments * horizontalSegments,
300				vertexFormat, 6 * (verticalSegments - 1) * horizontalSegments,
301				true, false);
302			for (int index = 0; index < verticalSegments; index++)
303			{
304				// Lets begin with the latitude and divide each segment. As we are
305				// using circular surface, we will split each position along the
306				// vertical axis with the cosine. That is every position in the
307				// vertical segment creates a ring with a maximum width stated by the
308				// cosine (at the top width=0, in the medium reaches the maximum
309				// width = 1, and at the bottom width=0).
310				float latitude = index * 180.0f / (float)(verticalSegments - 1) - 90.0f;
311				float dy = MathHelper.Sin(latitude);
312				float dxz = MathHelper.Cos(latitude);
313
314				// Create a single ring of vertices at this latitude.
315				for (int j = 0; j < horizontalSegments; j++)
316				{
317					// Next step is tessellation along horizontal axis in which we just 
318					// simple indicates the position of each vertex in the ring with the
319					// previously established width along the surface of the sphere
320					float longitude = j * 360.0f / (float)(horizontalSegments - 1);
321
322					float dx = MathHelper.Cos(longitude) * dxz;
323					float dz = MathHelper.Sin(longitude) * dxz;
324
325					// finally we got the correct position 
326					Vector normal = new Vector(dx, dy, dz);
327
328					// and we assign the corresponding U,V coordinate of the texture
329					// in a way that for each circle of the sphere it contains a line
330					// of the texture image
331					geometry.SetVertexData(
332						index * horizontalSegments + j,
333						normal * radius,
334						new Point((float)j / (float)(horizontalSegments - 1),
335							(float)index / (float)(verticalSegments - 1)), setColor, normal,
336							Vector.Cross(normal, Vector.UnitZ));
337				} // for
338			} // for
339
340			// Create a fan connecting the bottom vertex to the bottom latitude ring
341			// and finally set up the indices connecting each vertex.
342			// Fill the sphere body with triangles joining each pair of rings.
343			int num = 0;
344			for (int index = 0; index < verticalSegments - 1; index++)
345			{
346				for (int j = 0; j < horizontalSegments; j++)
347				{
348					int nextI = (index + 1);
349					int nextJ = (j + 1) % horizontalSegments;
350
351					// Note: Counter-Clockwise is front
352					geometry.Indices[num++] =
353						(ushort)(index * horizontalSegments + j);
354					geometry.Indices[num++] =
355						(ushort)(nextI * horizontalSegments + j);
356					geometry.Indices[num++] =
357						(ushort)(index * horizontalSegments + nextJ);
358
359					geometry.Indices[num++] =
360						(ushort)(index * horizontalSegments + nextJ);
361					geometry.Indices[num++] =
362						(ushort)(nextI * horizontalSegments + j);
363					geometry.Indices[num++] =
364						(ushort)(nextI * horizontalSegments + nextJ);
365				} // for
366			} // for
367
368			return geometry;
369		} // CreateSphere(vertexFormat, radius, setColor)
370		#endregion
371
372		#region Constructor (forbidden)
373		/// <summary>
374		/// Geometry data
375		/// </summary>
376		private GeometryHelper()
377			: base(null, null)
378		{
379		}
380		#endregion
381	}
382}