Notifications
Clear all

[Closed] Matrix Interpolation

Hi there,

The last couple of days, I’m trying to find a way to interpolate two Matrices by a percent.
After a lot of failed attempts, I’ve decided to try recreate Blur’s Transform Cache in C#.

Blur’s C++ code


static void comp_affine( const AffineParts &ap, Matrix3 &mat )
{
	Matrix3 tm;
	
	mat.IdentityMatrix();
	mat.SetTrans( ap.t );

	if ( ap.f != 1.0f ) {				// has f component
		tm.SetScale( Point3( ap.f, ap.f, ap.f ) );
		mat = tm * mat;
	}

	if ( !ap.q.IsIdentity() ) {			// has q rotation component
		ap.q.MakeMatrix( tm );
		mat = tm * mat;
	}
	
	if ( ap.k.x != 1.0f || ap.k.y != 1.0f || ap.k.z != 1.0f ) {		// has k scale component
		tm.SetScale( ap.k );
		if ( !ap.u.IsIdentity() ) {			// has u rotation component
			Matrix3 utm;
			ap.u.MakeMatrix( utm );
			mat = Inverse( utm ) * tm * utm * mat;
		} else {
			mat = tm * mat;
		}
	}
}

// matrix interpolation
Matrix3 InterpolateMatrix3 ( Matrix3 matA, Matrix3 matB, float percentage )
{
	AffineParts parts;
	decomp_affine(matA,&parts);
	parts.q.Normalize();
	Point3 tempPos = parts.t;
	Quat   tempRot = parts.q;
	Point3 tempScl = parts.k;

	decomp_affine(matB,&parts);
	parts.q.Normalize();
	parts.q.MakeClosest(tempRot);
	tempPos = tempPos + ( (parts.t - tempPos)*(float)percentage );
	tempRot = Slerp(tempRot, parts.q , percentage);
	tempScl = tempScl + ( (parts.k - tempScl)*(float)percentage );
	
	parts.t = tempPos;
	parts.q = tempRot;
	parts.k = tempScl;
	
	Matrix3 mat(0);
	comp_affine(parts, mat);

	return mat;
}

My Implementation in C#


        static private IGlobal global = GlobalInterface.Instance;
        static private IMatrix3 mat;

        static private void comp_affine( IAffineParts ap, IMatrix3 mat )
        {
            IMatrix3 tm = global.Matrix3.Create();

            mat.IdentityMatrix();
            mat.SetTranslate(ap.T);

            if (ap.F != 1.0f)				// has f component
            {
                tm.SetScale(global.Point3.Create(ap.F, ap.F, ap.F));
                mat = tm.Multiply(mat);
            }
            if (!ap.Q.IsIdentity)			// has q rotation component
            {
                ap.Q.MakeMatrix(mat, false);
                mat = tm.Multiply(mat);
            }
            if (ap.K.X != 1.0f || ap.K.Y != 1.0f || ap.K.Z != 1.0f)		// has k scale component
            {
                tm.SetScale(ap.K);
                if (!ap.U.IsIdentity)			// has u rotation component
                {
                    IMatrix3 utm = global.Matrix3.Create();
                    ap.U.MakeMatrix(utm, false);
                    mat = global.Inverse(utm).Multiply(tm).Multiply(utm).Multiply(mat);
                }
                else
                {
                    mat = tm.Multiply(mat);
                }
            }
        }
        public static IMatrix3 InterpolateMatrix3(IMatrix3 matA, IMatrix3 matB, float percentage)
        {
            IAffineParts parts = global.AffineParts.Create();
            global.DecompAffine(matA, parts);
            parts.Q.Normalize();
            IPoint3 tempPos = parts.T;
            IQuat tempRot = parts.Q;
            IPoint3 tempScl = parts.K;

            global.DecompAffine(matB, parts);
            parts.Q.Normalize();
            parts.Q.MakeClosest(tempRot);

            tempPos = tempPos.Add(parts.T.Subtract(tempPos).MultiplyBy(percentage));
            tempRot = global.Slerp(tempRot, parts.Q, percentage);
            tempScl = tempScl.Add(parts.K.Subtract(tempScl).MultiplyBy(percentage));

            parts.T = tempPos;
            parts.Q = tempRot;
            parts.K = tempScl;

            mat = global.Matrix3.Create();
            mat.Zero();
            comp_affine(parts, mat);

            return mat;
        }

MXS code


