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

# Aligning matrices along a spline path?

**martinB**#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?

**Malkalypse**#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…

**Malkalypse**#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.

**Klvnk**#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;
)
```

**Malkalypse**#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

**Klvnk**#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
```

**grabjacket**#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]

**Swordslayer**#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
*/
```

**Malkalypse**#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.

**Swordslayer**#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.

**Malkalypse**#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.

**Swordslayer**#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
)
```

**Swordslayer**#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)
```

**Malkalypse**#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 FUNCTIONSfn 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 < 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`

)

**Swordslayer**#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?

**Malkalypse**#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.

**Swordslayer**#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.

**freemanprof**#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
```