创建可变宽度的管

Creating a tube of variable width

我正在努力在 3ds Max 中编写脚本来创建一个重量沿其变化的管。

作为输入,我有这个管子应该通过的点,还有这些点的方向和宽度。这个管子应该是某种东西的可视化,对我来说重要的是将它作为一个对象以便稍后在其上映射纹理(例如,颜色将是另一个可视化参数)。

我对 maxscript 比较陌生,所以我可能会错过一个明显的方法,但到目前为止我发现只有挤压适用于该任务。

因为我需要在每一步都逐渐变细,所以我首先想到制作一堆初始圆圈,然后将它们挤压成管状。但是为了到达下一点,我需要根据管子的方向旋转圆圈。经过一番搜索后,我找到了一种旋转单个面的方法,但似乎我搞砸了向量到角度的转换,因为管子看起来不像它们应该的那样。

你能告诉我哪里错了吗,更重要的是,是否有更好的方法来绘制可变宽度的管子?

代码如下:

--first, matrix with all the points of 5 tubes. for each point the first three numbers are position, next 3 - direction, then width, but I don't use width in this code, since I encountered problems even befor approaching tapering.
tubeMatr = #(#(#(-0.252, -0.282, 0.02, 0.025, 0.953, 0.302, 181.2), #(-0.252, -0.282, 0.074, 0.045, 0.957, -0.183, 42.16), #(-0.252, -0.282, 0.128, 0.045, 0.927, -0.341, 25.83), #(-0.252, -0.282, 0.182, 0.013, 0.903, -0.407, 23.73), #(-0.252, -0.191, 0.02, 0.062, 0.955, 0.282, 317.7)), #(#(-0.252, -0.272, 0.023, 0.03, 0.952, 0.274, 192.8), #(-0.251, -0.272, 0.072, 0.06, 0.945, -0.195, 51.41), #(-0.251, -0.273, 0.124, 0.056, 0.916, -0.361, 27.39), #(-0.252, -0.273, 0.178, 0.026, 0.892, -0.427, 24.9), #(-0.251, -0.181, 0.023, 0.069, 0.955, 0.25, 309.6)), #(#(-0.251, -0.263, 0.026, 0.036, 0.95, 0.245, 203.5), #(-0.251, -0.263, 0.07, 0.073, 0.934, -0.204, 62.15), #(-0.251, -0.263, 0.121, 0.068, 0.906, -0.381, 29.05), #(-0.251, -0.264, 0.173, 0.039, 0.88, -0.447, 26.13), #(-0.25, -0.172, 0.025, 0.074, 0.958, 0.226, 304.1)), #(#(-0.251, -0.253, 0.028, 0.044, 0.947, 0.216, 213.4), #(-0.25, -0.253, 0.068, 0.084, 0.924, -0.21, 74.34), #(-0.25, -0.254, 0.117, 0.08, 0.895, -0.401, 30.81), #(-0.251, -0.255, 0.169, 0.051, 0.87, -0.467, 27.44), #(-0.25, -0.162, 0.028, 0.077, 0.961, 0.21, 300.6)), #(#(-0.25, -0.244, 0.03, 0.051, 0.943, 0.188, 223.0), #(-0.249, -0.244, 0.066, 0.094, 0.915, -0.214, 87.96), #(-0.249, -0.245, 0.113, 0.092, 0.885, -0.423, 32.69), #(-0.25, -0.246, 0.164, 0.063, 0.859, -0.486, 28.84), #(-0.249, -0.153, 0.03, 0.079, 0.965, 0.199, 298.8)), #(#(-0.25, -0.234, 0.032, 0.06, 0.939, 0.16, 232.4), #(-0.248, -0.235, 0.064, 0.103, 0.907, -0.215, 102.9), #(-0.248, -0.236, 0.109, 0.106, 0.875, -0.445, 34.7), #(-0.25, -0.238, 0.159, 0.075, 0.849, -0.505, 30.33), #(-0.248, -0.143, 0.032, 0.08, 0.971, 0.193, 298.3)), #(#(-0.249, -0.225, 0.034, 0.068, 0.934, 0.133, 241.8), #(-0.247, -0.226, 0.062, 0.11, 0.9, -0.214, 119.2), #(-0.247, -0.228, 0.104, 0.12, 0.865, -0.468, 36.87), #(-0.249, -0.229, 0.154, 0.087, 0.84, -0.525, 31.93), #(-0.247, -0.133, 0.034, 0.079, 0.973, 0.19, 301.0)), #(#(-0.249, -0.216, 0.035, 0.073, 0.933, 0.115, 246.9), #(-0.246, -0.217, 0.06, 0.111, 0.9, -0.192, 138.0), #(-0.246, -0.219, 0.1, 0.132, 0.857, -0.486, 40.28), #(-0.248, -0.221, 0.149, 0.098, 0.83, -0.544, 33.64), #(-0.247, -0.124, 0.035, 0.077, 0.973, 0.19, 305.7)), #(#(-0.248, -0.206, 0.036, 0.075, 0.937, 0.11, 247.8), #(-0.245, -0.208, 0.058, 0.101, 0.912, -0.136, 158.1), #(-0.245, -0.211, 0.095, 0.126, 0.866, -0.443, 54.04), #(-0.247, -0.213, 0.144, 0.101, 0.819, -0.558, 35.67), #(-0.246, -0.114, 0.037, 0.075, 0.974, 0.19, 311.5)), #(#(-0.247, -0.197, 0.037, 0.076, 0.941, 0.108, 249.5), #(-0.244, -0.199, 0.056, 0.094, 0.923, -0.09, 175.0), #(-0.244, -0.202, 0.09, 0.119, 0.878, -0.389, 70.1), #(-0.246, -0.204, 0.138, 0.102, 0.809, -0.566, 38.7), #(-0.245, -0.104, 0.039, 0.073, 0.974, 0.191, 318.5)), #(#(-0.246, -0.188, 0.039, 0.076, 0.947, 0.108, 251.9), #(-0.243, -0.19, 0.055, 0.088, 0.933, -0.051, 189.6), #(-0.242, -0.193, 0.086, 0.112, 0.895, -0.326, 88.15), #(-0.245, -0.196, 0.132, 0.102, 0.806, -0.555, 45.22), #(-0.244, -0.094, 0.041, 0.071, 0.974, 0.194, 326.7)), #(#(-0.246, -0.178, 0.04, 0.076, 0.952, 0.111, 254.9), #(-0.242, -0.18, 0.055, 0.083, 0.943, -0.017, 202.4), #(-0.241, -0.184, 0.083, 0.104, 0.914, -0.256, 107.8), #(-0.244, -0.188, 0.127, 0.101, 0.808, -0.531, 54.32), #(-0.244, -0.085, 0.043, 0.069, 0.974, 0.197, 336.0)), #(#(-0.245, -0.169, 0.041, 0.076, 0.959, 0.116, 258.3), #(-0.241, -0.171, 0.055, 0.079, 0.954, 0.015, 214.0), #(-0.24, -0.175, 0.081, 0.096, 0.935, -0.18, 128.4), #(-0.243, -0.18, 0.122, 0.098, 0.814, -0.496, 65.77), #(-0.243, -0.075, 0.045, 0.067, 0.973, 0.202, 346.5)), #(#(-0.244, -0.159, 0.042, 0.075, 0.966, 0.123, 262.3), #(-0.241, -0.161, 0.055, 0.075, 0.964, 0.044, 224.6), #(-0.239, -0.166, 0.079, 0.087, 0.952, -0.107, 150.9), #(-0.242, -0.172, 0.117, 0.094, 0.824, -0.452, 79.27), #(-0.242, -0.065, 0.047, 0.065, 0.973, 0.208, 358.3)), #(#(-0.243, -0.149, 0.043, 0.073, 0.974, 0.133, 266.8), #(-0.24, -0.152, 0.055, 0.071, 0.975, 0.073, 234.7), #(-0.238, -0.156, 0.078, 0.078, 0.968, -0.041, 171.8), #(-0.241, -0.164, 0.112, 0.09, 0.836, -0.4, 94.45), #(-0.242, -0.055, 0.049, 0.063, 0.972, 0.213, 366.9)), #(#(-0.243, -0.14, 0.044, 0.072, 0.982, 0.146, 271.9), #(-0.239, -0.142, 0.056, 0.068, 0.986, 0.103, 244.5), #(-0.238, -0.146, 0.077, 0.071, 0.984, 0.022, 190.9), #(-0.24, -0.155, 0.108, 0.085, 0.851, -0.342, 110.9), #(-0.241, -0.046, 0.051, 0.063, 0.972, 0.216, 371.1)), #(#(-0.242, -0.13, 0.046, 0.07, 0.982, 0.149, 280.5), #(-0.238, -0.132, 0.057, 0.066, 0.987, 0.115, 256.8), #(-0.237, -0.137, 0.078, 0.066, 0.994, 0.067, 209.9), #(-0.24, -0.147, 0.105, 0.079, 0.867, -0.28, 128.2), #(-0.24, -0.036, 0.053, 0.063, 0.971, 0.219, 375.5)), #(#(-0.241, -0.12, 0.047, 0.068, 0.981, 0.153, 290.0), #(-0.238, -0.122, 0.058, 0.064, 0.987, 0.124, 270.2), #(-0.236, -0.127, 0.078, 0.064, 0.992, 0.083, 230.4), #(-0.239, -0.138, 0.102, 0.074, 0.881, -0.223, 146.1), #(-0.24, -0.026, 0.056, 0.063, 0.97, 0.223, 380.0)), #(#(-0.241, -0.11, 0.049, 0.066, 0.981, 0.158, 300.3), #(-0.237, -0.112, 0.059, 0.062, 0.986, 0.132, 284.2), #(-0.236, -0.117, 0.079, 0.061, 0.99, 0.098, 251.1), #(-0.238, -0.129, 0.1, 0.069, 0.886, -0.188, 165.3), #(-0.239, -0.017, 0.058, 0.063, 0.97, 0.227, 384.7)), #(#(-0.24, -0.1, 0.051, 0.064, 0.98, 0.164, 311.4), #(-0.237, -0.102, 0.061, 0.06, 0.985, 0.142, 298.8), #(-0.235, -0.107, 0.08, 0.059, 0.988, 0.113, 272.0), #(-0.237, -0.121, 0.098, 0.064, 0.89, -0.156, 184.9), #(-0.238, -0.007, 0.06, 0.063, 0.969, 0.232, 389.4)))

