I finally had time to solve this problem. Now I understand how it works much better. There is my solution:
we have to normalize weights during load of geometry. Even small error caused by small precision of uchar is visible (strange is that this problem is much more visible during software skinning (this is probably caused by FPU precision setting)). There is my patch for this problem:
egGeomery.cpp, Ln: 226 (Horde3D Beta2)
Old code:
Code:
case 5: // Weights
if( streamElemSize != 4 ) return raiseError( "Invalid weight stream" );
for( uint32 j = 0; j < streamSize; ++j )
{
memcpy( &uc, myData, sizeof( char ) ); myData += sizeof( char ); _vertData->staticData[j].weightVec[0] = uc / 255.0f;
memcpy( &uc, myData, sizeof( char ) ); myData += sizeof( char ); _vertData->staticData[j].weightVec[1] = uc / 255.0f;
memcpy( &uc, myData, sizeof( char ) ); myData += sizeof( char ); _vertData->staticData[j].weightVec[2] = uc / 255.0f;
memcpy( &uc, myData, sizeof( char ) ); myData += sizeof( char ); _vertData->staticData[j].weightVec[3] = uc / 255.0f;
}
break;
New code:
Code:
case 5: // Weights
if( streamElemSize != 4 ) return raiseError( "Invalid weight stream" );
for( uint32 j = 0; j < streamSize; ++j )
{
memcpy( &uc, myData, sizeof( char ) ); myData += sizeof( char ); _vertData->staticData[j].weightVec[0] = uc / 255.0f;
memcpy( &uc, myData, sizeof( char ) ); myData += sizeof( char ); _vertData->staticData[j].weightVec[1] = uc / 255.0f;
memcpy( &uc, myData, sizeof( char ) ); myData += sizeof( char ); _vertData->staticData[j].weightVec[2] = uc / 255.0f;
memcpy( &uc, myData, sizeof( char ) ); myData += sizeof( char ); _vertData->staticData[j].weightVec[3] = uc / 255.0f;
float totalWeights = _vertData->staticData[j].weightVec[0] + _vertData->staticData[j].weightVec[1] + _vertData->staticData[j].weightVec[2] + _vertData->staticData[j].weightVec[3];
if ( totalWeights > Math::Epsilon )
{
float factor = 1.0f / totalWeights;
_vertData->staticData[j].weightVec[0] *= factor;
_vertData->staticData[j].weightVec[1] *= factor;
_vertData->staticData[j].weightVec[2] *= factor;
_vertData->staticData[j].weightVec[3] *= factor;
}
}
break;
This patch solves problem with my original file. But Goblin.DAE has another problem which is caused by duplicity of joint's names. I created function which detects these Joins/Meshe and creates for them new names.
ColladaConverter\converter.cpp (Horde3D Beta2)
Code:
void Converter::checkNodeName( SceneNode *node )
{
bool jointHit = false;
// Check Joint names
for( unsigned int i = 0; i < _joints.size(); ++i )
{
if ( _joints[i]->name != node->name && strcmp( _joints[i]->name, node->name ) == 0 )
jointHit = true;
}
bool meshHit = false;
// Check Mesh names
for( unsigned int i = 0; i < _meshes.size(); ++i )
{
if ( _meshes[i]->name != node->name && strcmp( _meshes[i]->name, node->name ) == 0 )
meshHit = true;
}
if ( !meshHit && !jointHit )
return;
if( strlen( node->name ) > 200 )
node->name[ 200 ] = 0; // Clear some space
char newName[ 256 ];
int count = 2;
// Search loop
while ( true )
{
bool hit = false;
// Create new name
sprintf( newName, "%s%d", node->name, count++ );
// Check Joint names
for( unsigned int i = 0; i < _joints.size(); ++i )
{
if ( _joints[i]->name != node->name && strcmp( _joints[i]->name, newName ) == 0 )
{
hit = true;
break;
}
}
if ( hit == false )
{
// Check Mesh names
for( unsigned int i = 0; i < _meshes.size(); ++i )
{
if ( _meshes[i]->name != node->name && strcmp( _meshes[i]->name, newName ) == 0 )
{
hit = true;
break;
}
}
}
// Is name OK?
if ( hit == false )
break;
}
char message[ 1024 ];
// Get message
if ( jointHit == true )
sprintf( message, "Warning: Joint with name '%s' already exists. New name for Joint is '%s'.", node->name, newName );
if ( meshHit == true )
sprintf( message, "Warning: Mesh '%s' has same name as Joint. New name for Mesh is '%s'.", node->name, newName );
// Log message
log( message );
// Set new name
strcpy( node->name, newName );
}
I call this function at the beginning of the Converter::writeSGNode..
Code:
void Converter::writeSGNode( const string &modelName, SceneNode *node, unsigned int depth, ofstream &outf )
{
// Check node name
checkNodeName( node );
.....
This solves problem with Goblin.DAE.
We should better normalize weights in Converter::processMeshes
Old code:
Code:
if( vertWeights.size() > 4 )
{
v.weights[0] = 1.0f - (v.weights[1] + v.weights[2] + v.weights[3]);
}
New code:
Code:
if( vertWeights.size() > 4 )
{
float totalWeights = v.weights[0] + v.weights[1] + v.weights[2] + v.weights[3];
if ( totalWeights > Math::Epsilon )
{
float factor = 1.0f / totalWeights;
v.weights[0] *= factor;
v.weights[1] *= factor;
v.weights[2] *= factor;
v.weights[3] *= factor;
}
else
{
v.weights[0] = 1.0f;
v.weights[1] = 0;
v.weights[2] = 0;
v.weights[3] = 0;
}
}
We should sort weights every time (not only when vertWeights.size() > 4 ) because this helps as with optimization of software skinning.