(
	local
		mat,
		matA,
		matB,
		percent = .5;
	
	--Create DotNet Matrix3 for the first key
	u = GlobalInterface.Point3.create 1 0 0
	v = GlobalInterface.Point3.create 0 1 0
	n = GlobalInterface.Point3.create 0 0 1
	t = GlobalInterface.Point3.create 10 0 0
	matA = GlobalInterface.Matrix3.create u v n t
	
	--Create DotNet Matrix3 for the second key
	u = GlobalInterface.Point3.create 1 0 0
	v = GlobalInterface.Point3.create 0 1 0
	n = GlobalInterface.Point3.create 0 0 1
	t = GlobalInterface.Point3.create -10 0 0
	matB = GlobalInterface.Matrix3.create u v n t
	
	--Find interpolation key
	mat = myClass.InterpolateMatrix3 matA matB percent
	
	--Convert to MXS Matrix3
	row1 = [(mat.GetColumn3 1).x, (mat.GetColumn3 1).y, (mat.GetColumn3 1).z]
	row2 = [(mat.GetColumn3 2).x, (mat.GetColumn3 2).y, (mat.GetColumn3 2).z]
	row3 = [(mat.GetColumn3 3).x, (mat.GetColumn3 3).y, (mat.GetColumn3 3).z]
	row4 = [(mat.GetColumn3 4).x, (mat.GetColumn3 4).y, (mat.GetColumn3 4).z]
	matrix3 row1 row2 row3 row4
)

The result:


(matrix3 [0,1,0] [0,0,1] [0,0,-10] [1,0,0])

The code compiles fine, but no matter what values I type, I get the same (wrong) result.

Do you know what I miss?

Thanks,
Nick

6 Replies

Any help plzzz?! :rolleyes:

hm… I’ve just realized that I get the following error “– Runtime error: dotNet runtime exception: Unable to cast object of type ‘Autodesk.Max.Wrappers.Point3’ to type ‘Autodesk.Max.IAffineParts’.” if I execute the following code


g = (dotNetClass "Autodesk.Max.GlobalInterface").instance
parts = g.AffineParts.Create()
parts.T

the parts has the following properties


  .F : <System.Single>
  .Implementation : <System.Object>, read-only
  .INativeObject__Handle : <System.IntPtr>, read-only
  .K : <Autodesk.Max.IPoint3>
  .Q : <Autodesk.Max.IQuat>
  .T : <Autodesk.Max.IPoint3>
  .U : <Autodesk.Max.IQuat>

and everything seems to work fine, except from the Point3 properties. Is this probably a DotNet wrapper problem or something?

Your implementation looks correct, I have the same code in C++ working fine for me.
But the original interpolation method doesn’t care about of axis scale interpolation.

For example, if you rotate one matrix and then scale it not in the local space, this interpolation will fail.
In order to get a correct result for scale part you should add an interpolation of rotation stretch component as you did it for regular rotation. It’s a “U” component of AffineParts.


  tempStretchRot = global.Slerp(tempStretchRot , parts.U, percentage);

Thanks Daniel for your reply,

here is an update to the code that kinda works


using Autodesk.Max;
using System.IO;

namespace realtimeuk
{
    public class math
    {
        static private IGlobal global = GlobalInterface.Instance;
        static private string contents = "";

        static private IMatrix3 comp_affine( IAffineParts ap, IMatrix3 mat )
        {
            IMatrix3 tm = global.Matrix3.Create();
            tm.IdentityMatrix();
            mat.IdentityMatrix();
            mat.SetTranslate(ap.T);

            if (ap.F != 1.0f)				// has f component
            {
                tm.SetScale(global.Point3.Create(ap.F, ap.F, ap.F));
                mat = tm.Multiply(mat);
            }
            if (!ap.Q.IsIdentity)			// has q rotation component
            {
                ap.Q.MakeMatrix(mat, false);
                mat = tm.Multiply(mat);
            }
            if (ap.K.X != 1.0f || ap.K.Y != 1.0f || ap.K.Z != 1.0f)		// has k scale component
            {
                tm.SetScale(ap.K);
                if (!ap.U.IsIdentity)			// has u rotation component
                {
                    IMatrix3 utm = global.Matrix3.Create();
                    utm.IdentityMatrix();
                    ap.U.MakeMatrix(utm, false);
                    mat = global.Inverse(utm).Multiply(tm).Multiply(utm).Multiply(mat);
                }
                else
                {
                    mat = tm.Multiply(mat);
                }
            }

            return mat;
        }
        static public IMatrix3 InterpolateMatrix3(IMatrix3 matA, IMatrix3 matB, float percentage)
        {
            IAffineParts partsA = global.AffineParts.Create();
            global.DecompAffine(matA, partsA);
            partsA.Q.Normalize();
            IPoint3 tempPos = partsA.T;
            IQuat tempRot = partsA.Q;
            IPoint3 tempScl = partsA.K;
            IQuat tempStretchRot = partsA.U;

            IAffineParts partsB = global.AffineParts.Create();
            global.DecompAffine(matB, partsB);
            partsB.Q.Normalize();
            partsB.Q.MakeClosest(tempRot);
            tempPos = tempPos.Add(partsB.T.Subtract(tempPos).MultiplyBy(percentage));
            tempRot = global.Slerp(tempRot, partsB.Q, percentage);
            tempScl = tempScl.Add(partsB.K.Subtract(tempScl).MultiplyBy(percentage));
            tempStretchRot = global.Slerp(tempStretchRot, partsA.U, percentage);

            partsB.T = tempPos;
            partsB.Q = tempRot;
            partsB.K = tempScl;
            partsB.U = tempStretchRot;


            contents += "$test.rotation = quat " + tempStretchRot.X.ToString() + " " + tempStretchRot.Y.ToString() + " " + tempStretchRot.Z.ToString() + " " + tempStretchRot.W.ToString() + "
";
            contents += "$test.rotation = quat " + tempRot.X.ToString() + " " + tempRot.Y.ToString() + " " + tempRot.Z.ToString() + " " + tempRot.W.ToString() + "
";
            contents += "$test.scale = [" + tempScl.X.ToString() + ", " + tempScl.Y.ToString() + ", " + tempScl.Z.ToString() + "]
";
            contents += "$test.pos = [" + tempPos.X.ToString() + ", " + tempPos.Y.ToString() + ", " + tempPos.Z.ToString() + "]
";


            IMatrix3 mat = global.Matrix3.Create();
            mat.Zero();
            mat = comp_affine(partsB, mat);

            contents += "$test.transform = matrix3 ";
            contents += "[" + mat.GetColumn3(0).X.ToString() + ", " + mat.GetColumn3(0).Y.ToString() + ", " + mat.GetColumn3(0).Z.ToString() + "] ";
            contents += "[" + mat.GetColumn3(1).X.ToString() + ", " + mat.GetColumn3(1).Y.ToString() + ", " + mat.GetColumn3(1).Z.ToString() + "] ";
            contents += "[" + mat.GetColumn3(2).X.ToString() + ", " + mat.GetColumn3(2).Y.ToString() + ", " + mat.GetColumn3(2).Z.ToString() + "] ";
            contents += "[" + mat.GetColumn3(3).X.ToString() + ", " + mat.GetColumn3(3).Y.ToString() + ", " + mat.GetColumn3(3).Z.ToString() + "]
";
            File.WriteAllText(@"C:\path.txt", contents);


            return mat;
        }
    }
}

