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 FUNCTIONSfn squigglyLine segs:(random 33 100) tm:(matrix3 1) minDist:1 maxDist:10 r:45 = (
lin = splineShape()
spl = addNewSpline linfor 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 quatvalues = 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:redb = 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
)