Notifications
Clear all

[Closed] Aligning matrices along a spline path?

a complete and slightly tidier 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 GetSplineCurveBaseTransform spl si = 
(
	tm = BasisFromZDir2 (tangentCurve3D spl si 0.0);
	tm.translation = getKnotPoint spl si 1;	
	tm;
)

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;
	transpose_base_tm = transposeMat base_tm;
	for k = 2 to numFrenets do
	(
		location = positions[k] * inverse_base_tm;
		tangent = tangents[k] * transpose_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)

		xDir = Normalize (xDir * rotatetm);
		yDir = Normalize (yDir * rotatetm);
		zDir = Normalize (zDir * rotatetm);
			
		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

Hi, this is possibly overkill but I’ve used a similar approach to space Christmas lights along a path. One thing led to another and now I’ve got a script wrapped around it. You can try it out here.
[VEROLD]5498a7610725bf3b2f00299c[/VEROLD]

For arbitrary points along the spline, Klunk’s code can be shortened to:

fn getMatricesAlongSplineCurve spl curve count =
(
	local lastTangent = tangentCurve3D spl curve 0
	local lastRot = arbAxis lastTangent as quat
	local step = 1. / count

	for i = 1 to count collect
	(
		local location = interpCurve3D spl curve (i * step)
		local tangent = tangentCurve3D spl curve (i * step)

		local axis = normalize (cross tangent lastTangent)
		local theta = acos (dot tangent lastTangent)
		local rotation = quat theta axis

		lastTangent = tangent
		lastRot *= rotation
		translate (lastRot as matrix3) location
	)
)

/*
fn testgetPathMatrices spl curve count =
(
	local TMs = getMatricesAlongSplineCurve spl curve count

	for TM in TMs do
		Point transform:TM size:10 wirecolor:green axisTripod:on cross:off
)

testgetPathMatrices $Line01 1 15
*/

For arbitrary points along the spline, Klunk’s code can be shortened to…

Hm… I actually seem to be getting different results using your code than I do with Klunk’s.

Well, I have used arbAxis for initial transform where Klunk uses custom BasisFromZDir2 function – there’s also a glitch present in that function, when zDir is [0,0,1], it will normalize the result of yDir to [1,0,0] so the following test yDir == [0,0,0] will never return true. And of course you’d have to compare on a spline with all segments of the same length, otherwise there’ll always be differences – every matrix depends on the previous matrix and its placement, the differences will be small with big number of points, huge with few of them.

Okay, so question… how would I modify either of the scripts so that I could start a spline with a specific axis rotation?

For example, say I have a second spline starting partway along the first, and want spline 2 to behave as if it is a continuation of spline 1. I can now get the matrix at the exact point where along spline 1 where spline2 begins, but I don’t know how to make spline2 use that as its starting rotation.

Something like this should do the trick:

fn getMatrixByFraction spl curve fraction &lastTangent &lastRot =
(
	local location = interpCurve3D spl curve fraction
	local tangent = tangentCurve3D spl curve fraction

	local axis = normalize (cross tangent lastTangent)
	local theta = acos (dot tangent lastTangent)
	local rotation = quat theta axis

	lastTangent = tangent
	lastRot *= rotation
	translate (lastRot as matrix3) location
)

fn getMatricesAlongSplineCurve spl curve count refTM: =
(
	if refTM != unsupplied then 
	(
		local lastTangent = refTM.row3
		local lastRot = refTM as quat
		getMatrixByFraction spl curve 0 &lastTangent &lastRot
	)
	else
	(
		local lastTangent = tangentCurve3D spl curve 0
		local lastRot = arbAxis lastTangent as quat
	)

	local step = 1. / count

	for i = 1 to count collect
		getMatrixByFraction spl curve (i * step) &lastTangent &lastRot
)

Hm, could you whip up another test function to demonstrate how these are used?

Still the same, only add refTM:

fn testgetPathMatrices spl curve count refTM: =
(
	local TMs = getMatricesAlongSplineCurve spl curve count refTM:refTM

	for TM in TMs do
		Point transform:TM size:10 wirecolor:green axisTripod:on cross:off
)

testgetPathMatrices $Line01 1 15 refTM:((quat 60 [1,2,3]) as matrix3)

Hm. I tried adapting the function for my current needs, but I may have messed something up, because while the results are usually close to what I need, they don’t /quite/ match up.

Would you mind looking this over and telling me where I’m going wrong? The green points show the orientations I am looking for, and the blue ones show what I’m getting using refTM:

/*******************************************************************************/
– OTHER FUNCTIONS

fn squigglyLine segs:(random 33 100) tm:(matrix3 1) minDist:1 maxDist:10 r:45 = (
lin = splineShape()
spl = addNewSpline lin

 for s = 1 to segs do (
     addKnot lin spl #corner #line tm.pos
     offset = (eulerAngles (random -r r) (random -r r) (random -r r))
     preTranslate (preRotate tm offset) [random (minDist) maxDist, 0, 0]
 )

 updateShape lin
 return lin

)

fn threePointMatrix3 p1 p2 p3 = (
front = normalize (p2 – p1)
dir = normalize (p3 – p1)
side = normalize (cross dir front)
up = normalize (cross front side)
matrix3 front side up p1
)

fn randomP3 r:100.0 = ([random -r r, random -r r, random -r r])

fn randomTM origin: = (
tm = threePointMatrix3 (randomP3()) (randomP3()) (randomP3())
if origin != unsupplied do tm[4] = origin
tm
)

fn getUVal p1 p2 u = (p1 + ((p2 – p1) * u))
/*******************************************************************************/

fn splineKnotMatrices lin spl refTM:(matrix3 1) show:false wc: = (
local knotCount = numKnots lin spl
local lastTangent = refTM[3]
local lastRot = refTM as quat

 values = for k = 1 to knotCount collect (
     local pos = getKnotPoint lin spl k

     local tangent = (
         if k &lt; knotCount then 
             tangentBezier3D lin spl k 0.0 pathParam:true
         else
             tangentBezier3D lin spl (k - 1) 1.0 pathParam:true
     )

     local axis = normalize (cross tangent lastTangent)
     local theta = acos (dot tangent lastTangent)
     local rot = quat theta axis

     lastTangent = tangent
     lastRot *= rot
     translate (lastRot as matrix3) pos
 )

 if show do (
     if wc == unsupplied do wc = color (random 0 255) (random 0 255) (random 0 255) 
     for tm in values do point transform:tm size:10 wireColor:wc axisTripod:on box:off cross:off
 )

 values

)

(
– TEST SCENE
delete objects
lin1 = squigglyLine segs:10 r:30 minDist:10.0 maxDist:50.0 tm:(randomTM())
m1 = splineKnotMatrices lin1 1 show:true wc:red

 b = random 2 ((numKnots lin1) - 1)
 u = random 0.0 1.0

 lin2 = squigglyLine segs:10 r:45 minDist:5 maxDist:50 tm:(randomTM origin:(getUVal m1[b][4] m1[b + 1][4] u))
 m2 = splineKnotMatrices lin2 1 show:true wc:blue refTM:m1[b]

 lin3 = line()
 spl = addNewSpline lin3
 for k = 1 to b do addKnot lin3 spl #corner #line m1[k][4]
 for k = 1 to 10 do addKnot lin3 spl #corner #line m2[k][4]

 m3 = splineKnotMatrices lin3 1 show:true wc:green

)

Page 2 / 3