2012-08-16 14:18:21 +00:00
#region Using Statements
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
#endregion
// This file is part of the ANX.Framework created by the
// "ANX.Framework developer group" and released under the Ms-PL license.
// For details see: http://anxframework.codeplex.com/license
namespace ANX.Framework.Content.Pipeline.Graphics
{
public static class MeshHelper
{
2015-04-08 14:50:03 +02:00
private static bool HasCompleteNormals ( MeshContent mesh )
{
foreach ( var geometry in mesh . Geometry )
{
if ( ! geometry . Vertices . Channels . Contains ( VertexChannelNames . Normal ( ) ) )
{
return false ;
}
}
return true ;
}
2012-08-16 14:18:21 +00:00
public static void CalculateNormals ( MeshContent mesh , bool overwriteExistingNormals )
{
2015-04-08 14:50:03 +02:00
if ( mesh = = null )
throw new ArgumentNullException ( "mesh" ) ;
if ( ! overwriteExistingNormals & & HasCompleteNormals ( mesh ) )
{
return ;
}
foreach ( GeometryContent geometry in mesh . Geometry )
{
VertexChannelCollection channels = geometry . Vertices . Channels ;
if ( overwriteExistingNormals )
{
channels . Remove ( VertexChannelNames . Normal ( ) ) ;
}
else if ( channels . Contains ( VertexChannelNames . Normal ( ) ) )
{
continue ;
}
Vector3 [ ] normals = new Vector3 [ mesh . Positions . Count ] ;
IndirectPositionCollection positions = geometry . Vertices . Positions ;
VertexChannel < int > positionIndices = geometry . Vertices . PositionIndices ;
for ( int i = 0 ; i < positionIndices . Count ; i + = 3 )
{
Vector3 vector = positions [ positionIndices [ i + 1 ] ] ;
//Calculate the normale for the given Vector.
Vector3 normalVector = Vector3 . SafeNormalize ( Vector3 . Cross ( positions [ positionIndices [ i + 2 ] ] - vector , vector - positions [ positionIndices [ i ] ] ) ) ;
for ( int j = 0 ; j < 3 ; j + + )
{
normals [ positionIndices [ geometry . Indices [ i + j ] ] ] = normalVector ;
}
}
VertexChannel < Vector3 > vertexChannel = channels . Add < Vector3 > ( VertexChannelNames . Normal ( ) , null ) ;
for ( int i = 0 ; i < vertexChannel . Count ; i + + )
{
vertexChannel [ i ] = normals [ positionIndices [ i ] ] ;
}
}
2012-08-16 14:18:21 +00:00
}
2015-04-08 14:50:03 +02:00
//description from msdn.
/// <summary>
/// Compute tangent frames for the given mesh.
/// </summary>
/// <remarks>
/// This method computes and adds tangent and binormal vertex channels to the given mesh.
///
/// An InvalidContentException is thrown if:
/// <list type="bullet">
/// <item>The mesh does not contain a normal channel (stored in Normal).</item>
/// <item>The data specified in the normal channel is not a Vector3 type.</item>
/// <item>The vertex channel of the mesh does not have the name specified by textureCoordinateChannelName.</item>
/// <item>The data specified in the texture coordinate channel is not a Vector2 type.</item>
/// <item>The channel specified by tangentChannelName already exists.</item>
/// <item>The channel specified by binormalChannelName already exists.</item>
/// </list>
/// </remarks>
/// <param name="mesh">The target mesh used to create the tangent frame. All geometries in this mesh must have normal vertex channels stored in Normal, and must contain Vector3 data.</param>
/// <param name="textureCoordinateChannelName">The texture coordinate channel used for computing the tangent frame. This channel most contain Vector2 data.</param>
/// <param name="tangentChannelName">Target channel name used to store calculated tangents. A tangent channel is not generated if null or an empty string is specified.</param>
/// <param name="binormalChannelName">Target channel name used to store calculated binormals. A tangent channel is not generated if null or an empty string is specified.</param>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="ANX.Framework.Content.Pipeline.InvalidContentException"></exception>
2012-08-16 14:18:21 +00:00
public static void CalculateTangentFrames ( MeshContent mesh , string textureCoordinateChannelName , string tangentChannelName , string binormalChannelName )
{
2015-04-08 14:50:03 +02:00
if ( mesh = = null )
throw new ArgumentNullException ( "mesh" ) ;
if ( string . IsNullOrEmpty ( textureCoordinateChannelName ) )
throw new ArgumentNullException ( "textureCoordinateChannelName" ) ;
//Check first for all the cases that we throw an InvalidContentException.
foreach ( var geometry in mesh . Geometry )
{
VertexChannelCollection channels = geometry . Vertices . Channels ;
if ( ! channels . Contains ( VertexChannelNames . Normal ( ) ) )
throw new InvalidContentException ( string . Format ( "Does not contain a channel for {0}." , mesh . Name , VertexChannelNames . Normal ( ) ) , mesh . Identity ) ;
if ( channels [ VertexChannelNames . Normal ( ) ] . ElementType ! = typeof ( Vector3 ) )
throw new InvalidContentException ( string . Format ( "ElementType of channel \"{0}\" is not {1}, found instead {2}." , VertexChannelNames . Normal ( ) , typeof ( Vector3 ) . Name , channels [ VertexChannelNames . Normal ( ) ] . ElementType ) , mesh . Identity ) ;
if ( ! channels . Contains ( textureCoordinateChannelName ) )
throw new InvalidContentException ( string . Format ( "Does not contain a channel with the name \"{0}\"." , textureCoordinateChannelName ) , mesh . Identity ) ;
if ( channels [ textureCoordinateChannelName ] . ElementType ! = typeof ( Vector2 ) )
throw new InvalidContentException ( string . Format ( "ElementType of channel \"{0}\" is not {1}, found instead {2}." , textureCoordinateChannelName , typeof ( Vector2 ) . Name , channels [ textureCoordinateChannelName ] . ElementType ) , mesh . Identity ) ;
if ( ! string . IsNullOrEmpty ( tangentChannelName ) & & channels . Contains ( tangentChannelName ) )
throw new InvalidContentException ( string . Format ( "Wanted to create a new channel for \"{0}\", but it already exists." , tangentChannelName ) , mesh . Identity ) ;
if ( ! string . IsNullOrEmpty ( binormalChannelName ) & & channels . Contains ( binormalChannelName ) )
throw new InvalidContentException ( string . Format ( "Wanted to create a new channel for \"{0}\", but it already exists." , binormalChannelName ) , mesh . Identity ) ;
}
2012-08-16 14:18:21 +00:00
throw new NotImplementedException ( ) ;
}
public static BoneContent FindSkeleton ( NodeContent node )
{
2015-04-08 14:50:03 +02:00
if ( node = = null )
throw new ArgumentNullException ( "node" ) ;
while ( node ! = null )
{
if ( node is BoneContent )
{
BoneContent boneContent = ( BoneContent ) node ;
while ( boneContent . Parent is BoneContent )
{
boneContent = ( BoneContent ) boneContent . Parent ;
}
return boneContent ;
}
node = node . Parent ;
}
return null ;
2012-08-16 14:18:21 +00:00
}
public static IList < BoneContent > FlattenSkeleton ( BoneContent skeleton )
{
2015-04-08 14:50:03 +02:00
if ( skeleton = = null )
throw new ArgumentNullException ( "skeleton" ) ;
Dictionary < BoneContent , bool > list = new Dictionary < BoneContent , bool > ( ) ;
MeshHelper . FlattenSkeleton ( skeleton , list ) ;
return list . Keys . ToList ( ) ;
}
private static void FlattenSkeleton ( BoneContent skeleton , Dictionary < BoneContent , bool > list )
{
if ( list . ContainsKey ( skeleton ) )
{
throw new ArgumentException ( "The skeleton contains duplicate entries: {0}" , skeleton . Name ) ;
}
list . Add ( skeleton , false ) ;
foreach ( var child in skeleton . Children )
{
if ( child is BoneContent )
{
FlattenSkeleton ( ( BoneContent ) child , list ) ;
}
}
2012-08-16 14:18:21 +00:00
}
public static void MergeDuplicatePositions ( MeshContent mesh , float tolerance )
{
2015-04-08 14:50:03 +02:00
if ( mesh = = null )
throw new ArgumentNullException ( "mesh" ) ;
//TODO: test
float toleranceSquared = tolerance * tolerance ;
var positions = mesh . Positions ;
for ( int i = 0 ; i < positions . Count ; i + + )
{
for ( int j = positions . Count - 1 ; j > i ; j - - )
{
if ( Vector3 . DistanceSquared ( positions [ i ] , positions [ j ] ) < toleranceSquared )
{
//Remove duplicate position.
positions . RemoveAt ( j ) ;
foreach ( GeometryContent geometry in mesh . Geometry )
{
var positionIndices = geometry . Vertices . PositionIndices ;
for ( int k = 0 ; k < positionIndices . Count ; k + + )
{
if ( positionIndices [ k ] = = j )
{
positionIndices [ k ] = i ;
}
//reduce the index for all position indexes that references the removed position by 1.
else if ( positionIndices [ k ] > j )
{
positionIndices [ k ] - - ;
}
}
}
j + + ;
}
}
}
2012-08-16 14:18:21 +00:00
}
public static void MergeDuplicateVertices ( GeometryContent geometry )
{
2015-04-08 14:50:03 +02:00
if ( geometry = = null )
throw new ArgumentNullException ( "geometry" ) ;
List < int > newIndices = new List < int > ( ) ;
List < int > newPositionIndices = new List < int > ( ) ;
//Maps from the index in newPositionIndices to the index in the old position indices.
List < int > positionIndexMapping = new List < int > ( ) ;
//Find positionIndex duplicates
var positionIndices = geometry . Vertices . PositionIndices ;
for ( int i = 0 ; i < positionIndices . Count ; i + + )
{
int index = newPositionIndices . IndexOf ( positionIndices [ i ] ) ;
//If the positionIndex is not in the list, add it.
if ( index = = - 1 )
{
index = newPositionIndices . Count ;
newPositionIndices . Add ( positionIndices [ i ] ) ;
positionIndexMapping . Add ( i ) ;
}
else
{
var originalIndex = positionIndexMapping [ index ] ;
//check other channels, if any of them has other data, we can't skip that vertex.
foreach ( var channel in geometry . Vertices . Channels )
{
var originalVertex = channel [ originalIndex ] ;
var currentVertex = channel [ i ] ;
//Only add an positionIndex that was already found if the vertex represented is different in any channel.
if ( currentVertex ! = null & & ( originalVertex = = null | | ! originalVertex . Equals ( currentVertex ) ) )
{
index = newPositionIndices . Count ;
newPositionIndices . Add ( positionIndices [ i ] ) ;
positionIndexMapping . Add ( i ) ;
break ;
}
}
}
newIndices . Add ( index ) ;
}
if ( newPositionIndices . Count ! = positionIndices . Count )
{
geometry . Indices . Clear ( ) ;
geometry . Indices . AddRange ( newIndices ) ;
//Copy the needed data from the vertex channels.
var channels = geometry . Vertices . Channels ;
List < object > [ ] vertexChannelsData = new List < object > [ channels . Count ] ;
for ( int i = 0 ; i < channels . Count ; i + + )
{
List < object > data = new List < object > ( ) ;
vertexChannelsData [ i ] = data ;
var channel = channels [ i ] ;
for ( int j = 0 ; j < newPositionIndices . Count ; j + + )
{
data . Add ( channel [ positionIndexMapping [ j ] ] ) ;
}
}
//Set new position indices.
positionIndices . Clear ( ) ;
positionIndices . AddRange ( newPositionIndices ) ;
//Set data of the vertex channels.
for ( int i = 0 ; i < channels . Count ; i + + )
{
var channel = channels [ i ] ;
channel . Clear ( ) ;
channel . AddRange ( vertexChannelsData [ i ] ) ;
}
}
2012-08-16 14:18:21 +00:00
}
public static void MergeDuplicateVertices ( MeshContent mesh )
{
2015-04-08 14:50:03 +02:00
if ( mesh = = null )
throw new ArgumentNullException ( "mesh" ) ;
foreach ( var geometry in mesh . Geometry )
{
MergeDuplicateVertices ( geometry ) ;
}
2012-08-16 14:18:21 +00:00
}
public static void OptimizeForCache ( MeshContent mesh )
{
2015-04-08 14:50:03 +02:00
if ( mesh = = null )
throw new ArgumentNullException ( "mesh" ) ;
2012-08-16 14:18:21 +00:00
}
public static void SwapWindingOrder ( MeshContent mesh )
{
2015-04-08 14:50:03 +02:00
if ( mesh = = null )
throw new ArgumentNullException ( "mesh" ) ;
foreach ( var geometry in mesh . Geometry )
{
var indices = geometry . Indices ;
if ( indices . Count % 3 ! = 0 )
throw new ArgumentException ( string . Format ( "Number of indices in geometry \"{0}\" is not a multiple of 3." , geometry . Name ) ) ;
for ( int i = 0 ; i < indices . Count ; i + = 3 )
{
//Flip the start and the end index of the triangle.
int index = indices [ i ] ;
indices [ i ] = indices [ i + 2 ] ;
indices [ i + 2 ] = index ;
}
}
2012-08-16 14:18:21 +00:00
}
2015-04-08 14:50:03 +02:00
/// <summary>
/// Applies a transformation to the contents of a scene hierarchy.
/// </summary>
/// <remarks>
/// The resulting world space positions are similar to the results obtained from applying the specified transform to the Transform property of the scene object.
/// However, this method performs the transformation by cascading down through the scene hierarchy and modifying the actual underlying vertex positions.
/// </remarks>
/// <param name="scene">Scene hierarchy being transformed.</param>
/// <param name="transform">Matrix used in the transformation</param>
2012-08-16 14:18:21 +00:00
public static void TransformScene ( NodeContent scene , Matrix transform )
{
2015-04-08 14:50:03 +02:00
if ( scene is MeshContent )
{
var mesh = ( MeshContent ) scene ;
var positions = mesh . Positions ;
for ( int i = 0 ; i < positions . Count ; i + + )
{
positions [ i ] = Vector3 . Transform ( positions [ i ] , transform ) ;
}
}
foreach ( var animation in scene . Animations . Values )
{
foreach ( var channel in animation . Channels . Values )
{
for ( int i = 0 ; i < channel . Count ; i + + )
{
channel [ i ] . Transform * = transform ;
}
}
}
foreach ( var child in scene . Children )
{
TransformScene ( child , transform ) ;
}
2012-08-16 14:18:21 +00:00
}
}
}