/ContentSystem/Rendering/GeometryData.cs
# · C# · 1805 lines · 1008 code · 155 blank · 642 comment · 63 complexity · 69a8189ae9f3a714d289af3d194777ae MD5 · raw file
- using System;
- using System.Collections.Generic;
- using System.IO;
- using Delta.ContentSystem.Rendering.Helpers;
- using Delta.Engine.Dynamic;
- using Delta.Utilities;
- using Delta.Utilities.Datatypes;
- using Delta.Utilities.Graphics;
- using Delta.Utilities.Helpers;
- using NUnit.Framework;
-
- namespace Delta.ContentSystem.Rendering
- {
- /// <summary>
- /// Geometry data class, just contains vertices and indices for rendering.
- /// Geometry meshes are highly dependant on the underlying type for the
- /// vertices. Some helper methods can be used to generate simple meshes
- /// (boxes, spheres, etc.). Data is stored in binary format for quick access
- /// and the used material data is also stored internally in here. Please note
- /// that rendering a level or a complex model usually consists of multiple
- /// meshes, a geometry mesh is just the lowest underlying 3D type in the
- /// engine! Meshes are even used for dynamic vertex buffers, lines and all 2D
- /// UI drawing, but those meshes are usually not saved out to content files.
- /// <para />
- /// While this is not directly a content type, it is pretty much what defines
- /// a mesh, which is used in many other content types as well (Models, Levels
- /// and also important for MeshAnimations, which are linked up in Models
- /// too). You can of course also use this class for your own dynamically
- /// created data (e.g. landscape rendering, BSP trees, or any other type of
- /// rendering)
- /// <para />
- /// Note: Geometry data can get quite big and while we need this class and
- /// all the internal data for dynamic geometry data that needs to be updated
- /// from time to time, it is not needed for static geometry data after the
- /// internal data structure has been created on the graphic card (vertex
- /// and index buffer objects). To save memory we can dispose this object
- /// and just in case it is needed again, we need to notify the owner to
- /// make sure that the data is reloaded again.
- /// </summary>
- /// <remarks>
- /// Vertex data. Note: Filling data into this is up to 5 times slower than
- /// directly filling a struct array. For setting this once or even loading
- /// from a file, this class is perfectly fine (loading from a file is even
- /// about 2 times faster than using struct arrays). However, for dynamic
- /// vertex buffers it is a good optimization to have a native struct
- /// representing the data and setting that instead with SetData (e.g. see
- /// the DrawManager implementation for line rendering using ColoredVertex).
- /// Please note that even with the slower speed of GeometryData it is still
- /// a very good idea to use for our engine because we want to use vertex
- /// and index buffers, which is only possible with a generic byte data
- /// stream and thus much faster as converting data types or not using
- /// vertex buffers. So rendering is as quick as it gets, especially for
- /// static geometry data (which is what we will use 99% of the time) and
- /// dynamic geometry is also rendered very quickly (in fact as fast as
- /// possible on each platform), just setting the data is a little slow on
- /// the CPU because of all the reader/writer stuff involved here.
- /// </remarks>
- public class GeometryData : ISaveLoadBinary, IDisposable
- {
- #region Constants
- /// <summary>
- /// Version number for this MeshData. If this goes above 1, we need
- /// to support loading older versions as well. Saving is always going
- /// to be the latest version (this one).
- /// </summary>
- private const int VersionNumber = 2;
- #endregion
-
- #region CreateCube (Static)
- /// <summary>
- /// Generate a cube mesh with the same size in all dimensions. Box meshes
- /// have their pivot point at Vector.Zero (to make them easier to place).
- /// Note: The GeometryHelper in Delta.Rendering.Models has many more
- /// Create methods to create spheres, cones, cylinders, etc.
- /// </summary>
- /// <param name="vertexFormat">The vertex format for this geometry</param>
- /// <param name="size">Size of the box in all dimensions</param>
- /// <param name="setColor">Vertex color during creation.</param>
- /// <returns>
- /// Geometry Data containing all vertices and indices for this box
- /// </returns>
- public static GeometryData CreateCube(VertexFormat vertexFormat,
- float size, Color setColor)
- {
- return CreateBox(vertexFormat, size, size, size, false, setColor);
- }
-
- /// <summary>
- /// Generate a cube mesh with the same size in all dimensions. Box meshes
- /// have their pivot point at Vector.Zero (to make them easier to place).
- /// Note: The GeometryHelper in Delta.Rendering.Models has many more
- /// Create methods to create spheres, cones, cylinders, etc.
- /// </summary>
- /// <param name="vertexFormat">The vertex format for this geometry</param>
- /// <param name="size">Size of the box in all dimensions</param>
- /// <returns>
- /// Geometry Data containing all vertices and indices for this box
- /// </returns>
- public static GeometryData CreateCube(VertexFormat vertexFormat, float size)
- {
- return CreateBox(vertexFormat, size, size, size, false, Color.White);
- }
- #endregion
-
- #region CreateBox (Static)
- /// <summary>
- /// Generates a box mesh with the given sizes and a material. Box meshes
- /// have their pivot point at Vector.Zero (to make them easier to place).
- /// </summary>
- /// <param name="vertexFormat">The vertex format.</param>
- /// <param name="width">The width in the X dimension</param>
- /// <param name="depth">The depth in the Y dimension</param>
- /// <param name="height">The height in the Z dimension</param>
- /// <param name="invertWinding">Whether to invert indices order</param>
- /// <param name="setColor">Vertex color during creation.</param>
- /// <returns>
- /// Geometry Data containing all vertices and indices for this box
- /// </returns>
- public static GeometryData CreateBox(VertexFormat vertexFormat,
- float width, float depth, float height, bool invertWinding, Color setColor)
- {
- return CreateBox(vertexFormat, width, depth, height, invertWinding,
- "<GeneratedBox(" + width + ", " + depth + ", " + height + ")>",
- setColor);
- }
-
- /// <summary>
- /// Generates a box mesh with a given pivot point height (usually used
- /// to generate the box at Vector.Zero, see other overloads).
- /// </summary>
- /// <param name="vertexFormat">The vertex format.</param>
- /// <param name="width">The width in the X dimension</param>
- /// <param name="depth">The depth in the Y dimension</param>
- /// <param name="height">The height in the Z dimension</param>
- /// <param name="invertWinding">Whether to invert indices order</param>
- /// <param name="meshName">Name of the mesh.</param>
- /// <param name="setColor">Vertex color during creation.</param>
- /// <returns>
- /// Geometry Data containing all vertices and indices for this box
- /// </returns>
- public static GeometryData CreateBox(VertexFormat vertexFormat,
- float width, float depth, float height, bool invertWinding,
- string meshName, Color setColor)
- {
- // The box has 8 positions, but we need 24 vertices (4 for each of the
- // 6 sides, each side has its own texture coordinates, see ordering)
- // Texture coordinates begin at the left upper corner (0, 0)
- GeometryData geometry = new GeometryData(
- meshName, 24, vertexFormat, 36, true, false);
-
- Vector cubeHalfSize = new Vector(width, height, depth) / 2;
- // Create each face in turn.
- int vertexIndex = 0;
-
- // front side
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, -cubeHalfSize.Y, cubeHalfSize.Z),
- Point.UnitY, setColor, Vector.UnitZ, Vector.UnitZ);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, -cubeHalfSize.Y, cubeHalfSize.Z),
- Point.One, setColor, Vector.UnitZ, Vector.UnitZ);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, cubeHalfSize.Y, cubeHalfSize.Z),
- Point.UnitX, setColor, Vector.UnitZ, Vector.UnitZ);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, cubeHalfSize.Y, cubeHalfSize.Z),
- Point.Zero, setColor, Vector.UnitZ, Vector.UnitZ);
-
- // back side
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, -cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.UnitY, setColor, -Vector.UnitZ, -Vector.UnitZ);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, -cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.One, setColor, -Vector.UnitZ, -Vector.UnitZ);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.UnitX, setColor, -Vector.UnitZ, -Vector.UnitZ);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.Zero, setColor, -Vector.UnitZ, -Vector.UnitZ);
-
- // left side
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, -cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.UnitY, setColor, -Vector.UnitX, -Vector.UnitX);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, -cubeHalfSize.Y, cubeHalfSize.Z),
- Point.One, setColor, -Vector.UnitX, -Vector.UnitX);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, cubeHalfSize.Y, cubeHalfSize.Z),
- Point.UnitX, setColor, -Vector.UnitX, -Vector.UnitX);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.Zero, setColor, -Vector.UnitX, -Vector.UnitX);
-
- // right side
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, -cubeHalfSize.Y, cubeHalfSize.Z),
- Point.UnitY, setColor, Vector.UnitX, Vector.UnitX);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, -cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.One, setColor, Vector.UnitX, Vector.UnitX);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.UnitX, setColor, Vector.UnitX, Vector.UnitX);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, cubeHalfSize.Y, cubeHalfSize.Z),
- Point.Zero, setColor, Vector.UnitX, Vector.UnitX);
-
- // up side
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, cubeHalfSize.Y, cubeHalfSize.Z),
- Point.UnitY, setColor, Vector.UnitY, Vector.UnitY);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, cubeHalfSize.Y, cubeHalfSize.Z),
- Point.One, setColor, Vector.UnitY, Vector.UnitY);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.UnitX, setColor, Vector.UnitY, Vector.UnitY);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.Zero, setColor, Vector.UnitY, Vector.UnitY);
-
- // down side
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, -cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.UnitY, setColor, -Vector.UnitY, -Vector.UnitY);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, -cubeHalfSize.Y, -cubeHalfSize.Z),
- Point.One, setColor, -Vector.UnitY, -Vector.UnitY);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(cubeHalfSize.X, -cubeHalfSize.Y, cubeHalfSize.Z),
- Point.UnitX, setColor, -Vector.UnitY, -Vector.UnitY);
-
- geometry.SetVertexData(vertexIndex++,
- new Vector(-cubeHalfSize.X, -cubeHalfSize.Y, cubeHalfSize.Z),
- Point.Zero, setColor, -Vector.UnitY, -Vector.UnitY);
-
- if (invertWinding)
- {
- geometry.Indices = new ushort[]
- {
- // front
- 2, 1, 0,
- 3, 2, 0,
- // back
- 6, 5, 4,
- 7, 6, 4,
- // left
- 10, 9, 8,
- 11, 10, 8,
- // right
- 14, 13, 12,
- 15, 14, 12,
- // up
- 18, 17, 16,
- 19, 18, 16,
- // down
- 22, 21, 20,
- 23, 22, 20
- };
- }
- else
- {
- geometry.Indices = new ushort[]
- {
- // front
- 0, 1, 2,
- 0, 2, 3,
- // back
- 4, 5, 6,
- 4, 6, 7,
- // left
- 8, 9, 10,
- 8, 10, 11,
- // right
- 12, 13, 14,
- 12, 14, 15,
- // up
- 16, 17, 18,
- 16, 18, 19,
- // down
- 20, 21, 22,
- 20, 22, 23
- };
- }
-
- return geometry;
- }
- #endregion
-
- #region CreateSphere (Static)
- /// <summary>
- /// Creates a sphere as an GeometryData object with the given parameters.
- /// </summary>
- /// <param name="vertexFormat">The vertex format to use.</param>
- /// <param name="radius">The radius for the sphere from the center</param>
- /// <param name="setColor">
- /// Color for the new sphere in case the vertex format supports colored
- /// vertices (otherwise ignored).
- /// </param>
- /// <param name="invertWinding">
- /// Whether to invert winding order (usually false, except you want to be
- /// inside the sphere).
- /// </param>
- /// <returns>The geometry data of the specified sphere.</returns>
- public static GeometryData CreateSphere(VertexFormat vertexFormat,
- float radius, Color setColor, bool invertWinding = false)
- {
- // Set up the subdivision in which we build the geometry of the sphere.
- // The tessellation indicates in how many segments the sphere is
- // interpolated, smaller spheres have less tessellation, bigger spheres
- // use more tessellation, but we keep it between 4 and 32.
- int tessellation = (int)(MathHelper.Sqrt(radius) * 6.0f);
- tessellation = MathHelper.Clamp(tessellation, 6, 32);
- // Make multiple of 3 for better fitting texturing
- tessellation = (tessellation / 3) * 3;
- int verticalSegments = tessellation;
- // Add one horizontal segment to fit around a circle, good for UVs
- int horizontalSegments = tessellation * 2 + 1;
-
- // Create rings of vertices at progressively higher latitudes.
- GeometryData geometry = new GeometryData(
- "<GeneratedSphere(" + radius + ")>",
- verticalSegments * horizontalSegments,
- vertexFormat, 6 * (verticalSegments - 1) * horizontalSegments,
- true, false);
- for (int index = 0; index < verticalSegments; index++)
- {
- // Lets begin with the latitude and divide each segment. As we are
- // using circular surface, we will split each position along the
- // vertical axis with the cosine. That is every position in the
- // vertical segment creates a ring with a maximum width stated by the
- // cosine (at the top width=0, in the medium reaches the maximum
- // width = 1, and at the bottom width=0).
- float latitude = index * 180.0f / (verticalSegments - 1) - 90.0f;
- float dy = MathHelper.Sin(latitude);
- float dxz = MathHelper.Cos(latitude);
-
- // Create a single ring of vertices at this latitude.
- for (int j = 0; j < horizontalSegments; j++)
- {
- // Next step is tessellation along horizontal axis in which we just
- // simple indicates the position of each vertex in the ring with the
- // previously established width along the surface of the sphere
- float longitude = j * 360.0f / (horizontalSegments - 1);
-
- float dx = MathHelper.Cos(longitude) * dxz;
- float dz = MathHelper.Sin(longitude) * dxz;
-
- // finally we got the correct position
- Vector normal = new Vector(dx, dy, dz);
-
- // and we assign the corresponding U,V coordinate of the texture
- // in a way that for each circle of the sphere it contains a line
- // of the texture image
- geometry.SetVertexData(
- index * horizontalSegments + j,
- normal * radius,
- new Point(j / (float)(horizontalSegments - 1),
- index / (float)(verticalSegments - 1)), setColor, normal,
- Vector.Cross(normal, Vector.UnitZ));
- }
- }
-
- // Create a fan connecting the bottom vertex to the bottom latitude ring
- // and finally set up the indices connecting each vertex.
- // Fill the sphere body with triangles joining each pair of rings.
- int num = 0;
- for (int index = 0; index < verticalSegments - 1; index++)
- {
- for (int j = 0; j < horizontalSegments; j++)
- {
- int nextI = (index + 1);
- int nextJ = (j + 1) % horizontalSegments;
-
- if (invertWinding)
- {
- geometry.Indices[num++] =
- (ushort)(index * horizontalSegments + nextJ);
- geometry.Indices[num++] =
- (ushort)(nextI * horizontalSegments + j);
- geometry.Indices[num++] =
- (ushort)(index * horizontalSegments + j);
-
- geometry.Indices[num++] =
- (ushort)(nextI * horizontalSegments + nextJ);
- geometry.Indices[num++] =
- (ushort)(nextI * horizontalSegments + j);
- geometry.Indices[num++] =
- (ushort)(index * horizontalSegments + nextJ);
- }
- else
- {
- geometry.Indices[num++] =
- (ushort)(index * horizontalSegments + j);
- geometry.Indices[num++] =
- (ushort)(nextI * horizontalSegments + j);
- geometry.Indices[num++] =
- (ushort)(index * horizontalSegments + nextJ);
-
- geometry.Indices[num++] =
- (ushort)(index * horizontalSegments + nextJ);
- geometry.Indices[num++] =
- (ushort)(nextI * horizontalSegments + j);
- geometry.Indices[num++] =
- (ushort)(nextI * horizontalSegments + nextJ);
- }
- }
- }
-
- return geometry;
- }
- #endregion
-
- #region CreatePlane (Static)
- /// <summary>
- /// Create plane
- /// </summary>
- /// <param name="vertexFormat">The vertex format.</param>
- /// <param name="setWidth">Width of the set.</param>
- /// <param name="setHeight">Height of the set.</param>
- /// <returns>The geometry data of the specified plane.</returns>
- public static GeometryData CreatePlane(VertexFormat vertexFormat,
- float setWidth, float setHeight)
- {
- return CreatePlane(vertexFormat, setWidth, setHeight, Color.White);
- }
-
- /// <summary>
- /// Creates an XY plane in the specified vertex format (you can use
- /// one of the predefined vertex formats from VertexData).
- /// </summary>
- /// <param name="vertexFormat">The vertex format.</param>
- /// <param name="setWidth">Set width.</param>
- /// <param name="setHeight">Set Height.</param>
- /// <param name="setColor">Set Color.</param>
- /// <returns>The geometry data of the specified plane.</returns>
- public static GeometryData CreatePlane(VertexFormat vertexFormat,
- float setWidth, float setHeight, Color setColor)
- {
- float halfWidth = setWidth * 0.5f;
- float halfDepth = setHeight * 0.5f;
- float height = 0;
-
- // We use 4 vertices with 6 indices (0, 1, 2, 2, 1, 3):
- // 0 - 1
- // | / |
- // 2 - 3
- GeometryData geometry = new GeometryData(
- "<GeneratedPlane(" + setWidth + "x" + setHeight + ")>",
- 4, vertexFormat, 6, true, false);
- Vector planeNormal = new Vector(0, 0, 1);
- Vector planeTangent = new Vector(1, 0, 0);
- // Front left
- geometry.SetVertexData(0,
- new Vector(-halfWidth, halfDepth, height),
- new Point(0, 0), setColor, planeNormal, planeTangent);
- // Front right
- geometry.SetVertexData(1,
- new Vector(halfWidth, halfDepth, height),
- new Point(1, 0), setColor, planeNormal, planeTangent);
- // Rear left
- geometry.SetVertexData(2,
- new Vector(-halfWidth, -halfDepth, height),
- new Point(0, 1), setColor, planeNormal, planeTangent);
- // Rear right
- geometry.SetVertexData(3,
- new Vector(halfWidth, -halfDepth, height),
- new Point(1, 1), setColor, planeNormal, planeTangent);
-
- // Top left polygon (Note: Counter-Clockwise is front)
- geometry.Indices[0] = 0;
- geometry.Indices[1] = 2;
- geometry.Indices[2] = 1;
- // Bottom right polygon
- geometry.Indices[3] = 2;
- geometry.Indices[4] = 3;
- geometry.Indices[5] = 1;
-
- return geometry;
- }
- #endregion
-
- #region CreateSegmentedPlane (Static)
- /// <summary>
- /// Creates an XY plane in the specified vertex format (you can use
- /// one of the predefined vertex formats from VertexData).
- /// </summary>
- /// <param name="vertexFormat">The vertex format.</param>
- /// <param name="setWidth">Sets Width.</param>
- /// <param name="setHeight">Sets Height.</param>
- /// <param name="segments">The segments.</param>
- /// <param name="uvForEachSegment">Create new uv from 0-1 for each segment.
- /// Useful for tiling (works even without tileable textures)</param>
- /// <param name="setColor">Sets Color.</param>
- public static GeometryData CreateSegmentedPlane(VertexFormat vertexFormat,
- float setWidth, float setHeight, int segments, bool uvForEachSegment,
- Color setColor)
- {
- if (segments < 1)
- {
- throw new InvalidOperationException(
- "You need at least one segment for CreateSegmentedPlane!");
- }
-
- float halfWidth = setWidth * 0.5f;
- float halfDepth = setHeight * 0.5f;
- float height = 0;
-
- // We use 4 vertices with 6 indices (0, 1, 2, 2, 1, 3):
- // 0 - 1
- // | / |
- // 2 - 3
- // But duplicate this many times for as many segments in each direction.
- int totalSegments = segments * segments;
- int segmentStride = segments + 1;
- int totalPoints =
- uvForEachSegment
- ? totalSegments * 4
- : segmentStride * segmentStride;
- GeometryData geometry = new GeometryData(
- "<GeneratedSegmentedPlane(" + setWidth + "x" + setHeight + ")>",
- totalPoints, vertexFormat, 6 * totalSegments, true, false);
- Vector planeNormal = new Vector(0, 0, 1);
- Vector planeTangent = new Vector(1, 0, 0);
- // For each segment + 1, build the points
- if (uvForEachSegment)
- {
- // For each segment, build up the 2 triangles
- int index = 0;
- int pointIndex = 0;
- for (int ySegment = 0; ySegment < segments; ySegment++)
- {
- for (int xSegment = 0; xSegment < segments; xSegment++)
- {
- float xFactor = xSegment / (float)segments;
- float yFactor = ySegment / (float)segments;
- float xFactor2 = (xSegment + 1) / (float)segments;
- float yFactor2 = (ySegment + 1) / (float)segments;
- geometry.SetVertexData(pointIndex + 0,
- new Vector(-halfWidth + xFactor * setWidth,
- halfDepth - yFactor * setHeight, height),
- new Point(0, 0), setColor, planeNormal, planeTangent);
- geometry.SetVertexData(pointIndex + 1,
- new Vector(-halfWidth + xFactor2 * setWidth,
- halfDepth - yFactor * setHeight, height),
- new Point(1, 0), setColor, planeNormal, planeTangent);
- geometry.SetVertexData(pointIndex + 2,
- new Vector(-halfWidth + xFactor * setWidth,
- halfDepth - yFactor2 * setHeight, height),
- new Point(0, 1), setColor, planeNormal, planeTangent);
- geometry.SetVertexData(pointIndex + 3,
- new Vector(-halfWidth + xFactor2 * setWidth,
- halfDepth - yFactor2 * setHeight, height),
- new Point(1, 1), setColor, planeNormal, planeTangent);
-
- // Top left polygon (Note: Counter-Clockwise is front)
- geometry.Indices[index++] = //0,0
- (ushort)(pointIndex + 0);
- geometry.Indices[index++] = //0,1
- (ushort)(pointIndex + 2);
- geometry.Indices[index++] = //1,0
- (ushort)(pointIndex + 1);
- // Bottom right polygon
- geometry.Indices[index++] = //0,1
- (ushort)(pointIndex + 2);
- geometry.Indices[index++] = //1,1
- (ushort)(pointIndex + 3);
- geometry.Indices[index++] = //1,0
- (ushort)(pointIndex + 1);
-
- pointIndex += 4;
- }
- }
- }
- else
- {
- int pointIndex = 0;
- for (int ySegment = 0; ySegment < segments + 1; ySegment++)
- {
- for (int xSegment = 0; xSegment < segments + 1; xSegment++)
- {
- float xFactor = xSegment / (float)segments;
- float yFactor = ySegment / (float)segments;
- geometry.SetVertexData(pointIndex++,
- new Vector(-halfWidth + xFactor * setWidth,
- halfDepth - yFactor * setHeight, height),
- new Point(xFactor, yFactor),
- setColor, planeNormal, planeTangent);
- }
- }
-
- // For each segment, build up the 2 triangles
- int index = 0;
- for (int ySegment = 0; ySegment < segments; ySegment++)
- {
- for (int xSegment = 0; xSegment < segments; xSegment++)
- {
- // Top left polygon (Note: Counter-Clockwise is front)
- geometry.Indices[index++] = //0,0
- (ushort)(xSegment + 0 + (ySegment + 0) * segmentStride);
- geometry.Indices[index++] = //0,1
- (ushort)(xSegment + 0 + (ySegment + 1) * segmentStride);
- geometry.Indices[index++] = //1,0
- (ushort)(xSegment + 1 + (ySegment + 0) * segmentStride);
- // Bottom right polygon
- geometry.Indices[index++] = //0,1
- (ushort)(xSegment + 0 + (ySegment + 1) * segmentStride);
- geometry.Indices[index++] = //1,1
- (ushort)(xSegment + 1 + (ySegment + 1) * segmentStride);
- geometry.Indices[index++] = //1,0
- (ushort)(xSegment + 1 + (ySegment + 0) * segmentStride);
- }
- }
- }
-
- return geometry;
- }
-
- /// <summary>
- /// Create segmented plane.
- /// </summary>
- /// <param name="vertexFormat">The vertex format.</param>
- /// <param name="setWidth">Width of the set.</param>
- /// <param name="setHeight">Height of the set.</param>
- /// <param name="segments">The segments.</param>
- /// <returns></returns>
- public static GeometryData CreateSegmentedPlane(VertexFormat vertexFormat,
- float setWidth, float setHeight, int segments)
- {
- return CreateSegmentedPlane(vertexFormat, setWidth, setHeight, segments,
- false, Color.White);
- }
- #endregion
-
- #region CreateCapsule (Static)
- /// <summary>
- /// Creates a capsule geometry in the specified vertex format (you can use
- /// one of the predefined vertex formats from VertexData).
- /// </summary>
- /// <param name="vertexFormat">The vertex format.</param>
- /// <param name="setDiameter">Diameter of the capsule</param>
- /// <param name="setLength">Length of the capsule</param>
- /// <returns>The geometry data of the specified capsule.</returns>
- public static GeometryData CreateCapsule(VertexFormat vertexFormat,
- float setDiameter, float setLength)
- {
- return CreateCapsule(vertexFormat, setDiameter, setLength,
- Color.White);
- }
-
- /// <summary>
- /// Creates a capsule geometry in the specified vertex format (you can use
- /// one of the predefined vertex formats from VertexData).
- /// </summary>
- /// <param name="vertexFormat">The vertex format.</param>
- /// <param name="setDiameter">Diameter of the capsule</param>
- /// <param name="setLength">Length of the capsule</param>
- /// <param name="setColor">Color for the capsule</param>
- /// <returns>
- /// Geometry Data with the capsule, using tessellation of 12.
- /// </returns>
- public static GeometryData CreateCapsule(VertexFormat vertexFormat,
- float setDiameter, float setLength, Color setColor)
- {
- int tessellation = 12;
-
- int verticalSegments = tessellation;
- int horizontalSegments = tessellation * 2;
-
- float radius = setDiameter / 2;
-
- int vertexCount = horizontalSegments * (verticalSegments - 1) + 2;
-
- GeometryData geometry = new GeometryData(
- "<GeneratedSegmentedPlane(" + setDiameter + "x" + setLength + ")>",
- vertexCount, vertexFormat, 6 * vertexCount, true, false);
-
- Vector down = -Vector.UnitZ;
-
- // For each segment + 1, build the points
- int pointIndex = 0;
- // Start with a single vertex at the bottom of the sphere.
- geometry.SetVertexData(pointIndex++,
- down * radius + down * 0.5f * setLength,
- Point.Zero, setColor, down, down);
-
- // Create rings of vertices at progressively higher latitudes.
- for (int i = 0; i < verticalSegments - 1; i++)
- {
- float latitude = ((i + 1) * MathHelper.Pi /
- verticalSegments) - MathHelper.PiHalf;
- float dy = (float)Math.Sin(latitude);
- float dxz = (float)Math.Cos(latitude);
-
- bool bla = false;
-
- if (i > (verticalSegments - 2) / 2)
- {
- bla = true;
- }
-
- // Create a single ring of vertices at this latitude.
- for (int j = 0; j < horizontalSegments; j++)
- {
- float longitude = j * MathHelper.PiDouble / horizontalSegments;
-
- float dx = (float)Math.Cos(longitude) * dxz;
- float dz = (float)Math.Sin(longitude) * dxz;
-
- Vector normal = new Vector(dx, dy, dz);
- Vector position = normal * radius;
-
- if (bla)
- {
- position += Vector.UnitZ * 0.5f * setLength;
- }
- else
- {
- position += down * 0.5f * setLength;
- }
-
- geometry.SetVertexData(pointIndex++,
- position,
- new Point(1.0f, 0.0f), setColor, normal, normal);
- }
- }
-
- // Finish with a single vertex at the top of the sphere.
- geometry.SetVertexData(pointIndex++,
- Vector.UnitZ * radius + Vector.UnitZ * 0.5f * setLength,
- new Point(0.0f, 1.0f), setColor, Vector.UnitZ, Vector.UnitZ);
-
- int index = 0;
- // Create a fan connecting the bottom vertex to the bottom latitude ring.
- for (int i = 0; i < horizontalSegments; i++)
- {
- geometry.Indices[index++] = (ushort)(1 + i);
- geometry.Indices[index++] = (ushort)(1 + (i + 1) % horizontalSegments);
- geometry.Indices[index++] = 0;
- }
-
- // Fill the sphere body with triangles joining each pair of latitude rings.
- for (int i = 0; i < verticalSegments - 2; i++)
- {
- for (int j = 0; j < horizontalSegments; j++)
- {
- int nextI = i + 1;
- int nextJ = (j + 1) % horizontalSegments;
-
- geometry.Indices[index++] =
- (ushort)(1 + nextI * horizontalSegments + j);
- geometry.Indices[index++] =
- (ushort)(1 + i * horizontalSegments + nextJ);
- geometry.Indices[index++] =
- (ushort)(1 + i * horizontalSegments + j);
-
- geometry.Indices[index++] =
- (ushort)(1 + nextI * horizontalSegments + j);
- geometry.Indices[index++] =
- (ushort)(1 + nextI * horizontalSegments + nextJ);
- geometry.Indices[index++] =
- (ushort)(1 + i * horizontalSegments + nextJ);
- }
- }
-
- // Create a fan connecting the top vertex to the top latitude ring.
- for (int i = 0; i < horizontalSegments; i++)
- {
- geometry.Indices[index++] =
- (ushort)(vertexCount - 2 - i);
- geometry.Indices[index++] =
- (ushort)(vertexCount - 2 - (i + 1) % horizontalSegments);
- geometry.Indices[index++] =
- (ushort)(vertexCount - 1);
- }
-
- return geometry;
- }
- #endregion
-
- #region Create (Static)
- /// <summary>
- /// Helper method to create a geometry out of position vectors, indices
- /// and a color for each position. Will use VertexFormat.Position3DColor.
- /// </summary>
- /// <param name="positions">3D positions for the new geometry.</param>
- /// <param name="colors">
- /// Colors for each of the positions, must match the length of the
- /// positions list.
- /// </param>
- /// <param name="indices">
- /// Indices to connect the triangles, must be at least 3 for one triangle
- /// or a multiple of 3 for more.
- /// </param>
- /// <returns>
- /// A newly created GeometryData, which can be passed to MeshData to be
- /// used for rendering.
- /// </returns>
- public static GeometryData Create(List<Vector> positions,
- List<Color> colors, List<ushort> indices)
- {
- if (colors.Count != positions.Count)
- {
- throw new ArgumentException(
- "The colors list length=" + colors.Count + " must match the " +
- "positions list length=" + positions.Count);
- }
- if (indices.Count < 3 ||
- indices.Count % 3 != 0)
- {
- throw new ArgumentException(
- "The indices list length=" + indices.Count + " must be at least 3 " +
- "for one triangle or a multiple of 3 for more.");
- }
-
- // And finally create the geometry out of the custom positions
- GeometryData geometry = new GeometryData(
- "<CustomColoredGeometry>", positions.Count,
- VertexFormat.Position3DColor, indices.Count, true, false);
-
- // Add all vertices from the positions
- for (int num = 0; num < positions.Count; num++)
- {
- // Note: Only position and color is used here, rest is ignored.
- geometry.SetVertexData(num, positions[num], Point.Zero, colors[num],
- Vector.UnitZ, Vector.UnitZ);
- }
- // Set the indices
- geometry.Indices = indices.ToArray();
-
- return geometry;
- }
- #endregion
-
- #region Create (Static)
- /// <summary>
- /// Helper method to create a geometry out of position vectors, indices
- /// and a uv for each position. Will use VertexFormat.Position3DTextured.
- /// </summary>
- /// <param name="positions">3D positions for the new geometry.</param>
- /// <param name="uvs">
- /// Texture uvs for each of the positions, must match the length of the
- /// positions list.
- /// </param>
- /// <param name="indices">
- /// Indices to connect the triangles, must be at least 3 for one triangle
- /// or a multiple of 3 for more.
- /// </param>
- /// <returns>
- /// A newly created GeometryData, which can be passed to MeshData to be
- /// used for rendering.
- /// </returns>
- public static GeometryData Create(List<Vector> positions, List<Point> uvs,
- List<ushort> indices)
- {
- if (uvs.Count != positions.Count)
- {
- throw new ArgumentException(
- "The uvs list length=" + uvs.Count + " must match the " +
- "positions list length=" + positions.Count);
- }
- if (indices.Count < 3 ||
- indices.Count % 3 != 0)
- {
- throw new ArgumentException(
- "The indices list length=" + indices.Count + " must be at least 3 " +
- "for one triangle or a multiple of 3 for more.");
- }
-
- // And finally create the geometry out of the custom positions
- GeometryData geometry = new GeometryData(
- "<CustomColoredGeometry>", positions.Count,
- VertexFormat.Position3DTextured, indices.Count, true, false);
-
- // Add all vertices from the positions
- for (int num = 0; num < positions.Count; num++)
- {
- // Note: Only position and uv is used here, rest is ignored.
- geometry.SetVertexData(num, positions[num], uvs[num], Color.White,
- Vector.UnitZ, Vector.UnitZ);
- }
- // Set the indices
- geometry.Indices = indices.ToArray();
-
- return geometry;
- }
- #endregion
-
- #region DefaultBox (Static)
- /// <summary>
- /// Default box (created on demand) with the size (1, 1). Usually using
- /// the default material and used to display fallback content if loading
- /// failed (e.g. if mesh content file was not found).
- /// </summary>
- public static GeometryData DefaultBox
- {
- get
- {
- if (defaultBoxMesh == null)
- {
- defaultBoxMesh = CreateCube(VertexFormat.Position3DTextured, 1);
- }
- return defaultBoxMesh;
- }
- }
- #endregion
-
- #region Name (Public)
- /// <summary>
- /// Name of the geometry, useful for debugging or identify warnings.
- /// </summary>
- public string Name
- {
- get;
- private set;
- }
- #endregion
-
- #region writer (Public)
- /// <summary>
- /// Writer to directly manipulate the vertex data (use Seek and Write).
- /// You can also just define or use an existing vertex struct and use
- /// SetData to store it into this GeometryData.
- /// </summary>
- public BinaryWriter writer;
- #endregion
-
- #region reader (Public)
- /// <summary>
- /// Reader to get data back. For debugging you could also use the Get
- /// methods defined below (GetPosition, etc.), but they are slower.
- /// </summary>
- public BinaryReader reader;
- #endregion
-
- #region MaxNumberOfVertices (Public)
- /// <summary>
- /// Number of vertices this VertexData currently can hold. We usually only
- /// increase this if the buffer needs to get bigger (e.g. via SetData or
- /// IncreaseVertices). Normally this should be set once in the constructor
- /// however (for static meshes we know beforehand)! This value is usually
- /// the same as NumberOfUsedVertices, but for dynamic vertex data (e.g.
- /// in DrawManager for lines or in MaterialManager for 2D UI vertices)
- /// the number of used vertices can be lower and increase over time!
- /// </summary>
- public int MaxNumberOfVertices
- {
- get;
- private set;
- }
- #endregion
-
- #region NumberOfUsedVertices (Public)
- /// <summary>
- /// Number of used vertices, must always be the same (for static meshes)
- /// or lower than MaxNumberOfVertices. For dynamic vertex data this can
- /// go up to more than MaxNumberOfVertices can handle, then just call
- /// IncreaseMaxVertices to increase MaxNumberOfVertices!
- /// </summary>
- public int NumberOfUsedVertices
- {
- get
- {
- return numberOfUsedVerticesInternal;
- }
- }
- #endregion
-
- #region Format (Public)
- /// <summary>
- /// Vertex element format as specified in the constructor. Everything
- /// else here depends on it, so changing this is not possible, create
- /// a new VertexData instance to convert vertex formats.
- /// </summary>
- public VertexFormat Format
- {
- get;
- private set;
- }
- #endregion
-
- #region VertexDataLengthInBytes (Public)
- /// <summary>
- /// Vertex data length for each of the vertices in bytes.
- /// </summary>
- public int VertexDataLengthInBytes
- {
- get;
- private set;
- }
- #endregion
-
- #region Indices (Public)
- /// <summary>
- /// And all the indices that link into the vertices. For 2D, UI or
- /// effect billboards these indices can be pre-calculated (e.g.
- /// 0, 1, 2, 0, 2, 3) and be used with whatever vertices are used.
- /// </summary>
- public ushort[] Indices;
- #endregion
-
- #region NumberOfUsedIndices (Public)
- /// <summary>
- /// Number of used indices, only used for dynamic vertex and index data.
- /// Note: Not in Indices because that struct only contains the indices
- /// arrays as unions.
- /// </summary>
- public int NumberOfUsedIndices;
- #endregion
-
-
- #region Bones (Public)
- /// <summary>
- /// Flat list of bones, the first bone is always the root bone, all
- /// children can be accessed from here. The main reason for having a flat
- /// list is easy access to all bones for showing bone previous and of
- /// course to quickly access all animation matrices.
- /// </summary>
- public BoneData[] Bones = new BoneData[0];
- #endregion
-
- #region IsStatic (Public)
- /// <summary>
- /// Is the data in this Mesh static or dynamic? Only dynamic data can
- /// be changed at runtime and will force a dynamic vertex buffer update.
- /// Always true for loaded content, sometimes false for generated content.
- /// </summary>
- public bool IsStatic
- {
- get;
- private set;
- }
- #endregion
-
- #region IsStaticAndLoadedFromContent (Public)
- /// <summary>
- /// For static geometry we can get rid of the vertices and indices as we
- /// have them now on the GPU. This saves memory and the content can be
- /// reloaded at any time too.
- /// </summary>
- public bool IsStaticAndLoadedFromContent
- {
- get
- {
- return IsStatic &&
- reloadContent != null;
- }
- }
- #endregion
-
- #region HasDataChanged (Public)
- /// <summary>
- /// This value is used to check if the data has changed for dynamic
- /// geometry objects. Only if this is true we actually need to update the
- /// native objects on the GPU (VBO and IBO), which is slow and should be
- /// avoided if nothing has changed (e.g. no new lines were added).
- /// </summary>
- public bool HasDataChanged = true;
- #endregion
-
- #region IsLineData (Public)
- /// <summary>
- /// Is line data in the vertex stream? Only used for dynamic mesh data
- /// for the DrawManager. Only used without indices.
- /// </summary>
- public bool IsLineData
- {
- get;
- private set;
- }
- #endregion
-
- #region IsSky (Public)
- /// <summary>
- /// Is sky
- /// </summary>
- public bool IsSky
- {
- get;
- private set;
- }
- #endregion
-
- #region PolygonCount (Public)
- /// <summary>
- /// Gets the number of polygons for this mesh (indices / 3)
- /// </summary>
- public int PolygonCount
- {
- get
- {
- return Indices.Length / 3;
- }
- }
- #endregion
-
- #region BoundingBox (Public)
- /// <summary>
- /// Get bounding box of all vertices (in whatever space they are). Only
- /// calculated once and also used for the BoundingSphere calculation.
- /// </summary>
- public BoundingBox BoundingBox
- {
- get
- {
- // Was this bounding box initialized before? Note: Normally it is
- // already initialized at creation time for all processed content (from
- // the content-pipeline), only code-generated won't be initialized.
- if (IsStatic &&
- (boundingBox.Max.X != 0 ||
- boundingBox.Max.Y != 0 ||
- boundingBox.Max.Z != 0))
- {
- // Then just return it.
- return boundingBox;
- }
-
- // Else just get all vertex positions to compute it
- Vector[] positions = new Vector[numberOfUsedVerticesInternal];
- for (int index = 0; index < numberOfUsedVerticesInternal; index++)
- {
- positions[index] = GetPosition(index);
- }
-
- // before we return it
- boundingBox = BoundingBox.Create(positions);
- return boundingBox;
- }
- }
- #endregion
-
- #region BoundingSphere (Public)
- /// <summary>
- /// Bounding radius
- /// </summary>
- public BoundingSphere BoundingSphere
- {
- get
- {
- // Only calculate bounding radius once.
- if (IsStatic == false ||
- sphere.Radius == 0.0f)
- {
- sphere = BoundingBox.ToBoundingSphere();
- }
- return sphere;
- }
- }
- #endregion
-
- #region CheckFirstPlaneId (Public)
- /// <summary>
- /// The ID of the plane that the box was outside last in the
- /// frustum check. Most of the time the frustum check will fail at the
- /// same plane again so checking that one first makes it faster.
- /// </summary>
- public byte CheckFirstPlaneId
- {
- get;
- set;
- }
- #endregion
-
- #region Private
-
- #region defaultBoxMesh (Private)
- /// <summary>
- /// Cached default box mesh data.
- /// </summary>
- private static GeometryData defaultBoxMesh;
- #endregion
-
- #region numberOfUsedVerticesInternal (Private)
- /// <summary>
- /// Optimization to make the NumberOfUsedVertices property as fast as
- /// possible, especially in debug mode and when profiling the property
- /// set slows us down.
- /// </summary>
- private int numberOfUsedVerticesInternal;
- #endregion
-
- #region boundingBox (Private)
- /// <summary>
- /// Box
- /// </summary>
- private BoundingBox boundingBox;
- #endregion
-
- #region sphere (Private)
- /// <summary>
- /// Sphere
- /// </summary>
- private BoundingSphere sphere = new BoundingSphere(Vector.Zero, 0.0f);
- #endregion
-
- #region stream (Private)
- /// <summary>
- /// Data stream for all the data.
- /// </summary>
- private MemoryStream stream;
- #endregion
-
- #region reloadContent (Private)
- /// <summary>
- /// Helper delegate to reload this geometry content after it has been
- /// disposed. Usually used for static geometry, which can be disposed
- /// after the GPU VBO and IBO have been created to save memory.
- /// </summary>
- private readonly RunDelegate reloadContent;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create geometry data with a predefined number of vertices. This way all
- /// the vertex data is already allocated and future writes to this vertex
- /// data are fast. Use the writer directly to write up to 2000 vertices,
- /// for more use the SetData method below.
- /// Note: When rendering this is always send to the native vertex buffer
- /// (e.g. in Xna DynamicVertexBuffer is used). The index buffer is not used
- /// if setNumberOfIndices is 0, else you must fill the indices yourself
- /// after filling in the vertices.
- /// </summary>
- /// <param name="setName">
- /// Set name for this geometry for identification. Should start with >
- /// for dynamically created geometry to not confuse it with loaded gometry
- /// from content, which uses content names.
- /// </param>
- /// <param name="setMaxNumberOfVertices">
- /// Set maximum number of vertices to be used, can be increased if the
- /// geometry is dynamic (not static).
- /// </param>
- /// <param name="setVertexFormat">
- /// Set vertex format (usually from a shader).
- /// </param>
- /// <param name="setNumberOfIndices">Set number of indices</param>
- /// <param name="setIsStaticData">
- /// Is static data? Faster to render because does not need to be modified.
- /// </param>
- /// <param name="setIsLineData">
- /// Set if line data is used in this vertex stream. Only used for dynamic
- /// mesh data for the DrawManager. Only used without indices.
- /// </param>
- public GeometryData(string setName, int setMaxNumberOfVertices,
- VertexFormat setVertexFormat, int setNumberOfIndices,
- bool setIsStaticData, bool setIsLineData)
- : this(setName, setMaxNumberOfVertices, setVertexFormat,
- setNumberOfIndices, setIsStaticData, setIsLineData,
- new BoundingBox(Vector.Zero, Vector.Zero))
- {
- }
-
- /// <summary>
- /// Create geometry data with a predefined number of vertices. This way all
- /// the vertex data is already allocated and future writes to this vertex
- /// data are fast. Use the writer directly to write up to 2000 vertices,
- /// for more use the SetData method below.
- /// Note: When rendering this is always send to the native vertex buffer
- /// (e.g. in Xna DynamicVertexBuffer is used). The index buffer is not used
- /// if setNumberOfIndices is 0, else you must fill the indices yourself
- /// after filling in the vertices.
- /// </summary>
- /// <param name="setName">
- /// Set name for this geometry for identification. Should start with >
- /// for dynamically created geometry to not confuse it with loaded gometry
- /// from content, which uses content names.
- /// </param>
- /// <param name="setMaxNumberOfVertices">
- /// Set maximum number of vertices to be used, can be increased if the
- /// geometry is dynamic (not static).
- /// </param>
- /// <param name="setVertexFormat">
- /// Set vertex format (usually from a shader).
- /// </param>
- /// <param name="setNumberOfIndices">Set number of indices</param>
- /// <param name="setIsStaticData">
- /// Is static data? Faster to render because does not need to be modified.
- /// </param>
- /// <param name="setIsLineData">
- /// Set if line data is used in this vertex stream. Only used for dynamic
- /// mesh data for the DrawManager. Only used without indices.
- /// </param>
- /// <param name="setBoundingBox">
- /// Set bounding box with given data, useful for physics and displaying
- /// the boundings in debug mode. For content this is usually calculated
- /// already in the content system, for generated geometry it will not be
- /// set usually and will be calculated automatically when first needed.
- /// </param>
- public GeometryData(string setName, int setMaxNumberOfVertices,
- VertexFormat setVertexFormat, int setNumberOfIndices,
- bool setIsStaticData, bool setIsLineData, BoundingBox setBoundingBox)
- {
- Name = setName;
- IsStatic = setIsStaticData;
- IsLineData = setIsLineData;
- // Both the Skydome model and the generated sky box contain "Sky"
- IsSky = Name.Contains("Sky");
-
- Format = setVertexFormat;
- VertexDataLengthInBytes = Format.LengthInBytes;
- MaxNumberOfVertices = setMaxNumberOfVertices;
- // Dynamic vertex data is empty by default
- numberOfUsedVerticesInternal =
- setIsStaticData
- ? MaxNumberOfVertices
- : 0;
-
- stream = new MemoryStream(MaxNumberOfVertices * VertexDataLengthInBytes);
- stream.SetLength(stream.Capacity);
- writer = new BinaryWriter(stream);
- reader = new BinaryReader(stream);
-
- Indices = new ushort[setNumberOfIndices];
- NumberOfUsedIndices =
- setIsStaticData
- ? setNumberOfIndices
- : 0;
-
- boundingBox = setBoundingBox;
- }
-
- /// <summary>
- /// Create geometry data from a binary stream (e.g. as part of ModelData).
- /// This constructor requires the binary stream data and a way to reload
- /// the content when Dispose was called. This is used for static geometry
- /// to save memory and this constructor is always used for static geometry.
- /// </summary>
- /// <param name="dataReader">
- /// The data reader which contains the stored content data internally.
- /// </param>
- /// <param name="setReloadContent">
- /// The delegate which contains the code for reloading the content.
- /// </param>
- public GeometryData(BinaryReader dataReader, RunDelegate setReloadContent)
- {
- // Loaded data is always static
- IsStatic = true;
- IsLineData = false;
- reloadContent = setReloadContent;
- // All data is filled in Load
- Load(dataReader);
- // Both the Skydome model and the generated sky box contain "Sky"
- IsSky = Name.Contains("Sky");
- }
- #endregion
-
- #region IDisposable Members
- /// <summary>
- /// Dispose all internal data (vertices and indices). The lists are still
- /// valid, just empty. This is mainly used for static geometry data, which
- /// can be disposed after the native objects have been created.
- /// </summary>
- public void Dispose()
- {
- if (reader != null)
- {
- reader.Dispose();
- reader = null;
- }
- if (writer != null)
- {
- writer.Dispose();
- writer = null;
- }
- if (stream != null)
- {
- stream.Dispose();
- stream = null;
- }
- Indices = new ushort[0];
- numberOfUsedVerticesInternal = 0;
- // Note: Do not touch NumberOfUsedIndices or MaxNumberOfVertices as
- // these are still used even for disposed geometry data objects to
- // perform different rendering techniques.
- }
- #endregion
-
- #region ISaveLoadBinary Members
- /// <summary>
- /// Load the mesh data, the vertices, indices and the material used here.
- /// </summary>
- /// <param name="dataReader">The data reader.</param>
- public void Load(BinaryReader dataReader)
- {
- // We currently only support our version, if more versions are added,
- // we need to do different loading code depending on the version here.
- int version = dataReader.ReadInt32();
- switch (version)
- {
- case 1:
- LoadContentVersion1(dataReader);
- break;
-
- case 2:
- LoadContentVersion1(dataReader);
- Bones = new BoneData[dataReader.ReadInt32()];
- for (int index = 0; index < Bones.Length; index++)
- {
- Bones[index] = new BoneData(dataReader);
- }
- break;
-
- default:
- Log.InvalidVersionWarning(GetType().Name, version, VersionNumber);
- return;
- }
- }
-
- /// <summary>
- /// Save the geometry data, which consists of the vertices, indices and
- /// the MaterialId.
- /// </summary>
- /// <param name="dataWriter">The data writer.</param>
- public void Save(BinaryWriter dataWriter)
- {
- dataWriter.Write(VersionNumber);
-
- // Save out the name
- dataWriter.Write(Name);
- // the number of vertices and their vertex format first
- dataWriter.Write(NumberOfUsedVertices);
- Format.Save(dataWriter);
- dataWriter.Write(VertexDataLengthInBytes);
- // And then just save out the whole buffer
- byte[] byteData = stream.GetBuffer();
- //dataWriter.Write(byteData.Length);
- int usedBytes = VertexDataLengthInBytes * NumberOfUsedVertices;
- dataWriter.Write(usedBytes);
- dataWriter.Write(byteData, 0, usedBytes);
-
- // Instead of writing out each index, just convert to a byte array
- // and save it that way. The Buffer.BlockCopy creates additional memory,
- // but otherwise the copy is fast and writing out the data is much
- // faster than going through each index by hand.
- dataWriter.Write(NumberOfUsedIndices);
- byteData = new byte[NumberOfUsedIndices * 2];
- Buffer.BlockCopy(Indices, 0, byteData, 0, byteData.Length);
- dataWriter.Write(byteData);
-
- // Version 2 stuff
- dataWriter.Write(Bones.Length);
- foreach (BoneData bone in Bones)
- {
- bone.Save(dataWriter);
- }
- }
- #endregion
-
- #region AddNumberOfUsedVertices (Public)
- /// <summary>
- /// Add number of used vertices and if we reach the MaxNumberOfVertices
- /// limit, it is increased by a factor of 2. Used for dynamic vertex data.
- /// </summary>
- /// <param name="numberOfNewVerticesWeWantToAdd">
- /// Number of vertices we want to add (usually 4 for one quad)
- /// </param>
- public void AddNumberOfUsedVertices(int numberOfNewVerticesWeWantToAdd)
- {
- numberOfUsedVerticesInternal += numberOfNewVerticesWeWantToAdd;
- while (numberOfUsedVerticesInternal > MaxNumberOfVertices)
- {
- // Make sure we have a useful increase number, increasing 0 won't help!
- if (MaxNumberOfVertices < 2)
- {
- Log.Warning(
- "You are trying to add less than 2 vertices, which makes no " +
- "sense, each line needs 2 vertices, polygons need 3 vertices " +
- "make no sense anyway (we should allocate more). Increasing to " +
- "2!");
- MaxNumberOfVertices = 2;
- }
-
- // Increase number of vertices. This allows the attached reader and
- // writer to read and write more data. SetData does the same, but we
- // need a struct for it, using a reader and writer is actually faster!
- MaxNumberOfVertices += MaxNumberOfVertices;
-
- // Note: Do not touch NumberOfUsedVertices, this is only used for
- // dynamic vertex data.
- stream.SetLength(MaxNumberOfVertices * VertexDataLengthInBytes);
- }
- }
- #endregion
-
- #region AddNumberOfUsedIndices (Public)
- /// <summary>
- /// Add number of used indices and if we reach Length, it is increased by
- /// a factor of 2. Used for dynamic vertex and index data.
- /// </summary>
- /// <param name="numberOfNewIndicesWeWantToAdd">
- /// Number of indices we want to add (usually 6 for one quad)
- /// </param>
- public void AddNumberOfUsedIndices(int numberOfNewIndicesWeWantToAdd)
- {
-
- NumberOfUsedIndices += numberOfNewIndicesWeWantToAdd;
- // Increase index buffer (only needed very few times)
- while (NumberOfUsedIndices > Indices.Length)
- {
- // Increase number of indices by a factor of 2. If we started with 0
- // or have disposed Indices, then use 6, which is the default increase.
- ResizeIndices(
- Indices.Length <= 3
- ? 6
- : Indices.Length * 2);
- }
- }
- #endregion
-
- #region AddNumberOfUsedVerticesFast (Public)
- /// <summary>
- /// Add number of used vertices fast method, will not perform any checking
- /// and will not increase any buffers, the caller has to make sure that
- /// nothing changes and the new data fits!
- /// </summary>
- /// <param name="newVertices">The number of new vertices.</param>
- public void AddNumberOfUsedVerticesFast(int newVertices)
- {
- numberOfUsedVerticesInternal += newVertices;
- }
- #endregion
-
- #region AddNumberOfUsedVerticesAndIndicesFast (Public)
- /// <summary>
- /// Add number of used vertices and indices fast method, will not perform
- /// any checking and will not increase any buffers, the caller has to make
- /// sure that nothing changes and the new data fits!
- /// </summary>
- /// <param name="newVertices">The number of new vertices.</param>
- /// <param name="newIndices">The number of new indices.</param>
- public void AddNumberOfUsedVerticesAndIndicesFast(int newVertices,
- int newIndices)
- {
- numberOfUsedVerticesInternal += newVertices;
- NumberOfUsedIndices += newIndices;
- }
- #endregion
-
- #region ResizeIndices (Public)
- /// <summary>
- /// Resize indices array, all indices will be copied to the new array.
- /// </summary>
- /// <param name="newSize">The new size of the index buffer.</param>
- public void ResizeIndices(int newSize)
- {
- if (newSize < Indices.Length)
- {
- throw new ArgumentOutOfRangeException(
- "newSize=" + newSize + " must be bigger than existing indices " +
- "length=" + Indices.Length);
- }
-
- // Warn if we try to add anything below 3, which makes no sense, each
- // triangle needs 3 indices, and lines with just 2 indices make no sense
- // anyway (we should allocate more).
- if (newSize < 3)
- {
- Log.Warning(
- "You are trying to add less than 3 indices, which makes no sense, " +
- "each triangle needs 3 indices, and lines with just 2 indices " +
- "make no sense anyway (we should allocate more). Increasing to 3!");
- newSize = 3;
- }
-
- ushort[] newIndices = new ushort[newSize];
- // We need to copy over existing data (slow, but rarely needed)
- for (int num = 0; num < Indices.Length; num++)
- {
- newIndices[num] = Indices[num];
- }
- // And now start using the new array
- Indices = newIndices;
- }
- #endregion
-
- #region ResetUsedVerticesAndIndices (Public)
- /// <summary>
- /// Reset used vertices and indices, used to reset NumberOfUsedVertices
- /// and NumberOfUsedIndices, which should be increased again next frame
- /// with the AddNumberOfUsedVertices and AddNumberOfUsedIndices methods.
- /// If we have no active vertices (NumberOfUsedVertices is 0) there is no
- /// reason to render or update anything (often the case, e.g. no lines).
- /// Only used for dynamic geometry.
- /// </summary>
- public void ResetUsedVerticesAndIndices()
- {
- numberOfUsedVerticesInternal = 0;
- stream.Seek(0, SeekOrigin.Begin);
- NumberOfUsedIndices = 0;
- // If no data has changed, we can skip updating geometry, which speeds
- // up dynamic geometries a lot.
- HasDataChanged = false;
- }
- #endregion
-
- #region SetVertexData (Public)
- /// <summary>
- /// Set vertex data helper method to directly set vertex data without
- /// knowing the actual vertex format. This method and all its overloads
- /// have quite some overhead and should only be used for generating
- /// fallback content (simplifies setting vertices a lot at the cost of
- /// performance).
- /// </summary>
- /// <param name="vertexIndex">Index of the vertex.</param>
- /// <param name="position">The position.</param>
- /// <param name="uv">The uv.</param>
- public void SetVertexData(int vertexIndex, Vector position, Point uv)
- {
- SetVertexData(vertexIndex, position, uv, Color.White, Vector.Zero,
- Vector.Zero);
- }
-
- /// <summary>
- /// Set vertex data helper method to directly set vertex data without
- /// knowing the actual vertex format. This method and all its overloads
- /// have quite some overhead and should only be used for generating
- /// fallback content (simplifies setting vertices a lot at the cost of
- /// performance).
- /// </summary>
- /// <param name="vertexIndex">Index of the vertex.</param>
- /// <param name="position">The position.</param>
- /// <param name="uv">The uv.</param>
- /// <param name="color">The color.</param>
- /// <param name="normal">The normal.</param>
- /// <param name="tangent">The tangent.</param>
- public void SetVertexData(int vertexIndex, Vector position, Point uv,
- Color color, Vector normal, Vector tangent)
- {
- SetVertexData(vertexIndex, position, uv, color, normal, tangent,
- // We got no data for skinning, just save out 0
- Point.Zero, Point.Zero);
- }
-
- /// <summary>
- /// Set vertex data helper method to directly set vertex data without
- /// knowing the actual vertex format. This method and all its overloads
- /// have quite some overhead and should only be used for generating
- /// fallback content (simplifies setting vertices a lot at the cost of
- /// performance). Normally you should write your own code calling SaveData
- /// for each format element.
- /// </summary>
- /// <param name="vertexIndex">Index of the vertex</param>
- /// <param name="position">The position for this vertex</param>
- /// <param name="uv">UV for the diffuse channel</param>
- /// <param name="color">Vertex color</param>
- /// <param name="normal">Normal vector</param>
- /// <param name="tangent">Tangent vector</param>
- /// <param name="secondUv">
- /// Second uv channel data (lightmap or extra uv).
- /// </param>
- public void SetVertexData(int vertexIndex, Vector position, Point uv,
- Point secondUv, Color color, Vector normal, Vector tangent)
- {
- if (vertexIndex < 0 ||
- vertexIndex >= MaxNumberOfVertices)
- {
- throw new IndexOutOfRangeException("vertexIndex=" + vertexIndex +
- " is invalid, we only have " +
- MaxNumberOfVertices + " vertices!");
- }
-
- // First go to the start position for this vertex
- stream.Seek(vertexIndex * VertexDataLengthInBytes, SeekOrigin.Begin);
-
- // We have to go through all possible vertex elements
- for (int num = 0; num < Format.Elements.Length; num++)
- {
- switch (Format.Elements[num].Type)
- {
- case VertexElementType.Position2D:
- case VertexElementType.Position3D:
- Format.Elements[num].SaveData(writer, position);
- break;
- case VertexElementType.Normal:
- Format.Elements[num].SaveData(writer, normal);
- break;
- case VertexElementType.Tangent:
- Format.Elements[num].SaveData(writer, tangent);
- break;
- case VertexElementType.Binormal:
- // For non-mirrored geometry we can easily create the binormals
- // ourselves.
- Format.Elements[num].SaveData(writer,
- Vector.Cross(tangent, normal));
- break;
- case VertexElementType.Color:
- Format.Elements[num].SaveData(writer, color);
- break;
- case VertexElementType.TextureUV:
- Format.Elements[num].SaveData(writer, uv);
- break;
- case VertexElementType.LightMapUV:
- case VertexElementType.ExtraUV:
- Format.Elements[num].SaveData(writer, secondUv);
- break;
- case VertexElementType.TextureUVW:
- // Also use normal for UVW (is [-1, +1], we convert to [0, 1])
- Format.Elements[num].SaveData(writer,
- (normal + Vector.One) / 2.0f);
- break;
-
- default:
- // Unsupported vertex type
- throw new NotSupportedException("Invalid vertex type for " +
- "SetVertexData with color: " +
- Format.Elements[num].Type);
- }
- }
- }
-
- /// <summary>
- /// Set vertex data helper method to directly set vertex data without
- /// knowing the actual vertex format. This method and all its overloads
- /// have quite some overhead and should only be used for generating
- /// fallback content (simplifies setting vertices a lot at the cost of
- /// performance). Normally you should write your own code calling SaveData
- /// for each format element.
- /// </summary>
- /// <param name="vertexIndex">Index of the vertex.</param>
- /// <param name="position">The position.</param>
- /// <param name="uv">The uv.</param>
- /// <param name="color">The color.</param>
- /// <param name="normal">The normal.</param>
- /// <param name="tangent">The tangent.</param>
- /// <param name="skinWeights">The skin weights.</param>
- /// <param name="skinIndices">The skin indices.</param>
- public void SetVertexData(int vertexIndex, Vector position, Point uv,
- Color color, Vector normal, Vector tangent, Point skinWeights,
- Point skinIndices)
- {
- if (vertexIndex < 0 ||
- vertexIndex >= MaxNumberOfVertices)
- {
- throw new IndexOutOfRangeException("vertexIndex=" + vertexIndex +
- " is invalid, we only have " +
- MaxNumberOfVertices + " vertices!");
- }
-
- // First go to the start position for this vertex
- stream.Seek(vertexIndex * VertexDataLengthInBytes, SeekOrigin.Begin);
-
- // We have to go through all possible vertex elements
- for (int num = 0; num < Format.Elements.Length; num++)
- {
- switch (Format.Elements[num].Type)
- {
- case VertexElementType.Position2D:
- case VertexElementType.Position3D:
- Format.Elements[num].SaveData(writer, position);
- break;
- case VertexElementType.Normal:
- Format.Elements[num].SaveData(writer, normal);
- break;
- case VertexElementType.Tangent:
- Format.Elements[num].SaveData(writer, tangent);
- break;
- case VertexElementType.Binormal:
- // For non-mirrored geometry we can easily create the binormals
- // ourselves.
- Format.Elements[num].SaveData(writer,
- Vector.Cross(tangent, normal));
- break;
- case VertexElementType.Color:
- Format.Elements[num].SaveData(writer, color);
- break;
- case VertexElementType.TextureUV:
- case VertexElementType.LightMapUV:
- case VertexElementType.ExtraUV:
- Format.Elements[num].SaveData(writer, uv);
- break;
- case VertexElementType.TextureUVW:
- // Also use normal for UVW (is [-1, +1], we convert to [0, 1])
- Format.Elements[num].SaveData(writer,
- (normal + Vector.One) / 2.0f);
- break;
-
- case VertexElementType.SkinIndices:
- Format.Elements[num].SaveData(writer, skinIndices);
- break;
- case VertexElementType.SkinWeights:
- Format.Elements[num].SaveData(writer, skinWeights);
- break;
-
- default: