Notifications
Clear all

[Closed] Aligning matrices along a spline path?

What techniques would anyone recommend for getting the “best” series of matrices along a curving spline (i.e. with the least amount of twisting)? I’ve tried several but have not really had any luck.

In the particular case I am working on right now, 180 degree flipping would not be a serious issue, though a solution that prevents that would obviously be better than one which does not…

23 Replies

You could place the first matrix using an upvector, and all subsequent ones using the previous matrix. If placed densely enough, you should not get flipping, right?

– MartinB

they are called frenets in the max world

  this is an extension  function I use to extract them it (the original code is what max uses to extrude along a spline) I should be pretty easy to port to mxs basisfinder is very similar to
  MatrixFromNormal
//********************************************************************************************
      // getShapeFrenets
      
      static BasisFinder theBasisFinder;
      
      def_visible_primitive(getShapeFrenets, "getShapeFrenets");
      
      // <tm array> getShapeFrenets      <shape> <spline> <numsegs> <align> <zrotation>
      
      Value* getShapeFrenets_cf(Value **arg_list, int count)
      {
      	check_arg_count(getShapeFrenets, 5, count);
      	INode* node = get_valid_node((MAXNode*)arg_list[0], getShapeFrenets);
      	Object* obj = node->GetObjectRef();
      
      	if (obj->SuperClassID() == GEN_DERIVOB_CLASS_ID)
      		obj = ((IDerivedObject*)obj)->FindBaseObject();
      	
      	if (obj->ClassID() != splineShapeClassID && obj->ClassID() != Class_ID(SPLINE3D_CLASS_ID,0))
      		throw RuntimeError (GetString(IDS_SHAPE_OPERATION_ON_NONSPLINESHAPE), obj->GetObjectName());
      	
      	SplineShape* s = (SplineShape*)obj;
      	int index = arg_list[1]->to_int();
      	if (index < 1 || index > s->shape.splineCount)
      		throw RuntimeError (GetString(IDS_SHAPE_SPLINE_INDEX_OUT_OF_RANGE), Integer::intern(index));
      	Spline3D *pSpline = s->shape.splines[index-1];
      
      	int numSegs = arg_list[2]->to_int();
      	BOOL align = arg_list[3]->to_bool();
      	float rotateAroundZ =  arg_list[4]->to_float();;
      
      	Matrix3 relativeTransform = node->GetObjectTM(MAXScript_time());
      	relativeTransform.Invert();
      
      	one_typed_value_local(Array* result);
      	vl.result = new Array(numSegs+1);
      
      	int numIntervals = pSpline->Closed() ? numSegs+1 : numSegs;
      	float denominator = float(numIntervals);
      	Point3 xDir, yDir, zDir, location, tangent, axis;
      	float position, sine, cosine, theta;
      	Matrix3 rotation;
      
      	// Find initial x,y directions:
      	location = relativeTransform * pSpline->InterpCurve3D(0.0f);
      	tangent = relativeTransform.VectorTransform(pSpline->TangentCurve3D(0.0f));
      	Point3 lastTangent = tangent;
      	
      	Matrix3 inverseBasisOfSpline(1);
      	if(align) 
      	{
      		theBasisFinder.BasisFromZDir(tangent, xDir, yDir);
      		if (rotateAroundZ) 
      		{
      			Matrix3 rotator(1);
      			rotator.SetRotate(AngAxis (tangent, rotateAroundZ));
      			xDir = xDir * rotator;
      			yDir = yDir * rotator;
      		}
      		Matrix3 basisOfSpline(1);
      		basisOfSpline.SetRow (0, xDir);
      		basisOfSpline.SetRow (1, yDir);
      		basisOfSpline.SetRow (2, tangent);
      		basisOfSpline.SetTrans (location);
      		inverseBasisOfSpline = Inverse (basisOfSpline);
      		lastTangent = Point3(0,0,1);
      	} 
      	else 
      	{
      		inverseBasisOfSpline.SetRow (3, -location);
      	}
      
      	// Make relative transform take the spline from its own object space to our object space,
      	// and from there into the space defined by its origin and initial direction:
      	relativeTransform = relativeTransform * inverseBasisOfSpline;
      	// (Note left-to-right evaluation order: Given matrices A,B, point x, x(AB) = (xA)B
      
      	// The first transform is necessarily the identity:
      
      	vl.result->append(new Matrix3Value(1));
      	//tFrenets[0].IdentityMatrix ();
      
      	// Set up xDir, yDir, zDir to match our first-point basis:
      	xDir = Point3 (1,0,0);
      	yDir = Point3 (0,1,0);
      	zDir = Point3 (0,0,1);
      
      	for(int i = 1; i <= numIntervals; i++) 
      	{
      		position = float(i) / denominator;
      		location = relativeTransform * pSpline->InterpCurve3D (position);
      		tangent = relativeTransform.VectorTransform(pSpline->TangentCurve3D(position));
      
      		// This is the procedure we follow at each step in the path: find the
      		// orthonormal basis with the right orientation, then compose with
      		// the translation putting the origin at the path-point.
      
      		// As we proceed along the path, we apply minimal rotations to
      		// our original basis to keep the Z-axis tangent to the curve.
      		// The X and Y axes follow in a natural manner.
      
      		// xDir, yDir, zDir still have their values from last time...
      		// Create a rotation matrix which maps the last tangent onto the current tangent:
      		axis = lastTangent ^ tangent;	// gives axis, scaled by sine of angle.
      		sine = FLength(axis);	// positive - keeps angle value in (0,PI) range.
      		cosine = DotProd (lastTangent, tangent);	// Gives cosine of angle.
      		theta = atan2f (sine, cosine);
      		rotation.SetRotate(AngAxis (Normalize(axis), theta));
      		xDir = Normalize(rotation * xDir);
      		yDir = Normalize(rotation * yDir);
      		zDir = Normalize(rotation * zDir);
      		lastTangent = tangent;
      
      		Point3 temp = Normalize(axis ^ tangent);
      
      		if(i <= numSegs) 
      			//vl.result->append(new Matrix3Value(Normalize(axis),temp,Normalize(tangent),location));
      			vl.result->append(new Matrix3Value(xDir,yDir,zDir,location));
      
      	}
      
      	/* following code not needed for now - treating all splines as open.
      	if (!pSpline->Closed ()) return;
      
      	// If we have a closed loop, our procedure may result in X,Y vectors
      	// that are twisted severely between the first and last point.
      	// Here we correct for that by pre-transforming each frenet transform
      	// by a small rotation around the Z-axis.  (The last pass through the above
      	// loop should give the final twist: the initial and final Z-axis directions
      	// are the same, and the difference between the initial and final X-axis
      	// directions is the total twist along the spline.)
      
      	// Now we measure the difference between our original and final x-vectors:
      	axis = originalXDir ^ xDir;
      	sine = FLength (axis);
      	cosine = DotProd (originalXDir, xDir);
      	theta = atan2f (sine, cosine);
      	for (i=1; i<=numSegs; i++) {
      		rotation.SetRotate (AngAxis (Normalize(axis), -theta/denominator));
      		tFrenets[i] = rotation * tFrenets[i];
      	}
      */	
      	return_value(vl.result);
      }
here ya go I thought I may have a mxs version

   fn BasisFromZDir2 zDir =
  (	
  	yDir = normalize(cross [0,0,1] zDir);
  	if yDir == [0,0,0] then
  		yDir = normalize(cross [0,1,0] zDir);
  	xDir = normalize(cross zDir yDir); 
  	matrix3 xDir yDir zDir [0,0,0];
  )
 
 fn transposeMat inMat =
 (
 	outMat = matrix3 0;
 	outMat.row1 = [inMat.row1.x,inMat.row2.x,inMat.row3.x];	
 	outMat.row2 = [inMat.row1.y,inMat.row2.y,inMat.row3.y];	
 	outMat.row3 = [inMat.row1.z,inMat.row2.z,inMat.row3.z];
 	outMat;
 )	
 
  fn IsSplineSegmentOptimized spl si seg =
(
	res = false;
	if spl.optimize then
		res = true;
	
	if getKnotType spl si seg == #corner and getKnotType spl si (seg + 1) == #corner then
		res = true;
	else if getSegmentType spl si seg == #line  then
		res = true;
	else
	(
		p2i = seg + 1;
		if seg == numsegments spl si and isClosed spl si then
			p2i = 1;
		p1 = getKnotPoint spl si seg;
		p2 = getKnotPoint spl si p2i;
		d1 = getOutVec spl si seg;
		d2 = getInVec spl si p2i;
		if dot (normalize (d1 - p1)) (normalize (p2 - d2)) < 0.9975 then -- a guesstimate value but seems to work ok
			res = false;
	)	
	res;
)	

  fn getSplineValues spl si func just_the_knots:false =
  (
  	values = #();
  	numsegs = numSegments spl si;
  	if just_the_knots then
  	(
  		for i = 1 to numsegs do
  			append 	values (func spl si i 0.0 pathParam:true);
  		
  		if not isClosed spl si then
  			append values (func spl si numsegs 1.0 pathParam:true);
  	)
  	else
  	(
  		numsteps = spl.steps
  		multi = 1.0/(numsteps + 1.0);
  		for i = 1 to numsegs do
  		(
  			if IsSplineSegmentOptimized spl si i then -- if optimize just the starting point of the segment is collected
  				append 	values (func spl si i 0.0 pathParam:true);
  			else
  				for j = 0 to numsteps do
  					append values (func spl si i (j * multi) pathParam:true);
  		)			
  		if not isClosed spl si then
  			append values (func spl si numsegs 1.0 pathParam:true);
  	)	
  	values;
  )
  
  --****************************************************************************************************
  
  fn getPathFrenets spl si base_tm =
  (
  -- collect interpolated curve values	
  	
  	positions = getSplineValues spl si interpBezier3D;
  	tangents = getSplineValues spl si tangentBezier3D;
  	
  	numFrenets = positions.count;
  	Frenets = #()
  	Frenets.count = numFrenets;
  	
  -- initialize the frenet array 
  	
  	Frenets[1] = matrix3 1;
  	
  -- inialize the previous value to match the initial frenet too	
  	
  	lastTangent = [0,0,1];
  	xDir = [1,0,0];
  	yDir = [0,1,0];
  	zDir = [0,0,1];
  	
  	inverse_base_tm = Inverse base_tm;
  	for k = 2 to numFrenets do
  	(
  		location = positions[k] * (inverse_base_tm);
  		tangent = tangents[k] * (transposeMat base_tm);
  
  		axis = cross lastTangent tangent;
  		sine =  radtodeg (Length axis);
  		cosine = radtodeg (dot lastTangent tangent);
  		theta = atan2 sine cosine;
  		rotatetm =  inverse ((angleaxis theta (Normalize axis)) as matrix3)
  		
  		new_xDir = Normalize (xDir * rotatetm);
  		new_yDir = Normalize (yDir * rotatetm);
  		new_zDir = Normalize (zDir * rotatetm);
  
  		--format "x:% y:%
" (dot new_xDir xDir) (dot new_yDir yDir)
  		
  		xDir = new_xDir;
  		yDir = new_yDir;
  		zDir = new_zDir;
  			
  		lastTangent = tangent;
  		Frenets[k] = (matrix3 xDir yDir zDir location);	
  	)	
  	
  	/*if isClosed spl si then
  	(
  		curveparams = GetCurveParamsFromPositions  positions;
  		axis = cross [1,0,0] xDir;
  		sine =  radtodeg (Length axis);
  		cosine = radtodeg (dot [1,0,0] xDir);
  		theta = atan2 sine  cosine;
  		for k = 2 to numFrenets do
  		(
  			rotatetm = ((angleaxis (theta * curveparams[k]) (Normalize axis)) as matrix3);
  			Frenets[k] = rotatetm * Frenets[k];
  		)	
  	)*/
  	Frenets
  )	
  
  fn testgetPathFrenets spl si =
  (	
  	base =  prerotatez (GetSplineCurveBaseTransform spl si) 0.0;
  	tms = getPathFrenets spl si base;
  	for i in tms do
  	(	
  		tm = i * base;
  		point transform:tm size:10 wirecolor:black axistripod:on cross:off;
  	)			
  )
  
  testgetPathFrenets $Line01 1 
    

