SMD skeleton problems

I am attempting to convert the skeleton from a source engine SMD file to an anim8or an8 file.

The SMD file contains a position of the end of the bone relative to the parent and 3 euler angles for orientation, rotation order is X,Y,Z
the an8 format uses a bone length and quaternion for orientation.

this is the code i’m using.

for(int x=0;x<bones.size();x++)
		{
			smdbone sb=bones.get(x);
			
		Quat4f orientation= new Quat4f();
		float length;
		
		
		Quat4f xrot=new Quat4f();
		xrot.set(new AxisAngle4f(1,0,0,sb.rotx));
		
		Quat4f yrot=new Quat4f();
		yrot.set(new AxisAngle4f(0,1,0,sb.roty));
		
		Quat4f zrot=new Quat4f();
		zrot.set(new AxisAngle4f(0,0,1,sb.rotz));
		
		
		orientation.set(xrot);
		orientation.mul(yrot);
		orientation.mul(zrot);
		

		String pname;
			if(sb.parent==null)
			{
				length=2;
				pname="root";
			}
			else
			{
				length=new Vector3f(sb.x,sb.y,sb.z).length();
				pname=sb.parent.name;
			}
			
			
			Bone b = new Bone();
			b.name=sb.name;
			b.length=length;
			b.orientation=orientation;
			
			fig.getbone(pname).subbones.add(b);
			
		}

the Bone and smdbone classes represent an8 bones and smd bones respectively. I have verified that my SMD loading and an8 save code works properly.

so far the results I’ve been getting in no way resemble the input.
any ideas what’s going wrong?

thanks!

I’m afraid you calculate bone length incorrectly.

Hope this will work:


Vector3f boneEnd;
if(sb.parent==null)
         {
            boneEnd = new Vector3f(sb.x,sb.y,sb.z);
            pname="root";
         }
         else
         {
            boneStart=boneEnd;
            boneEnd = new Vector3f(sb.x-boneStart.x,sb.y-boneStart.y,sb.z-boneStart.z);
            pname=sb.parent.name;
         }
length=boneEnd.length();

sorry, that didn’t work.
I think the problem is with the orientation code.

Try to normalize quaternion after multiplication.

 orientation.set(xrot);
      orientation.mul(yrot);
      orientation.mul(zrot);
     orientation.normalize(); 

no joy. would it be a problem if the coordinate systems ware different? I would think the result should at least look like the input, but oriented along a different axis.

Hi deepthought,

The third trial. :slight_smile:

Are you sure it’s alright with degrees-to-radians converting?
AxisAngle4f accepts radians I guess.

According to https://developer.valvesoftware.com/wiki/Studiomdl_Data the angles given are in radians.

Maybe it could help you as a hint:

	
for( int bone = 0; bone < (int)pFigures[i_Fig].m_FigureHelper.vBones.size(); ++bone)
		{
			if( bone > 0 )// Not a parent
			{
				fprintf(_File,"%d %f %f %f %f %f %f\n",
						bone,
						pFigures[i_Fig].m_FigureHelper.vBones[bone].Translation.x,
						pFigures[i_Fig].m_FigureHelper.vBones[bone].Translation.y,
						pFigures[i_Fig].m_FigureHelper.vBones[bone].Translation.z,
						pFigures[i_Fig].m_FigureHelper.vBones[bone].Rotation.x, // rot X
						-pFigures[i_Fig].m_FigureHelper.vBones[bone].Rotation.z,// rot Y
						pFigures[i_Fig].m_FigureHelper.vBones[bone].Rotation.y // rot Z
						);
			}
			else
			{
				fprintf(_File,"%d %f %f %f %f %f %f\n",
						bone,
						pFigures[i_Fig].m_FigureHelper.vBones[bone].Translation.x,
						pFigures[i_Fig].m_FigureHelper.vBones[bone].Translation.y,
						pFigures[i_Fig].m_FigureHelper.vBones[bone].Translation.z,
						pFigures[i_Fig].m_FigureHelper.vBones[bone].Rotation.x-1.561606f, // rot X
						-pFigures[i_Fig].m_FigureHelper.vBones[bone].Rotation.y,// rot Y
						pFigures[i_Fig].m_FigureHelper.vBones[bone].Rotation.z // rot Z
						);
			}
		}
 

It’s a fragment of the program (in C++) converting an8 to smd.

the translation component of the matrix wouldn’t have any effect, would it?
where did you get that code from?

Home page:
http://texel3d.free.fr/projets/ani2pov/

Code fragment:
link

thanks. too bad ani2pov can’t save anim8or files with a skeleton.
I’m going to try asking on the anim8or forum for some insight.

for SMD x is left, y is forward, z is up.
in anim8or, x is left, y is up, z is forward.
how do i convert rotation between these 2 systems?

I think z-up to y-up conversion may be done in this way:


for(int x=0;x<bones.size();x++)
{
    smdbone sb=bones.get(x);
         
    Quat4f orientation= new Quat4f();
    float length;
      
      
    Quat4f xrot=new Quat4f();
    xrot.set(new AxisAngle4f(1,0,0,sb.rotx));

+   float temp;
+   temp = sb.roty;
+   sb.roty= sb.rotz;
+   sb.rotz = -temp;
        
    Quat4f yrot=new Quat4f();
    yrot.set(new AxisAngle4f(0,1,0,sb.roty));
      
    Quat4f zrot=new Quat4f();
    zrot.set(new AxisAngle4f(0,0,1,sb.rotz));
      
      
    orientation.set(xrot);
    orientation.mul(yrot);
    orientation.mul(zrot);
      

    String pname;
    if(sb.parent==null)
    {
            length=2;
            pname="root";
    }
    else
    {
            length=new Vector3f(sb.x,sb.y,sb.z).length();
            pname=sb.parent.name;
    }
         
         
    Bone b = new Bone();
    b.name=sb.name;
    b.length=length;
    b.orientation=orientation;
         
    fig.getbone(pname).subbones.add(b);
}

i tried switching roty and rotz. just got a different weird looking skeleton

not just switching but also negating rotz.

This is what I do convert the Euler angles (stored in a Vector3) to a quaternion:


	private Quaternion convertOrientation(Vector3 o) {
		
		float x = o.x * 0.5f;
		float y = -o.y * 0.5f; //NOTE
		float z = o.z * 0.5f;
		
		float c1 = (float)Math.cos(z);
		float c2 = (float)Math.cos(y);
		float c3 = (float)Math.cos(x);
		
		float s1 = (float)Math.sin(z);
		float s2 = (float)Math.sin(y);
		float s3 = (float)Math.sin(x);
		
		Quaternion q = new Quaternion(
				s1*s2*c3 + c1*c2*s3,
				s1*c2*c3 + c1*s2*s3,
				c1*s2*c3 - s1*c2*s3,
				c1*c2*c3 - s1*s2*s3);

		if(q.w < 0){
			q.x = -q.x;
			q.y = -q.y;
			q.z = -q.z;
			q.w = -q.w;
		}
		return q;
	}

It seems we simplified the solution overly :-\ :).
Insert this for positions:


+   temp = sb.y;
+   sb.y= sb.z;
+   sb.z = -temp;

the only thing i use position for is calculating of the bone. switching values won’t make a difference.
I found an SMD loader for JMonkey, which has the same coordinate system as anim8or…

this is the code used to create a rotation matrix for the bone.

Matrix3f m1 = null;
        Matrix3f m2 = null;
        Matrix3f m3 = null;
        try {
            m1 = new Matrix3f();
            m1.fromAngleAxis(rot.x, Vector3f.UNIT_X);
            m2 = new Matrix3f();
            m2.fromAngleAxis(rot.y, Vector3f.UNIT_Y);
            m3 = new Matrix3f();
            m3.fromAngleAxis(rot.z, Vector3f.UNIT_Z);
        } catch (Exception Ex) {
            logger.log(Level.SEVERE, "Error, building rotation Matrix3f from "
                    + "rotation vector.", Ex);
        }
        return m3.mult(m2).mult(m1);

this is the same as what I’ve been doing in my code, and also in theagentd’s code.
there must be something stupidly simple we’re missing.

I not entirely get how do you convert the bones.
As i realize SMD bones are, in fact, joints.
so, an8 bones = SMD bones - 1.

What do you think?

I noticed some strange stuff in my SMD file.


2 3.890447 0.000000 0.000000 -1.570796 0.051836 -1.565574 // left thigh
5 -3.890447 0.000000 0.000000 -1.570796 0.051836 -1.576018 //right thigh 

14 2.059528 0.977921 1.937661 1.589740 -1.286515 2.950147//left clavicle
 18 2.059528 0.977922 -1.937661 -1.661569 1.286515 2.950147//right clavicle

both the thighs (which are really where the hips are) and clavicles are mirror images of each other.
notice how the thighs are done using negated position but same rotation, but the clavicles have a different axis inverted and have 2 inverted angles.

as far as the bone format goes, the position refers to where the end of the bone is. Also, because in anim8or, the root bone can’t own objects, so i have to add the smd root bone to the an8 root bone.