First of all, I’ve tried to add your code, did I did it right cause I don’t really understand how it works.

with this code I get the following results.

  1. Only translation
    returns wrong Matrix but the printing on the text file, returns the correct position.
  2. With rotation and scale
    returns wrong Matrix but the printing on the text file, returns correct position and scale but wrong rotation.

In any case, the comp_affine function doesn’t seem to work not even for a simple translation. any ideas?

First of all, that line

 tempStretchRot = global.Slerp(tempStretchRot, partsA.U, percentage);

should be replaced with that one

 tempStretchRot = global.Slerp(tempStretchRot, partsB.U, percentage);

about the other problems,
I don’t work with C# Api, so I can not really say at first sight if there are some broken methods.

Hm, I think I’m stack with C#…

Can you please take a look the following MXS code?


--because MXS doesn't have a makeClosest function
fn makeClosest prevRot nextRot = (
	prevDotNetQuat = ((dotNetClass "Autodesk.Max.GlobalInterface").instance).quat.create prevRot.x prevRot.y prevRot.z prevRot.w
	nextDotNetQuat = ((dotNetClass "Autodesk.Max.GlobalInterface").instance).quat.create nextRot.x nextRot.y nextRot.z nextRot.w
	newDotNetQuat = nextDotNetQuat.MakeClosest prevDotNetQuat
	quat newDotNetQuat.x newDotNetQuat.y newDotNetQuat.z newDotNetQuat.w
)

(
	start = 2
	end = 4
	sampleRate = 1
	samples = for t=start to end+1 by sampleRate collect at time t $source.transform
	
	cTime = (currentTime as float)/ticksPerFrame
	samplePostMultiplied = (1/sampleRate)
	
	modResult = mod cTime sampleRate
	prevS = ((cTime - modResult - start)*samplePostMultiplied)+1
	nextS = prevS + 1

	prevTM = samples[prevS]
	nextTM = samples[nextS]
	nextF = modResult * samplePostMultiplied
	prevF = 1 - nextF
	
	
	nextRot = makeClosest prevTM.rotationPart nextTM.rotationPart
	rotationValue = slerp prevTM.rotationPart nextRot nextF
	returnMatrix = rotate (matrix3 1) rotationValue
	returnMatrix.row1 *= ((length prevTM.row1)*prevF + (length nextTM.row1)*nextF)
	returnMatrix.row2 *= ((length prevTM.row2)*prevF + (length nextTM.row2)*nextF)
	returnMatrix.row3 *= ((length prevTM.row3)*prevF + (length nextTM.row3)*nextF)
	returnMatrix.pos = prevTM.pos * prevF + nextTM.pos * nextF
	$interpolated.transform = returnMatrix
)

It almost works… ( interpolation between two keyframes with sample 1)

I’ve also attached the max file.