Thanks, Klunk!

Wow, that turns out to be quite a bit more complicated than I expected! I’ll try and get my head around it, but I would definitely have not ever figured this one out on my own…

Hmm, it appears that you may have left a function out. GetSplineCurveBaseTransform is not defined, so the test function does not work…

And if you simply use a Path Constraint with “Follow” on a helper object from which you grab the transforms, you don’t get your desired result?

That doesn’t really seem like the most efficient way to do it.

For example, if I am building a bunch of trees using multi-dimensonal arrays of point3 values, it would require converting each series of points into a spline, creating a helper object, constraining the object to the spline, moving it repeatedly, and finally storing the matrix3 values, for every single branch of each tree.

Using a math based solution that bypasses all of that seems extremely preferable…

Ok, so I’m not working with any curved splines here, I just needed something that would give me the aligned matrices for particular points in space. However, I’ve gotten the script working even if I only understand about half of it right now.

The GetSplineCurveBaseTransform function turned out to be something I could easily just replace with a known value, and I’m working on breaking down the remaining parts that I don’t yet fully comprehend.

sorry… i shifted it all to a new file as there was a load of stuff not need


fn GetSplineCurveBaseTransform spl si = 
(
	tm = BasisFromZDir2 (tangentCurve3D spl si 0.0);
	tm.translation = getKnotPoint spl si 1;	
	tm;
)

Ah good, it’s basically the same as the one I wrote to replace it! I guess that means I have a good handle on how it all works

Page 1 / 3