/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}