/Utilities/Graphics/VertexElement.cs
C# | 851 lines | 544 code | 77 blank | 230 comment | 18 complexity | 63f2bedd4ebfe2c895757cf38c8d44ff MD5 | raw file
Possible License(s): Apache-2.0
- using System;
- using System.IO;
- using Delta.Utilities.Datatypes;
- using NUnit.Framework;
-
- namespace Delta.Utilities.Graphics
- {
- /// <summary>
- /// Vertex element, defines the type of a vertex element, if it is
- /// compressed and the size occupied. See VertexFormat for details.
- /// </summary>
- public struct VertexElement : ISaveLoadBinary
- {
- #region Constants
- /// <summary>
- /// The max. distance (in meters) that a vertex may be distant from the
- /// coordinate origin (because of mobile vertex compression). When
- /// importing 3D models this is checked and a warning will be shown if a
- /// vertex is farther away from the origin than 300m.
- /// See Position3DCompressionValue for the formula.
- /// </summary>
- public const float MaxCompressedVertexDistance =
- //this is a good value, big and nice, but does not help for Soulcraft: 300.0f;
- // Soulcraft has animated models with -1100 to +1500 units, even with centering we
- // would need -1200 to +1200 to make the crappy animated files work (we cannot
- // center however as animated matrices do not know about it).
- 1600.0f;
-
- //300.0f;
- //soulcraft tech demo camera fix: 267//75//64//127
-
- /// <summary>
- /// When converting vertex positions to shorts to store them into
- /// Position3DCompressed this formula is used (1/436.0f). The minimum
- /// resulting 3D position is -75.0f and the maximum is 75.0f. Everything
- /// beyond that cannot be represented as Position3DCompressed shorts, but
- /// since most models are only a few meters in size this should be no
- /// problem.
- /// </summary>
- public const float Position3DCompressionValue =
- (short.MaxValue) / MaxCompressedVertexDistance;
-
- //for 267: 122.72, for 75: 436.0f;//for 64: 512.0f;
-
- /// <summary>
- /// When converting vertex positions to shorts to store them into
- /// Position2DCompressed this formula is used (1/16384). The minimum
- /// valid value for shorts is -32767, divided through this constant this
- /// is -2.0f, the maximum value is 32768, which is +2.0f.
- /// </summary>
- public const float Position2DCompressionValue = (short.MaxValue / 2);
-
- /// <summary>
- /// For TextureUVCompressed we just use a simple 1/32767 formula, since
- /// we will only accept UVs between 0 and 1 anyway. This precision is also
- /// good for the bigger atlas textures we might have (up to 2k/2k).
- /// </summary>
- public const float ShortCompressionValue = short.MaxValue;
-
- /// <summary>
- /// For normals, tangents and binormals (colors are not normalized, they
- /// won't need any compression, we can just use the bytes values from
- /// our Color struct). So only for normalized values we use a simple
- /// compression formula: value * 127
- /// Decompress formula: value / 127
- /// The formula is only for values between -1 and +1.
- /// </summary>
- private const float SignedByteCompressionValue = sbyte.MaxValue;
-
- /// <summary>
- /// OpenGL attributes are used for the shader generation and for getting
- /// of the attributes to set them.
- /// </summary>
- public const string OpenGLPositionAttribute = "vPosition";
-
- /// <summary>
- /// Open GL tex coord attribute
- /// </summary>
- public const string OpenGLTexCoordAttribute = "vTexCoord";
-
- /// <summary>
- /// Open GL normal attribute
- /// </summary>
- public const string OpenGLNormalAttribute = "vNormal";
-
- /// <summary>
- /// Open GL tangent attribute
- /// </summary>
- public const string OpenGLTangentAttribute = "vTangent";
-
- public const string OpenGLBinormalAttribute = "vBinormal";
-
- public const string OpenGLColorAttribute = "vColor";
-
- public const string OpenGLLightMapTexCoordAttribute = "vLightTexCoord";
-
- public const string OpenGLExtraTexCoordAttribute = "vExtraTexCoord";
-
- public const string OpenGLSkinWeightsAttribute = "vSkinWeights";
-
- public const string OpenGLSkinIndicesAttribute = "vSkinIndices";
- #endregion
-
- #region Type (Public)
- /// <summary>
- /// Type for this vertex element (position, uv, etc.)
- /// </summary>
- public VertexElementType Type
- {
- get;
- private set;
- }
- #endregion
-
- #region IsCompressed (Public)
- /// <summary>
- /// like color and skin data are already compressed and cannot be more
- /// compact. Most VertexFormats like Position2DTexture really get much
- /// smaller with compressed on (down to 10 bytes from 20 uncompressed).
- /// </summary>
- public bool IsCompressed
- {
- get;
- private set;
- }
- #endregion
-
- #region Size (Public)
- /// <summary>
- /// Size in bytes of this vertex element. Used to find the offset for the
- /// next element mostly and for the total vertex size in bytes of course.
- /// </summary>
- public int Size
- {
- get;
- private set;
- }
- #endregion
-
- #region Offset (Public)
- /// <summary>
- /// Offset from the start of the vertex data to this element in bytes.
- /// Calculated in VertexFormat constructor.
- /// </summary>
- public int Offset
- {
- get;
- internal set;
- }
- #endregion
-
- #region ComponentCount (Public)
- /// <summary>
- /// Returns the number of components of that element.
- /// E.g. Color = 4, Vector (Position, Normal) = 3, Point (UV) = 2.
- /// </summary>
- public int ComponentCount
- {
- get;
- private set;
- }
- #endregion
-
- #region IsNormalized (Public)
- /// <summary>
- /// Is normalized? Currently used for compressed Texture UVs, Normals,
- /// Tangents, Color and SkinWeights. This is used to save bandwidth
- /// because compressed vertex data is smaller, but needs to be normalized
- /// usually (except when we account for it in the shader like for
- /// position vertex data via the World or WorldViewProj matrices).
- /// </summary>
- public bool IsNormalized
- {
- get;
- private set;
- }
- #endregion
-
- #region IsByteFormat (Public)
- /// <summary>
- /// Is byte format? Currently only used for colors. Use IsSignedByteFormat
- /// for normals, tangents and binormals.
- /// </summary>
- public bool IsByteFormat
- {
- get;
- private set;
- }
- #endregion
-
- #region IsSignedByteFormat (Public)
- /// <summary>
- /// Is signed byte format? Only used for normals, tangents, binormals.
- /// Colors use IsByteFormat (unsigned).
- /// </summary>
- public bool IsSignedByteFormat
- {
- get;
- private set;
- }
- #endregion
-
- #region IsShortFormat (Public)
- /// <summary>
- /// Is short format? Used mostly for compressed vertex data. If this
- /// and IsByteFormat are false the vertex element components are floats!
- /// </summary>
- public bool IsShortFormat
- {
- get;
- private set;
- }
- #endregion
-
- #region Constructors
- /// <summary>
- /// for vertex elements to save bandwidth and make rendering much faster.
- /// </summary>
- public VertexElement(VertexElementType elementType)
- : this()
- {
- Type = elementType;
- IsCompressed = false;
- SetupProperties();
- }
-
- /// <summary>
- /// Create vertex element, this constructor forces compression if wanted.
- /// This is usually controlled by the content and build system on the
- /// improve rendering performance).
- /// </summary>
- public VertexElement(VertexElementType elementType, bool setIsCompressed)
- : this()
- {
- Type = elementType;
- IsCompressed = setIsCompressed;
- SetupProperties();
- }
- #endregion
-
- #region ISaveLoadBinary Members
- /// <summary>
- /// Load
- /// </summary>
- public void Load(BinaryReader reader)
- {
- Type = (VertexElementType)reader.ReadInt32();
- IsCompressed = reader.ReadBoolean();
- Size = reader.ReadInt32();
- Offset = reader.ReadInt32();
- SetupProperties();
- }
-
- /// <summary>
- /// Save
- /// </summary>
- public void Save(BinaryWriter writer)
- {
- writer.Write((int)Type);
- writer.Write(IsCompressed);
- writer.Write(Size);
- writer.Write(Offset);
- }
- #endregion
-
- #region LoadData (Public)
- /// <summary>
- /// Extract value as a Vector, not very efficient, but always works :)
- /// </summary>
- /// <param name="reader">Reader</param>
- public Vector LoadData(BinaryReader reader)
- {
- // We have to check each type and if the data is compressed
- switch (Type)
- {
- case VertexElementType.Position2D:
- return (IsCompressed)
- ? // If data is compressed, decompress it
- new Vector(
- Position2DDecompress(reader.ReadInt16()),
- Position2DDecompress(reader.ReadInt16()), 0.0f)
- : // else we can just take uncompressed data
- new Vector(new Point(reader), 0.0f);
-
- case VertexElementType.Position3D:
- if (IsCompressed)
- {
- // If data is compressed, decompress it
- Vector vec = new Vector(
- Position3DDecompress(reader.ReadInt16()),
- Position3DDecompress(reader.ReadInt16()),
- Position3DDecompress(reader.ReadInt16()));
- // Since we write 2 extra bytes to the stream,
- // should we also read 2 extra bytes? Check SaveData.
- reader.ReadInt16();
- return vec;
- }
- else
- {
- // else we can just take uncompressed data
- return new Vector(reader);
- }
-
- case VertexElementType.Normal:
- case VertexElementType.Tangent:
- case VertexElementType.Binormal:
- case VertexElementType.TextureUVW:
- if (IsCompressed)
- {
- // Read three bytes for X, Y, Z
- Vector vec = new Vector(
- SignedByteDecompress(reader.ReadSByte()),
- SignedByteDecompress(reader.ReadSByte()),
- SignedByteDecompress(reader.ReadSByte()));
-
- // Read an extra byte.
- reader.ReadSByte();
-
- return vec;
- }
- else
- {
- return new Vector(reader);
- }
-
- case VertexElementType.TextureUV:
- case VertexElementType.LightMapUV:
- case VertexElementType.ExtraUV:
- return (IsCompressed)
- ? // If data is compressed, decompress it
- new Vector(
- ShortDecompress(reader.ReadInt16()),
- ShortDecompress(reader.ReadInt16()), 0.0f)
- : // else we can just take uncompressed data
- new Vector(new Point(reader), 0.0f);
-
- case VertexElementType.Color:
- // Color data is always compressed (4 bytes), but we only return a
- // vector here, so only return RGB.
- Color color = new Color(reader);
- return new Vector(color.R, color.G, color.B);
-
- case VertexElementType.SkinIndices:
- // Skinned data is always compressed (2 shorts)
- return new Vector(
- // Use the short data directly, no conversion or normalization.
- reader.ReadUInt16(),
- reader.ReadUInt16(), 0.0f);
- case VertexElementType.SkinWeights:
- // Skinned data is always compressed (2 shorts)
- return new Vector(
- ShortDecompress(reader.ReadInt16()),
- ShortDecompress(reader.ReadInt16()), 0.0f);
-
- default:
- // Unsupported vertex type
- throw new NotSupportedException("Invalid vertex type: " + Type);
- }
- }
- #endregion
-
- #region LoadDataAsPoint (Public)
- /// <summary>
- /// Extract value as a Point, not very efficient, but always works :)
- /// </summary>
- /// <param name="reader">Reader</param>
- public Point LoadDataAsPoint(BinaryReader reader)
- {
- // We have to check each type and if the data is compressed
- switch (Type)
- {
- case VertexElementType.Position2D:
- // If data is compressed, decompress it, else take uncompressed data
- return
- IsCompressed
- ? new Point(
- Position2DDecompress(reader.ReadInt16()),
- Position2DDecompress(reader.ReadInt16()))
- : new Point(reader);
-
- case VertexElementType.TextureUV:
- case VertexElementType.LightMapUV:
- case VertexElementType.ExtraUV:
- // If data is compressed, decompress it, else take uncompressed data
- return
- IsCompressed
- ? new Point(
- ShortDecompress(reader.ReadInt16()),
- ShortDecompress(reader.ReadInt16()))
- : new Point(reader);
-
- case VertexElementType.SkinIndices:
- // Skinned data is always compressed (2 shorts)
- return new Point(
- // Use the short data directly, no conversion or normalization.
- reader.ReadUInt16(),
- reader.ReadUInt16());
- case VertexElementType.SkinWeights:
- // Skinned data is always compressed (2 shorts)
- return new Point(
- ShortDecompress(reader.ReadInt16()),
- ShortDecompress(reader.ReadInt16()));
-
- default:
- // Unsupported vertex type
- Log.Warning("Invalid vertex type: " + Type);
- return Point.Zero;
- }
- }
- #endregion
-
- #region SaveData (Public)
- /// <summary>
- /// Save data helper method for GeometryData.SetVertexData, which is slow,
- /// but still helpful for debugging and creating geometry dynamically.
- /// </summary>
- public void SaveData(BinaryWriter writer, Vector data)
- {
- // We have to check each type and if the data is compressed
- switch (Type)
- {
- case VertexElementType.Position2D:
- if (IsCompressed)
- {
- // Write data compressed
- writer.Write(Position2DCompress(data.X));
- writer.Write(Position2DCompress(data.Y));
- }
- else
- {
- data.ToPoint().Save(writer);
- }
- break;
-
- case VertexElementType.Position3D:
- if (IsCompressed)
- {
- // Write data compressed
- writer.Write(Position3DCompress(data.X));
- writer.Write(Position3DCompress(data.Y));
- writer.Write(Position3DCompress(data.Z));
-
- // Writing 2 extra bytes to the stream to make it 8 bytes long
- // instead of 6, it is needed for the compression to work with
- // XNA, but also very good for OpenGL and DirectX (more optimized
- // to be 4 byte aligned like everything else).
- writer.Write((short)0);
- }
- else
- {
- data.Save(writer);
- }
- break;
-
- case VertexElementType.Normal:
- case VertexElementType.Tangent:
- case VertexElementType.Binormal:
- case VertexElementType.TextureUVW:
- if (IsCompressed)
- {
- // Write data compressed
- writer.Write(SignedByteCompress(data.X));
- writer.Write(SignedByteCompress(data.Y));
- writer.Write(SignedByteCompress(data.Z));
-
- // Writing 1 extra bytes to the stream to make it 4 bytes long
- // instead of 3, it's needed for the compression to work with XNA.
- writer.Write((sbyte)0);
- }
- else
- {
- data.Save(writer);
- }
- break;
-
- case VertexElementType.TextureUV:
- case VertexElementType.LightMapUV:
- case VertexElementType.ExtraUV:
- if (IsCompressed)
- {
- // Write data compressed
- writer.Write(ShortCompress(data.X));
- writer.Write(ShortCompress(data.Y));
- }
- else
- {
- data.ToPoint().Save(writer);
- }
- break;
-
- case VertexElementType.Color:
- // Color data is always compressed (4 bytes), but we only return a
- // vector here, so only use RGB. Note: Use SaveData(Color) overload!
- new Color(data.X, data.Y, data.Z, 1.0f).Save(writer);
- break;
-
- case VertexElementType.SkinIndices:
- // Skinned data is always compressed (2 shorts)
- writer.Write((short)data.X);
- writer.Write((short)data.Y);
- break;
- case VertexElementType.SkinWeights:
- // Skinned data is always compressed (2 shorts)
- writer.Write(ShortCompress(data.X));
- writer.Write(ShortCompress(data.Y));
- break;
-
- default:
- // Unsupported vertex type
- Log.Warning("Invalid vertex type: " + Type);
- break;
- }
- }
-
- /// <summary>
- /// Save data helper method for GeometryData.SetVertexData, which is slow,
- /// but still helpful for debugging and creating geometry dynamically.
- /// </summary>
- public void SaveData(BinaryWriter writer, Point data)
- {
- // We have to check each type and if the data is compressed
- switch (Type)
- {
- case VertexElementType.Position2D:
- if (IsCompressed)
- {
- // Write data compressed
- writer.Write(Position2DCompress(data.X));
- writer.Write(Position2DCompress(data.Y));
- }
- else
- {
- data.Save(writer);
- }
- break;
-
- case VertexElementType.TextureUV:
- case VertexElementType.LightMapUV:
- case VertexElementType.ExtraUV:
- if (IsCompressed)
- {
- // Write data compressed
- writer.Write(ShortCompress(data.X));
- writer.Write(ShortCompress(data.Y));
- }
- else
- {
- data.Save(writer);
- }
- break;
-
- case VertexElementType.SkinIndices:
- // Skinned data is always compressed (2 shorts)
- writer.Write((short)data.X);
- writer.Write((short)data.Y);
- break;
- case VertexElementType.SkinWeights:
- // Skinned data is always compressed (2 shorts)
- writer.Write(ShortCompress(data.X));
- writer.Write(ShortCompress(data.Y));
- break;
-
- default:
- // Unsupported vertex type
- Log.Warning("Invalid vertex type for " +
- "SaveData with point: " + Type);
- break;
- }
- }
-
- /// <summary>
- /// Save data helper method for GeometryData.SetVertexData, which is slow,
- /// but still helpful for debugging and creating geometry dynamically.
- /// </summary>
- public void SaveData(BinaryWriter writer, Color data)
- {
- // We have to check each type and if the data is compressed
- switch (Type)
- {
- case VertexElementType.Color:
- data.Save(writer);
- break;
-
- default:
- // Unsupported vertex type
- Log.Warning("Invalid vertex type for " +
- "SaveData with color: " + Type);
- break;
- }
- }
- #endregion
-
- #region ToString (Public)
- /// <summary>
- /// To string
- /// </summary>
- public override string ToString()
- {
- return GetType().Name + "(Type=" + Type + ", IsCompressed=" +
- IsCompressed + ")";
- }
- #endregion
-
- #region Methods (Private)
-
- #region SetupProperties
- /// <summary>
- /// Get size, component count, vertex data format and if this vertex data
- /// needs to be normalized (for compressed data).
- /// </summary>
- private void SetupProperties()
- {
- switch (Type)
- {
- case VertexElementType.Position3D:
- case VertexElementType.TextureUVW:
- // 3 shorts for compressed data, 3 floats uncompressed
- // Note: 3 shorts are 6 bytes, but we align it to 8 bytes to be
- // more efficient on many platforms that expect 4 byte alignment.
- // In Xna for example it is only allowed to have 4 byte alignment!
- Size = IsCompressed
- ? 8
- : 12;
- ComponentCount = 3;
- IsNormalized = false;
- IsByteFormat = false;
- IsSignedByteFormat = false;
- IsShortFormat = IsCompressed
- ? true
- : false;
- break;
-
- case VertexElementType.Normal:
- case VertexElementType.Tangent:
- case VertexElementType.Binormal:
- // Trying out 3 bytes.
- Size = IsCompressed
- ? 4
- : 12;
- ComponentCount = 3;
- if (IsCompressed)
- {
- IsNormalized = true;
- IsByteFormat = false;
- IsSignedByteFormat = true;
- }
- else
- {
- IsNormalized = false;
- IsByteFormat = false;
- IsSignedByteFormat = false;
- }
- IsShortFormat = false;
- break;
-
- case VertexElementType.Position2D:
- // 2 shorts for compressed data, 2 floats uncompressed
- Size = IsCompressed
- ? 4
- : 8;
- ComponentCount = 2;
- IsNormalized = false;
- IsByteFormat = false;
- IsSignedByteFormat = false;
- IsShortFormat = IsCompressed
- ? true
- : false;
- break;
-
- case VertexElementType.TextureUV:
- case VertexElementType.LightMapUV:
- case VertexElementType.ExtraUV:
- // 2 shorts for compressed data, 2 floats uncompressed
- Size = IsCompressed
- ? 4
- : 8;
- ComponentCount = 2;
- IsNormalized = IsCompressed
- ? true
- : false;
- IsByteFormat = false;
- IsSignedByteFormat = false;
- IsShortFormat = IsCompressed
- ? true
- : false;
- break;
-
- case VertexElementType.Color:
- // Skinned and color data is already compressed (4 bytes)
- Size = 4;
- ComponentCount = 4;
- IsNormalized = true;
- IsByteFormat = true;
- IsSignedByteFormat = false;
- IsShortFormat = false;
- break;
-
- case VertexElementType.SkinIndices:
- case VertexElementType.SkinWeights:
- // Skinned and color data is already compressed (2 shorts)
- Size = 4;
- ComponentCount = 2;
- // Only skin weights need to be normalized, indices are fine.
- IsNormalized = Type == VertexElementType.SkinWeights;
- // decompress weights in vertex shader, indices are not normalized!
- IsByteFormat = false;
- IsSignedByteFormat = false;
- IsShortFormat = true;
- if (IsCompressed == false)
- {
- Log.Warning(
- "The vertex type '" + Type + "' can not be used as " +
- "uncompressed data !");
- }
- break;
-
- default:
- Log.Warning(
- "VertexElement Type " + Type + " is not supported, unable to " +
- "SetupProperties!");
- break;
- }
- }
- #endregion
-
- #region Position3DCompress
- /// <summary>
- /// Short compression is only for values between -75.0 and +75.0.
- /// </summary>
- private short Position3DCompress(float originalValue)
- {
- return (short)Math.Round(originalValue * Position3DCompressionValue);
- }
- #endregion
-
- #region Position2DCompress
- /// <summary>
- /// Position 2D compression
- /// </summary>
- private short Position2DCompress(float originalValue)
- {
- return (short)Math.Round(originalValue * Position2DCompressionValue);
- }
- #endregion
-
- #region ShortCompress
- /// <summary>
- /// Short compression is only for values between -1.0 and +1.0.
- /// </summary>
- private short ShortCompress(float originalValue)
- {
- return (short)Math.Round(originalValue * ShortCompressionValue);
- }
- #endregion
-
- #region SignedByteCompress
- /// <summary>
- /// Byte compression is only for normalized values between -1.0 and +1.0.
- /// </summary>
- private sbyte SignedByteCompress(float originalValue)
- {
- // Note: We are ignoring the -128 issue (this will go to max. -127)
- return (sbyte)Math.Round(originalValue * SignedByteCompressionValue);
- }
- #endregion
-
- #region Position3DDecompress
- /// <summary>
- /// Decompress a short value to a float.
- /// Return min value: -75.0
- /// Return max value: +75.0
- /// </summary>
- private float Position3DDecompress(short compressedValue)
- {
- return compressedValue / Position3DCompressionValue;
- }
- #endregion
-
- #region Position2DDecompress
- /// <summary>
- /// Position 2D decompress
- /// </summary>
- private float Position2DDecompress(short compressedValue)
- {
- return compressedValue / Position2DCompressionValue;
- }
- #endregion
-
- #region ShortDecompress
- /// <summary>
- /// Short decompression.
- /// </summary>
- private float ShortDecompress(short compressedValue)
- {
- return compressedValue / ShortCompressionValue;
- }
- #endregion
-
- #region SignedByteDecompress
- /// <summary>
- /// Decompress a byte value to a float (for normals, tangents, etc.)
- /// Return min value: -1.0
- /// Return max value: +1.0
- /// </summary>
- private float SignedByteDecompress(sbyte compressedValue)
- {
- // Note: We are ignoring the -128 issue (this will go to max. -127)
- return compressedValue / SignedByteCompressionValue;
- }
- #endregion
-
- #endregion
-
- /// <summary>
- /// Tests
- /// </summary>
- internal class VertexElementTests
- {
- #region GetSize
- /// <summary>
- /// Get size
- /// </summary>
- [Test]
- public void GetSize()
- {
- Assert.Equal(8,
- new VertexElement(VertexElementType.Position2D, false).Size);
- Assert.Equal(4,
- new VertexElement(VertexElementType.Position2D, true).Size);
- Assert.Equal(12,
- new VertexElement(VertexElementType.Position3D, false).Size);
- Assert.Equal(8,
- new VertexElement(VertexElementType.Position3D, true).Size);
- Assert.Equal(12,
- new VertexElement(VertexElementType.Normal, false).Size);
- Assert.Equal(4,
- new VertexElement(VertexElementType.Normal, true).Size);
- Assert.Equal(8,
- new VertexElement(VertexElementType.TextureUV, false).Size);
- Assert.Equal(4,
- new VertexElement(VertexElementType.TextureUV, true).Size);
- // Compressed or not makes no difference for Color, its always 4 bytes
- Assert.Equal(4,
- new VertexElement(VertexElementType.Color, false).Size);
- Assert.Equal(4,
- new VertexElement(VertexElementType.Color, true).Size);
- }
- #endregion
- }
- }
- }