fn SetObjectRotation obj rx ry rz =
(
-- Reset the object's transformation matrix so that
-- it only includes position and scale information.
-- Doing this clears out any previous object rotation.
local translateMat = transMatrix obj.transform.pos
local scaleMat = scaleMatrix obj.transform.scale
obj.transform = scaleMat * translateMat

-- Perform each axis rotation individually
rotate obj (angleaxis rx [1,0,0])
rotate obj (angleaxis ry [0,1,0])
rotate obj (angleaxis rz [0,0,1])
)
--make empty array for tubes
extrudeMatr = #()
for i = 1 to ntubes do extrudeMatr[i] = 0

--make init circles
for j = 1 to 5 do--ntubes do 
(
    b=Circle radius:10 pos:[(tubeMatr[1][j][1] )*1000, (tubeMatr[1][j][2] )*1000,(tubeMatr[1][j][3] )*1000] isSelected:on
    convertTo b Editable_Poly
    SetObjectRotation b 0 (acos (tubeMatr[1][j][6] as float))  (atan2 (tubeMatr[1][j][5] as float)  (tubeMatr[1][j][4] as float))
    extrudeMatr[j]=b
)

--length of step
stepVec=#(tubeMatr[2][1][1]- tubeMatr[1][1][1] , tubeMatr[2][1][2] - tubeMatr[1][1][2], tubeMatr[2][1][3] - tubeMatr[1][1][3])
stepLength= sqrt (stepVec[1]^2+stepVec[2]^2+stepVec[2]^2)


