Index: /trunk/nv/gfx/mesh_creator.hh
===================================================================
--- /trunk/nv/gfx/mesh_creator.hh	(revision 456)
+++ /trunk/nv/gfx/mesh_creator.hh	(revision 457)
@@ -25,9 +25,13 @@
 		// assumes that position and normal is vec3, tangent is vec4
 		void rotate_quadrant( uint8 rotation );
+		// assumes that position and normal is vec3, tangent is vec4
+		void mirror( bool x, bool z );
+		// assumes mesh is indexed
+		void swap_culling();
 		void translate( vec3 offset );
 		void flip_normals();
 		void scale_texture( vec2 min, vec2 max );
-		bool is_same_format( data_channel_set* other );
-		void merge( data_channel_set* other );
+		bool is_same_format( const data_channel_set* other );
+		void merge( const data_channel_set* other );
 	private:
 		void initialize();
Index: /trunk/nv/interface/mesh_data.hh
===================================================================
--- /trunk/nv/interface/mesh_data.hh	(revision 456)
+++ /trunk/nv/interface/mesh_data.hh	(revision 457)
@@ -116,4 +116,13 @@
 			return &m_meshes[ index ];
 		}
+		const data_channel_set* get_mesh_by_hash( shash64 h ) const
+		{
+			for ( uint32 i = 0; i < m_count; ++i )
+			{
+				if ( m_meshes[i].get_name() == h )
+					return &m_meshes[i];
+			}
+			return nullptr;
+		}
 		uint32 get_count() const { return m_count; }
 		const mesh_nodes_data* get_nodes() const { return m_nodes; }
Index: /trunk/src/gfx/mesh_creator.cc
===================================================================
--- /trunk/src/gfx/mesh_creator.cc	(revision 456)
+++ /trunk/src/gfx/mesh_creator.cc	(revision 457)
@@ -345,9 +345,78 @@
 			tan.y,
 			tan.x * r21 + tan.z * r22,
-			1.0f // make sure this is proper
+			tan.w // make sure this is proper
 			);
 	}
 
 
+}
+
+void nv::mesh_data_creator::mirror( bool x, bool z )
+{
+	if ( !x && !z ) return;
+	NV_ASSERT( m_pos_type == FLOAT_VECTOR_3, "Unsupported position vector type!" );
+	NV_ASSERT( m_nrm_type == FLOAT_VECTOR_3, "Unsupported normal vector type!" );
+	NV_ASSERT( m_tan_type == FLOAT_VECTOR_4, "Unsupported tangent vector type!" );
+
+	float kx = x ? -1.0f : 1.0f;
+	float kz = z ? -1.0f : 1.0f;
+
+	unsigned vtx_count = m_pos_channel->size();
+	uint8* pos_data = raw_data_channel_access( m_pos_channel ).raw_data();
+	uint8* nrm_data = raw_data_channel_access( m_nrm_channel ).raw_data();
+	uint8* tan_data = raw_data_channel_access( m_tan_channel ).raw_data();
+	for ( unsigned int i = 0; i < vtx_count; ++i )
+	{
+		vec3& pos = *reinterpret_cast<vec3*>( pos_data + m_pos_channel->element_size() * i + m_pos_offset );
+		vec3& nrm = *reinterpret_cast<vec3*>( nrm_data + m_nrm_channel->element_size() * i + m_nrm_offset );
+		vec4& tan = *reinterpret_cast<vec4*>( tan_data + m_tan_channel->element_size() * i + m_tan_offset );
+
+		pos = vec3(
+			pos.x * kx,
+			pos.y,
+			pos.z * kz
+			);
+		nrm = vec3(
+			nrm.x * kx,
+			nrm.y,
+			nrm.z * kz
+			);
+		tan = vec4(
+			tan.x * kx,
+			tan.y,
+			tan.z * kz,
+			tan.w * kx * kz// make sure this is proper
+			);
+	}
+
+	if ( !( x && z ) ) 
+		swap_culling();
+}
+
+template < typename T >
+static inline void swap_culling_impl( nv::raw_data_channel* index_channel )
+{
+	nv::raw_data_channel_access ichannel( index_channel );
+	T* indices = reinterpret_cast<T*>( ichannel.raw_data() );
+	nv::uint32 count = index_channel->size() / 3;
+	for ( nv::uint32 i = 0; i < count; ++i )
+	{
+		nv::swap( indices[i * 3], indices[i * 3 + 1] );
+	}
+}
+
+void nv::mesh_data_creator::swap_culling()
+{
+	NV_ASSERT( m_idx_channel, "Swap culling unsupported on non-indexed meshes!" );
+	NV_ASSERT( m_idx_channel->descriptor().size() == 1, "Malformed index channel!" );
+	NV_ASSERT( m_idx_channel->size() % 3 == 0, "Malformed index channel - not per GL_TRIANGLE LAYOUT?" );
+
+	if ( m_idx_channel->size() == 0 ) return;
+	switch ( m_idx_type )
+	{
+	case USHORT: swap_culling_impl< uint16 >( m_idx_channel ); break;
+	case UINT: swap_culling_impl< uint16 >( m_idx_channel ); break;
+	default: NV_ASSERT( false, "Swap culling supports only unsigned and unsigned short indices!" ); break;
+	}
 }
 
@@ -477,5 +546,5 @@
 
 
-bool nv::mesh_data_creator::is_same_format( data_channel_set* other )
+bool nv::mesh_data_creator::is_same_format( const data_channel_set* other )
 {
 	if ( m_data->size() != other->size() ) return false;
@@ -488,5 +557,5 @@
 }
 
-void nv::mesh_data_creator::merge( data_channel_set* other )
+void nv::mesh_data_creator::merge( const data_channel_set* other )
 {
 	if ( !is_same_format( other ) ) return;
