Aligning matrices along a spline path?


#5

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


#6

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?


#7

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…


#8

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.


#9

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;
)


#10

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 :slight_smile:


#11

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

#12

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]


#13

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
*/

#14

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.


#15

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.


#16

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.


#17

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
)

#18

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


#19

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)

#20

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

)


#21

It would be better if you explained what logic you want to follow, I’m afraid I don’t quite get it. Using refTM, they will be different, they are based on a matrix (or rather its z-axis component) that’s different from unit matrix. When the two directions will be similar, the results will be somewhat similar, too, when not… well, see the attached picture, what component of the green XYZ system would you like to keep and why?


#22

The simplest and clearest answer is that I want the matrices of the second line to be treated the same as if it were a continuation of the first line immediately after the point where it connects.


#23

And what criteria do you have for that, that aren’t met? Because they are treated and computed as such, the only thing that would be different if it were a continuous spline would be that here you’re giving it a source matrix at the point of connection (knot ‘b’) and if it were a single spline, you’d be giving it matrix at point ‘b-1’, i.e. the previous point on the line.


#24

Um. I have tried to adapt the function to my current needs, but I can’t do that so that the Y-axis always has an angle of 90 with the UpVector [0.0.1] with save Direction.


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 $Shape002 1 20