--make tubes

diffAng=#()
for i = 1 to nsteps-1 do 
(
    for j = 1 to 5 do--ntubes do 
    (

        extrudeMatr[j].EditablePoly.SetSelection #Face #{1}
        extrudeMatr[j].extrudeFaces 5
        update extrudeMatr[j]
     -- I change the rotation of the faces based on the difference between angles of two vectors. This is the point I'm less confident. 
        diffAng[i]=#((atan2 tubeMatr[i+1][j][5]  tubeMatr[i+1][j][4]) - (atan2 (tubeMatr[i][j][5])  (tubeMatr[i][j][4] )) ,  (acos (tubeMatr[i+1][j][6] ))-(acos (tubeMatr[i][j][6] )))  

        extrudeMatr[j].EditablePoly.SetSelection #Face #{1}
--next chunk performs the rotation of the face. I found it on one of the cg forums and it seems legit. Though, I might use it wrong
        (
        angle =diffAng[i][1]
        selected_faces = (polyOp.getFaceSelection extrudeMatr[j]) as array
        center_face = polyOp.getFaceCenter extrudeMatr[j] selected_faces[1]
        transform_mat = matrixFromNormal (polyOp.getFaceNormal extrudeMatr[j] selected_faces[1])
        transform_mat.row4 = center_face --move TM to center of polygon

        face_vertices = (polyOp.getVertsUsingFace extrudeMatr[j] #selection) as array

        for v in face_vertices do
        (
        in coordsys transform_mat --get vertex in Normal's TM coordinates
        vertex_position = (polyOp.getVert extrudeMatr[j] v)
        -- create a Rotation TM, transform in the Normal's TM:
        RotateZ = (rotateZMatrix angle) * transform_mat
        -- finally, set the vertex to the position transformed by the rotation matrix
        polyOp.setVert extrudeMatr[j] v (vertex_position * RotateZ)
        )
        angle = diffAng[i][2]
        for v in face_vertices do
        (
        in coordsys transform_mat --get vertex in Normal's TM coordinates
        vertex_position = (polyOp.getVert extrudeMatr[j] v)
        -- create a Rotation TM, transform in the Normal's TM:
        RotateZ = (rotateYMatrix angle) * transform_mat
        -- finally, set the vertex to the position transformed by the rotation matrix
        polyOp.setVert extrudeMatr[j] v (vertex_position * RotateZ)
        )

        )

    )
)

我也尝试用样条曲线来做,但是在旋转面部之后一切都变得糟糕(因为挤压是相对于圆形方向的),所以我放弃了那个方法。然而,这里的样条显示了管子应该去的地方(关心形状,而不是长度):

    --first, same matrix
    tubeMatr = #(#(#(-0.252, -0.282, 0.02, 0.025, 0.953, 0.302, 181.2), #(-0.252, -0.282, 0.074, 0.045, 0.957, -0.183, 42.16), #(-0.252, -0.282, 0.128, 0.045, 0.927, -0.341, 25.83), #(-0.252, -0.282, 0.182, 0.013, 0.903, -0.407, 23.73), #(-0.252, -0.191, 0.02, 0.062, 0.955, 0.282, 317.7)), #(#(-0.252, -0.272, 0.023, 0.03, 0.952, 0.274, 192.8), #(-0.251, -0.272, 0.072, 0.06, 0.945, -0.195, 51.41), #(-0.251, -0.273, 0.124, 0.056, 0.916, -0.361, 27.39), #(-0.252, -0.273, 0.178, 0.026, 0.892, -0.427, 24.9), #(-0.251, -0.181, 0.023, 0.069, 0.955, 0.25, 309.6)), #(#(-0.251, -0.263, 0.026, 0.036, 0.95, 0.245, 203.5), #(-0.251, -0.263, 0.07, 0.073, 0.934, -0.204, 62.15), #(-0.251, -0.263, 0.121, 0.068, 0.906, -0.381, 29.05), #(-0.251, -0.264, 0.173, 0.039, 0.88, -0.447, 26.13), #(-0.25, -0.172, 0.025, 0.074, 0.958, 0.226, 304.1)), #(#(-0.251, -0.253, 0.028, 0.044, 0.947, 0.216, 213.4), #(-0.25, -0.253, 0.068, 0.084, 0.924, -0.21, 74.34), #(-0.25, -0.254, 0.117, 0.08, 0.895, -0.401, 30.81), #(-0.251, -0.255, 0.169, 0.051, 0.87, -0.467, 27.44), #(-0.25, -0.162, 0.028, 0.077, 0.961, 0.21, 300.6)), #(#(-0.25, -0.244, 0.03, 0.051, 0.943, 0.188, 223.0), #(-0.249, -0.244, 0.066, 0.094, 0.915, -0.214, 87.96), #(-0.249, -0.245, 0.113, 0.092, 0.885, -0.423, 32.69), #(-0.25, -0.246, 0.164, 0.063, 0.859, -0.486, 28.84), #(-0.249, -0.153, 0.03, 0.079, 0.965, 0.199, 298.8)), #(#(-0.25, -0.234, 0.032, 0.06, 0.939, 0.16, 232.4), #(-0.248, -0.235, 0.064, 0.103, 0.907, -0.215, 102.9), #(-0.248, -0.236, 0.109, 0.106, 0.875, -0.445, 34.7), #(-0.25, -0.238, 0.159, 0.075, 0.849, -0.505, 30.33), #(-0.248, -0.143, 0.032, 0.08, 0.971, 0.193, 298.3)), #(#(-0.249, -0.225, 0.034, 0.068, 0.934, 0.133, 241.8), #(-0.247, -0.226, 0.062, 0.11, 0.9, -0.214, 119.2), #(-0.247, -0.228, 0.104, 0.12, 0.865, -0.468, 36.87), #(-0.249, -0.229, 0.154, 0.087, 0.84, -0.525, 31.93), #(-0.247, -0.133, 0.034, 0.079, 0.973, 0.19, 301.0)), #(#(-0.249, -0.216, 0.035, 0.073, 0.933, 0.115, 246.9), #(-0.246, -0.217, 0.06, 0.111, 0.9, -0.192, 138.0), #(-0.246, -0.219, 0.1, 0.132, 0.857, -0.486, 40.28), #(-0.248, -0.221, 0.149, 0.098, 0.83, -0.544, 33.64), #(-0.247, -0.124, 0.035, 0.077, 0.973, 0.19, 305.7)), #(#(-0.248, -0.206, 0.036, 0.075, 0.937, 0.11, 247.8), #(-0.245, -0.208, 0.058, 0.101, 0.912, -0.136, 158.1), #(-0.245, -0.211, 0.095, 0.126, 0.866, -0.443, 54.04), #(-0.247, -0.213, 0.144, 0.101, 0.819, -0.558, 35.67), #(-0.246, -0.114, 0.037, 0.075, 0.974, 0.19, 311.5)), #(#(-0.247, -0.197, 0.037, 0.076, 0.941, 0.108, 249.5), #(-0.244, -0.199, 0.056, 0.094, 0.923, -0.09, 175.0), #(-0.244, -0.202, 0.09, 0.119, 0.878, -0.389, 70.1), #(-0.246, -0.204, 0.138, 0.102, 0.809, -0.566, 38.7), #(-0.245, -0.104, 0.039, 0.073, 0.974, 0.191, 318.5)), #(#(-0.246, -0.188, 0.039, 0.076, 0.947, 0.108, 251.9), #(-0.243, -0.19, 0.055, 0.088, 0.933, -0.051, 189.6), #(-0.242, -0.193, 0.086, 0.112, 0.895, -0.326, 88.15), #(-0.245, -0.196, 0.132, 0.102, 0.806, -0.555, 45.22), #(-0.244, -0.094, 0.041, 0.071, 0.974, 0.194, 326.7)), #(#(-0.246, -0.178, 0.04, 0.076, 0.952, 0.111, 254.9), #(-0.242, -0.18, 0.055, 0.083, 0.943, -0.017, 202.4), #(-0.241, -0.184, 0.083, 0.104, 0.914, -0.256, 107.8), #(-0.244, -0.188, 0.127, 0.101, 0.808, -0.531, 54.32), #(-0.244, -0.085, 0.043, 0.069, 0.974, 0.197, 336.0)), #(#(-0.245, -0.169, 0.041, 0.076, 0.959, 0.116, 258.3), #(-0.241, -0.171, 0.055, 0.079, 0.954, 0.015, 214.0), #(-0.24, -0.175, 0.081, 0.096, 0.935, -0.18, 128.4), #(-0.243, -0.18, 0.122, 0.098, 0.814, -0.496, 65.77), #(-0.243, -0.075, 0.045, 0.067, 0.973, 0.202, 346.5)), #(#(-0.244, -0.159, 0.042, 0.075, 0.966, 0.123, 262.3), #(-0.241, -0.161, 0.055, 0.075, 0.964, 0.044, 224.6), #(-0.239, -0.166, 0.079, 0.087, 0.952, -0.107, 150.9), #(-0.242, -0.172, 0.117, 0.094, 0.824, -0.452, 79.27), #(-0.242, -0.065, 0.047, 0.065, 0.973, 0.208, 358.3)), #(#(-0.243, -0.149, 0.043, 0.073, 0.974, 0.133, 266.8), #(-0.24, -0.152, 0.055, 0.071, 0.975, 0.073, 234.7), #(-0.238, -0.156, 0.078, 0.078, 0.968, -0.041, 171.8), #(-0.241, -0.164, 0.112, 0.09, 0.836, -0.4, 94.45), #(-0.242, -0.055, 0.049, 0.063, 0.972, 0.213, 366.9)), #(#(-0.243, -0.14, 0.044, 0.072, 0.982, 0.146, 271.9), #(-0.239, -0.142, 0.056, 0.068, 0.986, 0.103, 244.5), #(-0.238, -0.146, 0.077, 0.071, 0.984, 0.022, 190.9), #(-0.24, -0.155, 0.108, 0.085, 0.851, -0.342, 110.9), #(-0.241, -0.046, 0.051, 0.063, 0.972, 0.216, 371.1)), #(#(-0.242, -0.13, 0.046, 0.07, 0.982, 0.149, 280.5), #(-0.238, -0.132, 0.057, 0.066, 0.987, 0.115, 256.8), #(-0.237, -0.137, 0.078, 0.066, 0.994, 0.067, 209.9), #(-0.24, -0.147, 0.105, 0.079, 0.867, -0.28, 128.2), #(-0.24, -0.036, 0.053, 0.063, 0.971, 0.219, 375.5)), #(#(-0.241, -0.12, 0.047, 0.068, 0.981, 0.153, 290.0), #(-0.238, -0.122, 0.058, 0.064, 0.987, 0.124, 270.2), #(-0.236, -0.127, 0.078, 0.064, 0.992, 0.083, 230.4), #(-0.239, -0.138, 0.102, 0.074, 0.881, -0.223, 146.1), #(-0.24, -0.026, 0.056, 0.063, 0.97, 0.223, 380.0)), #(#(-0.241, -0.11, 0.049, 0.066, 0.981, 0.158, 300.3), #(-0.237, -0.112, 0.059, 0.062, 0.986, 0.132, 284.2), #(-0.236, -0.117, 0.079, 0.061, 0.99, 0.098, 251.1), #(-0.238, -0.129, 0.1, 0.069, 0.886, -0.188, 165.3), #(-0.239, -0.017, 0.058, 0.063, 0.97, 0.227, 384.7)), #(#(-0.24, -0.1, 0.051, 0.064, 0.98, 0.164, 311.4), #(-0.237, -0.102, 0.061, 0.06, 0.985, 0.142, 298.8), #(-0.235, -0.107, 0.08, 0.059, 0.988, 0.113, 272.0), #(-0.237, -0.121, 0.098, 0.064, 0.89, -0.156, 184.9), #(-0.238, -0.007, 0.06, 0.063, 0.969, 0.232, 389.4)))
--make empty array for tubes
extrudeMatr = #()
for i = 1 to ntubes do extrudeMatr[i] = 0

--make splines
for i = 1 to nsteps-1 do 
(
    for j = 1 to 5 do--ntubes do 
    (

        PointA=[(tubeMatr[i][j][1] as float)*1000, (tubeMatr[i][j][2] as float)*1000,(tubeMatr[i][j][3] as float)*1000]
        PointB=[(tubeMatr[i+1][j][1] as float)*1000, (tubeMatr[i+1][j][2] as float)*1000,(tubeMatr[i+1][j][3] as float)*1000]
        ss = SplineShape pos:PointA
        addNewSpline ss
        addKnot ss 1 #corner #line PointA
        addKnot ss 1 #corner #line PointB
        updateShape ss

    )
)

抱歉问了这么长的问题,我真的被困在这里了。

只是好奇,这里的轴顺序是什么?当我假设它的 XYZ 时看起来有点不稳定:

struct tubePoint
(
    data,
    pos = 1000 * [data[1], data[2], data[3]],
    dir = [data[4], data[5], data[6]],
    radius = data[7] / 2d0
)

fn updateTM tm rot =
(
    tm.row1 *= rot
    tm.row2 *= rot
    tm.row3 *= rot
)

fn getRotQuat vec1 vec2 =
    quat (acos (dot vec1 vec2)) (normalize (cross vec1 vec2))

fn getTMAlongPath pt prevTM =
(
    updateTM prevTM (getRotQuat pt.dir prevTM.row3)
    prevTM * transMatrix pt.pos
)

fn addQuad pt1 pt2 pt3 pt4 &faces =
(
    append faces [pt1, pt4, pt3]
    append faces [pt3, pt2, pt1]
)

fn addNGonPoints n radius ngonTM &vertList clockwise:false =
(
    local angle = 360d0 / n * (if clockwise then 1 else -1)
    local vertCount = vertList.count

    for i = 1 to n collect
    (
        append vertList ([radius * sin (i * angle), radius * cos (i * angle), 0] * ngonTM)
        vertCount + i
    )
)

fn makeQuadStrip pts1 pts2 count &faceList closed:false =
(
    if closed do count -= 1
    for offset = 1 to count do
        addQuad pts1[offset] pts1[offset + 1] pts2[offset + 1] pts2[offset] &faceList

    if closed do addQuad pts1[count + 1] pts1[1] pts2[1] pts2[count + 1] &faceList
)

fn constructTube pts sides vertList:#() faceList:#() =
(
    local firstPoint = pts[1]
    local prevTM = arbAxis firstPoint.dir
    local prevRow = addNGonPoints sides firstPoint.radius (prevTM * transMatrix firstPoint.pos) &vertList clockwise:true

    for i = 2 to pts.count do
    (
        local nextPoint = pts[i]
        local nextRow = addNGonPoints sides nextPoint.radius (getTMAlongPath nextPoint prevTM) &vertList clockwise:true

        makeQuadStrip prevRow nextRow sides &faceList closed:true

        prevRow = nextRow
    )

    local tubeMesh = Mesh vertices:vertList faces:faceList
    for face in tubeMesh.faces do setEdgeVis tubeMesh face.index 3 false
    for face = 1 to faceList.count do setFaceSmoothGroup tubeMesh face (1 + int(mod ((face - 1) / (2 * sides)) 2))
)

for tubePts in tubeMatr do constructTube (for pt in tubePts collect tubePoint data:pt) 16

为了获得重新排列的数据列表(即 #(#(1, 1, 1), #(2, 2, 2), #(3, 3, 3), #(4, 4, 4), #(5, 5, 5)) 而不是 #(#(1, 2, 3, 4, 5), #(1, 2, 3, 4, 5), #(1, 2, 3, 4, 5)),用这两个替换最后一行:

transposedTubeMatr = for i = 1 to tubeMatr[1].count collect for j = 1 to tubeMatr.count collect tubeMatr[j][i]
for tubePts in transposedTubeMatr do constructTube (for pt in tubePts collect tubePoint data:pt) 16