Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
GeometricPrimitive.GeoSphere.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 //
4 // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to deal
8 // in the Software without restriction, including without limitation the rights
9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 // copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 // THE SOFTWARE.
23 // -----------------------------------------------------------------------------
24 // The following code is a port of DirectXTk http://directxtk.codeplex.com
25 // -----------------------------------------------------------------------------
26 // Microsoft Public License (Ms-PL)
27 //
28 // This license governs use of the accompanying software. If you use the
29 // software, you accept this license. If you do not accept the license, do not
30 // use the software.
31 //
32 // 1. Definitions
33 // The terms "reproduce," "reproduction," "derivative works," and
34 // "distribution" have the same meaning here as under U.S. copyright law.
35 // A "contribution" is the original software, or any additions or changes to
36 // the software.
37 // A "contributor" is any person that distributes its contribution under this
38 // license.
39 // "Licensed patents" are a contributor's patent claims that read directly on
40 // its contribution.
41 //
42 // 2. Grant of Rights
43 // (A) Copyright Grant- Subject to the terms of this license, including the
44 // license conditions and limitations in section 3, each contributor grants
45 // you a non-exclusive, worldwide, royalty-free copyright license to reproduce
46 // its contribution, prepare derivative works of its contribution, and
47 // distribute its contribution or any derivative works that you create.
48 // (B) Patent Grant- Subject to the terms of this license, including the license
49 // conditions and limitations in section 3, each contributor grants you a
50 // non-exclusive, worldwide, royalty-free license under its licensed patents to
51 // make, have made, use, sell, offer for sale, import, and/or otherwise dispose
52 // of its contribution in the software or derivative works of the contribution
53 // in the software.
54 //
55 // 3. Conditions and Limitations
56 // (A) No Trademark License- This license does not grant you rights to use any
57 // contributors' name, logo, or trademarks.
58 // (B) If you bring a patent claim against any contributor over patents that
59 // you claim are infringed by the software, your patent license from such
60 // contributor to the software ends automatically.
61 // (C) If you distribute any portion of the software, you must retain all
62 // copyright, patent, trademark, and attribution notices that are present in the
63 // software.
64 // (D) If you distribute any portion of the software in source code form, you
65 // may do so only under this license by including a complete copy of this
66 // license with your distribution. If you distribute any portion of the software
67 // in compiled or object code form, you may only do so under a license that
68 // complies with this license.
69 // (E) The software is licensed "as-is." You bear the risk of using it. The
70 // contributors give no express warranties, guarantees or conditions. You may
71 // have additional consumer rights under your local laws which this license
72 // cannot change. To the extent permitted under your local laws, the
73 // contributors exclude the implied warranties of merchantability, fitness for a
74 // particular purpose and non-infringement.
75 
76 using System;
77 using System.Collections.Generic;
78 using SiliconStudio.Core;
79 using SiliconStudio.Core.Mathematics;
80 
81 namespace SiliconStudio.Paradox.Graphics
82 {
83  public partial class GeometricPrimitive
84  {
85  /// <summary>
86  /// A Geodesic sphere.
87  /// </summary>
88  public struct GeoSphere
89  {
90  //--------------------------------------------------------------------------------------
91  // Geodesic sphere
92  //--------------------------------------------------------------------------------------
93 
94  private static readonly Vector3[] OctahedronVertices = new Vector3[]
95  {
96  // when looking down the negative z-axis (into the screen)
97  new Vector3( 0, 1, 0), // 0 top
98  new Vector3( 0, 0, -1), // 1 front
99  new Vector3( 1, 0, 0), // 2 right
100  new Vector3( 0, 0, 1), // 3 back
101  new Vector3(-1, 0, 0), // 4 left
102  new Vector3( 0, -1, 0), // 5 bottom
103  };
104 
105  private static readonly int[] OctahedronIndices = new int[]
106  {
107  0, 1, 2, // top front-right face
108  0, 2, 3, // top back-right face
109  0, 3, 4, // top back-left face
110  0, 4, 1, // top front-left face
111  5, 1, 4, // bottom front-left face
112  5, 4, 3, // bottom back-left face
113  5, 3, 2, // bottom back-right face
114  5, 2, 1, // bottom front-right face
115  };
116 
117  private List<Vector3> vertexPositions;
118 
119  private List<int> indexList;
120 
121  private List<VertexPositionNormalTexture> vertices;
122 
123  private unsafe int* indices;
124 
125  // Key: an edge
126  // Value: the index of the vertex which lies midway between the two vertices pointed to by the key value
127  // This map is used to avoid duplicating vertices when subdividing triangles along edges.
128  Dictionary<UndirectedEdge, int> subdividedEdges;
129 
130  /// <summary>
131  /// Creates a Geodesic sphere.
132  /// </summary>
133  /// <param name="graphicsDevice">The graphics device.</param>
134  /// <param name="diameter">The diameter.</param>
135  /// <param name="tessellation">The tessellation.</param>
136  /// <param name="toLeftHanded">if set to <c>true</c> vertices and indices will be transformed to left handed. Default is false.</param>
137  /// <returns>A Geodesic sphere.</returns>
138  public static GeometricPrimitive New(GraphicsDevice graphicsDevice, float diameter = 1.0f, int tessellation = 3, bool toLeftHanded = false)
139  {
140  return new GeometricPrimitive(graphicsDevice, New(diameter, tessellation, toLeftHanded));
141  }
142 
143  /// <summary>
144  /// Creates a Geodesic sphere.
145  /// </summary>
146  /// <param name="diameter">The diameter.</param>
147  /// <param name="tessellation">The tessellation.</param>
148  /// <param name="toLeftHanded">if set to <c>true</c> vertices and indices will be transformed to left handed. Default is false.</param>
149  /// <returns>A Geodesic sphere.</returns>
150  public static GeometricMeshData<VertexPositionNormalTexture> New(float diameter = 1.0f, int tessellation = 3, bool toLeftHanded = false)
151  {
152  var sphere = new GeoSphere();
153  return sphere.Create(diameter, tessellation, toLeftHanded);
154  }
155 
156  /// <summary>
157  /// Creates a Geodesic sphere.
158  /// </summary>
159  /// <param name="diameter">The diameter.</param>
160  /// <param name="tessellation">The tessellation.</param>
161  /// <param name="toLeftHanded">if set to <c>true</c> vertices and indices will be transformed to left handed. Default is false.</param>
162  /// <returns>A Geodesic sphere.</returns>
163  private unsafe GeometricMeshData<VertexPositionNormalTexture> Create(float diameter = 1.0f, int tessellation = 3, bool toLeftHanded = false)
164  {
165  subdividedEdges = new Dictionary<UndirectedEdge, int>();
166 
167  float radius = diameter / 2.0f;
168 
169  // Start with an octahedron; copy the data into the vertex/index collection.
170  vertexPositions = new List<Vector3>(OctahedronVertices);
171  indexList = new List<int>(OctahedronIndices);
172 
173  // We know these values by looking at the above index list for the octahedron. Despite the subdivisions that are
174  // about to go on, these values aren't ever going to change because the vertices don't move around in the array.
175  // We'll need these values later on to fix the singularities that show up at the poles.
176  const int northPoleIndex = 0;
177  const int southPoleIndex = 5;
178 
179  for (int iSubdivision = 0; iSubdivision < tessellation; ++iSubdivision)
180  {
181  // The new index collection after subdivision.
182  var newIndices = new List<int>();
183  subdividedEdges.Clear();
184 
185  int triangleCount = indexList.Count / 3;
186  for (int iTriangle = 0; iTriangle < triangleCount; ++iTriangle)
187  {
188  // For each edge on this triangle, create a new vertex in the middle of that edge.
189  // The winding order of the triangles we output are the same as the winding order of the inputs.
190 
191  // Indices of the vertices making up this triangle
192  int iv0 = indexList[iTriangle * 3 + 0];
193  int iv1 = indexList[iTriangle * 3 + 1];
194  int iv2 = indexList[iTriangle * 3 + 2];
195 
196  //// The existing vertices
197  //Vector3 v0 = vertexPositions[iv0];
198  //Vector3 v1 = vertexPositions[iv1];
199  //Vector3 v2 = vertexPositions[iv2];
200 
201  // Get the new vertices
202  Vector3 v01; // vertex on the midpoint of v0 and v1
203  Vector3 v12; // ditto v1 and v2
204  Vector3 v20; // ditto v2 and v0
205  int iv01; // index of v01
206  int iv12; // index of v12
207  int iv20; // index of v20
208 
209  // Add/get new vertices and their indices
210  DivideEdge(iv0, iv1, out v01, out iv01);
211  DivideEdge(iv1, iv2, out v12, out iv12);
212  DivideEdge(iv0, iv2, out v20, out iv20);
213 
214  // Add the new indices. We have four new triangles from our original one:
215  // v0
216  // o
217  // /a\
218  // v20 o---o v01
219  // /b\c/d\
220  // v2 o---o---o v1
221  // v12
222 
223  // a
224  newIndices.Add(iv0);
225  newIndices.Add(iv01);
226  newIndices.Add(iv20);
227 
228  // b
229  newIndices.Add(iv20);
230  newIndices.Add(iv12);
231  newIndices.Add(iv2);
232 
233  // d
234  newIndices.Add(iv20);
235  newIndices.Add(iv01);
236  newIndices.Add(iv12);
237 
238  // d
239  newIndices.Add(iv01);
240  newIndices.Add(iv1);
241  newIndices.Add(iv12);
242  }
243 
244  indexList.Clear();
245  indexList.AddRange(newIndices);
246  }
247 
248  // Now that we've completed subdivision, fill in the final vertex collection
249  vertices = new List<VertexPositionNormalTexture>(vertexPositions.Count);
250  for (int i = 0; i < vertexPositions.Count; i++)
251  {
252  var vertexValue = vertexPositions[i];
253 
254  var normal = vertexValue;
255  normal.Normalize();
256 
257  var pos = normal * radius;
258 
259  // calculate texture coordinates for this vertex
260  float longitude = (float)Math.Atan2(normal.X, -normal.Z);
261  float latitude = (float)Math.Acos(normal.Y);
262 
263  float u = (float)(longitude / (Math.PI * 2.0) + 0.5);
264  float v = (float)(latitude / Math.PI);
265 
266  var texcoord = new Vector2(1.0f - u, v);
267  vertices.Add(new VertexPositionNormalTexture(pos, normal, texcoord));
268  }
269 
270  const float XMVectorSplatEpsilon = 1.192092896e-7f;
271 
272  // There are a couple of fixes to do. One is a texture coordinate wraparound fixup. At some point, there will be
273  // a set of triangles somewhere in the mesh with texture coordinates such that the wraparound across 0.0/1.0
274  // occurs across that triangle. Eg. when the left hand side of the triangle has a U coordinate of 0.98 and the
275  // right hand side has a U coordinate of 0.0. The intent is that such a triangle should render with a U of 0.98 to
276  // 1.0, not 0.98 to 0.0. If we don't do this fixup, there will be a visible seam across one side of the sphere.
277  //
278  // Luckily this is relatively easy to fix. There is a straight edge which runs down the prime meridian of the
279  // completed sphere. If you imagine the vertices along that edge, they circumscribe a semicircular arc starting at
280  // y=1 and ending at y=-1, and sweeping across the range of z=0 to z=1. x stays zero. It's along this edge that we
281  // need to duplicate our vertices - and provide the correct texture coordinates.
282  int preCount = vertices.Count;
283  var indicesArray = indexList.ToArray();
284  fixed (void* pIndices = indicesArray)
285  {
286  indices = (int*)pIndices;
287 
288  for (int i = 0; i < preCount; ++i)
289  {
290  // This vertex is on the prime meridian if position.x and texcoord.u are both zero (allowing for small epsilon).
291  bool isOnPrimeMeridian = MathUtil.WithinEpsilon(vertices[i].Position.X, 0, XMVectorSplatEpsilon)
292  && MathUtil.WithinEpsilon(vertices[i].TextureCoordinate.X, 0, XMVectorSplatEpsilon);
293 
294  if (isOnPrimeMeridian)
295  {
296  int newIndex = vertices.Count;
297 
298  // copy this vertex, correct the texture coordinate, and add the vertex
299  VertexPositionNormalTexture v = vertices[i];
300  v.TextureCoordinate.X = 1.0f;
301  vertices.Add(v);
302 
303  // Now find all the triangles which contain this vertex and update them if necessary
304  for (int j = 0; j < indexList.Count; j += 3)
305  {
306  var triIndex0 = &indices[j + 0];
307  var triIndex1 = &indices[j + 1];
308  var triIndex2 = &indices[j + 2];
309 
310  if (*triIndex0 == i)
311  {
312  // nothing; just keep going
313  }
314  else if (*triIndex1 == i)
315  {
316  Utilities.Swap(ref *triIndex0, ref *triIndex1);
317  }
318  else if (*triIndex2 == i)
319  {
320  Utilities.Swap(ref *triIndex0, ref *triIndex2);
321  }
322  else
323  {
324  // this triangle doesn't use the vertex we're interested in
325  continue;
326  }
327 
328  // check the other two vertices to see if we might need to fix this triangle
329  if (Math.Abs(vertices[*triIndex0].TextureCoordinate.X - vertices[*triIndex1].TextureCoordinate.X) > 0.5f ||
330  Math.Abs(vertices[*triIndex0].TextureCoordinate.X - vertices[*triIndex2].TextureCoordinate.X) > 0.5f)
331  {
332  // yep; replace the specified index to point to the new, corrected vertex
333  indices[j + 0] = newIndex;
334  }
335  }
336  }
337  }
338 
339  FixPole(northPoleIndex);
340  FixPole(southPoleIndex);
341 
342  // Clear indices as it will not be accessible outside the fixed statement
343  indices = (int*)0;
344  }
345 
346  return new GeometricMeshData<VertexPositionNormalTexture>(vertices.ToArray(), indexList.ToArray(), toLeftHanded) {Name = "GeoSphere"};
347  }
348 
349  private unsafe void FixPole(int poleIndex)
350  {
351  var poleVertex = vertices[poleIndex];
352  bool overwrittenPoleVertex = false; // overwriting the original pole vertex saves us one vertex
353 
354  for (ushort i = 0; i < indexList.Count; i += 3)
355  {
356  // These pointers point to the three indices which make up this triangle. pPoleIndex is the pointer to the
357  // entry in the index array which represents the pole index, and the other two pointers point to the other
358  // two indices making up this triangle.
359  int* pPoleIndex;
360  int* pOtherIndex0;
361  int* pOtherIndex1;
362  if (indices[i + 0] == poleIndex)
363  {
364  pPoleIndex = &indices[i + 0];
365  pOtherIndex0 = &indices[i + 1];
366  pOtherIndex1 = &indices[i + 2];
367  }
368  else if (indices[i + 1] == poleIndex)
369  {
370  pPoleIndex = &indices[i + 1];
371  pOtherIndex0 = &indices[i + 2];
372  pOtherIndex1 = &indices[i + 0];
373  }
374  else if (indices[i + 2] == poleIndex)
375  {
376  pPoleIndex = &indices[i + 2];
377  pOtherIndex0 = &indices[i + 0];
378  pOtherIndex1 = &indices[i + 1];
379  }
380  else
381  {
382  continue;
383  }
384 
385  // Calculate the texcoords for the new pole vertex, add it to the vertices and update the index
386  var newPoleVertex = poleVertex;
387  newPoleVertex.TextureCoordinate.X = (vertices[*pOtherIndex0].TextureCoordinate.X + vertices[*pOtherIndex1].TextureCoordinate.X) * 0.5f;
388  newPoleVertex.TextureCoordinate.Y = poleVertex.TextureCoordinate.Y;
389 
390  if (!overwrittenPoleVertex)
391  {
392  vertices[poleIndex] = newPoleVertex;
393  overwrittenPoleVertex = true;
394  }
395  else
396  {
397  *pPoleIndex = vertices.Count;
398  vertices.Add(newPoleVertex);
399  }
400  }
401  }
402 
403  // Function that, when given the index of two vertices, creates a new vertex at the midpoint of those vertices.
404  private void DivideEdge(int i0, int i1, out Vector3 outVertex, out int outIndex)
405  {
406  var edge = new UndirectedEdge(i0, i1);
407 
408  // Check to see if we've already generated this vertex
409  if (subdividedEdges.TryGetValue(edge, out outIndex))
410  {
411  // We've already generated this vertex before
412  outVertex = vertexPositions[outIndex]; // and the vertex itself
413  }
414  else
415  {
416  // Haven't generated this vertex before: so add it now
417 
418  // outVertex = (vertices[i0] + vertices[i1]) / 2
419  outVertex = (vertexPositions[i0] + vertexPositions[i1])*0.5f;
420  outIndex = vertexPositions.Count;
421  vertexPositions.Add(outVertex);
422 
423  // Now add it to the map.
424  subdividedEdges[edge] = outIndex;
425  }
426  }
427 
428  // An undirected edge between two vertices, represented by a pair of indexes into a vertex array.
429  // Because this edge is undirected, (a,b) is the same as (b,a).
430  private struct UndirectedEdge : IEquatable<UndirectedEdge>
431  {
432  public UndirectedEdge(int item1, int item2)
433  {
434  // Makes an undirected edge. Rather than overloading comparison operators to give us the (a,b)==(b,a) property,
435  // we'll just ensure that the larger of the two goes first. This'll simplify things greatly.
436  Item1 = Math.Max(item1, item2);
437  Item2 = Math.Min(item1, item2);
438  }
439 
440  public readonly int Item1;
441 
442  public readonly int Item2;
443 
444  public bool Equals(UndirectedEdge other)
445  {
446  return Item1 == other.Item1 && Item2 == other.Item2;
447  }
448 
449  public override bool Equals(object obj)
450  {
451  if (ReferenceEquals(null, obj)) return false;
452  return obj is UndirectedEdge && Equals((UndirectedEdge)obj);
453  }
454 
455  public override int GetHashCode()
456  {
457  unchecked
458  {
459  return (Item1.GetHashCode() * 397) ^ Item2.GetHashCode();
460  }
461  }
462 
463  public static bool operator ==(UndirectedEdge left, UndirectedEdge right)
464  {
465  return left.Equals(right);
466  }
467 
468  public static bool operator !=(UndirectedEdge left, UndirectedEdge right)
469  {
470  return !left.Equals(right);
471  }
472  }
473  }
474  }
475 }
SiliconStudio.Paradox.Games.Mathematics.Vector2 Vector2
static GeometricMeshData< VertexPositionNormalTexture > New(float diameter=1.0f, int tessellation=3, bool toLeftHanded=false)
Creates a Geodesic sphere.
static GeometricPrimitive New(GraphicsDevice graphicsDevice, float diameter=1.0f, int tessellation=3, bool toLeftHanded=false)
Creates a Geodesic sphere.
A geometric primitive. Use Cube, Cylinder, GeoSphere, Plane, Sphere, Teapot, Torus. See Draw+vertices to learn how to use it.
Represents a three dimensional mathematical vector.
Definition: Vector3.cs:42
Performs primitive-based rendering, creates resources, handles system-level variables, adjusts gamma ramp levels, and creates shaders. See The+GraphicsDevice+class to learn more about the class.
static bool WithinEpsilon(float a, float b, float epsilon)
Checks if a - b are almost equals within a float epsilon.
Definition: MathUtil.cs:137
SiliconStudio.Core.Mathematics.Vector3 Vector3