spline.scad 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. // Spline module for scad-util library
  2. // Author Sergei Kuzmin, 2014.
  3. // For n+1 given point and hense n intervals returns the spline coefficient matrix.
  4. // param p defines the anchor points.
  5. // File defines two functions: spline_args and spline.
  6. // example usage:
  7. // spl1 = spline_args(point, v1=[0,1,0], closed=false);
  8. // interpolated_points = [for(t=[0:0.1:len(point)-1]) spline(spl1, t)]
  9. use <linalg.scad>
  10. use <lists.scad>
  11. q1=[[1,0,0,0],[1,1,1,1],[0,1,2,3],[0,0,1,3]];
  12. q1inv=[[1,0,0,0],[-3,3,-2,1],[3,-3,3,-2],[-1,1,-1,1]];
  13. q2=[[0,0,0,0],[0,0,0,0],[0,-1,0,0],[0,0,-1,0]];
  14. qn1i2=-q1inv*q2;
  15. z3=[0,0,0];
  16. z4=[0,0,0,0];
  17. function matrix_power(m,n)= n==0? (len(m)==3?identity3():identity4()) :
  18. n==1 ? m : (n%2==1) ? matrix_power(m*m,floor(n/2))*m : matrix_power(m*m,n/2);
  19. function det(m) = let(r=[for(i=[0:1:len(m)-1]) i]) det_help(m, 0, r);
  20. // Construction indices list is inefficient, but currently there is no way to imperatively
  21. // assign to a list element
  22. function det_help(m, i, r) = len(r) == 0 ? 1 :
  23. m[len(m)-len(r)][r[i]]*det_help(m,0,remove(r,i)) - (i+1<len(r)? det_help(m, i+1, r) : 0);
  24. function matrix_invert(m) = let(r=[for(i=[0:len(m)-1]) i]) [for(i=r) [for(j=r)
  25. ((i+j)%2==0 ? 1:-1) * matrix_minor(m,0,remove(r,j),remove(r,i))]] / det(m);
  26. function matrix_minor(m,k,ri, rj) = let(len_r=len(ri)) len_r == 0 ? 1 :
  27. m[ri[0]][rj[k]]*matrix_minor(m,0,remove(ri,0),remove(rj,k)) - (k+1<len_r?matrix_minor(m,k+1,ri,rj) : 0);
  28. function spline_u(i,p) = [p[i],p[i+1],z3,z3];
  29. function spline_args(p, closed=false, v1=undef, v2=undef)=len(p)<2 ? []:
  30. let(q3=closed?q2:[z4, z4, v1==undef?[0,0,1,0]:[0,1,0,0], z4],
  31. q4=closed?q1:[[1,0,0,0], [1,1,1,1], z4, v2==undef?[0,0,1,3]:[0,1,2,3]],
  32. pcnt=closed? len(p) + 1 : len(p),
  33. un=[p[pcnt-2],p[closed?0:pcnt-1],v1==undef?z4:v1, v2==undef?z4:v2],
  34. sn=matrix_invert(q4+q3*matrix_power(qn1i2,pcnt-2))*(un-q3*q1inv*spline_helper(0, pcnt, p)))
  35. // result[i+1] recurrently defines result[i]. This is O(n) runtime with imperative language and
  36. // may be O(n^2) if OpenSCAD doesn't cache spline_si(i+1).
  37. [for(i=[0:pcnt-2]) spline_si(i, pcnt-2, p, sn)];
  38. // n is number of points including pseudopoint for closed contour
  39. // Weird construct cause there is no if statement for functions
  40. function spline_helper(i, n, p) = let(u=[p[i], p[i+1], z3, z3]) i+3>=n? u : u-q2*q1inv*spline_helper(i+1, n, p);
  41. // knowing s[j+1], calculate s[j]. Stop when found s[i]
  42. function spline_si(i,n, p, sn) = i == n ? sn : q1inv*(spline_u(i,p)-q2*spline_si(i+1, n, p, sn));
  43. // Takes array of (3n+1) points or (2n + 2) points, if tangent segments are symmetric.
  44. // For non-symmetric version input is: point0, normal0, neg_normal1, point1, normal1, ... neg_normal_n, point_n
  45. // For symmetric version: point0, normal0, point1, normal1, ... , normal_n_sub_1, point_n
  46. // In the second case second tangent is constructed from the next tangent by symmetric map.
  47. // I.e. if current points are p0,p1,p2 then anchor points are p0 and p2, first tangent defined by p1-p0,
  48. // second tangent defined by p3-p2.
  49. // Return array of coefficients accepted by spline(), spline_tan() and similar
  50. function bezier3_args(p, symmetric=false) = let(step=symmetric?2:3)
  51. [for(i=[0:step:len(p)-3]) [[1,0,0,0],[-3,3,0,0],[3,-6,3,0],[-1,3,-3,1]]*
  52. (symmetric?[p[i],p[i]+p[i+1],p[i+2]-p[i+3],p[i+2]] : [p[i], p[i]+p[i+1], p[i+3]+p[i+2], p[i+3]])];
  53. // s - spline arguments calculated by spline_args
  54. // t - defines point on curve. each segment length is 1. I.e. t= 0..1 is first segment, t=1..2 - second.
  55. function spline(s, t)= let(i=t>=len(s)?len(s)-1: floor(t), t2=t-i) [1,t2,t2*t2,t2*t2*t2]*s[i];
  56. function spline_tan(s, t)= let(i=t>=len(s)?len(s)-1: floor(t), t2=t-i) [0,1,2*t2,3*t2*t2]*s[i];
  57. function spline_tan_unit(s, t)= unit(spline_tan(s,t));
  58. function spline_d2(s,t)= let(i=t>=len(s)?len(s)-1: floor(t), t2=t-i) [0,0,2,6*t2]*s[i];
  59. function spline_binormal_unit(s,t)= unit(cross(spline_tan(s, t), spline_d2(s,t)));
  60. function spline_normal_unit(s,t)= unit(cross(spline_tan(s, t), spline_binormal_unit(s,t)));
  61. function spline_transform(s, t)=
  62. construct_Rt(transpose_3([spline_normal_unit(s,t), spline_binormal_unit(s,t), spline_tan_unit(s,t)]), spline(s,t));
  63. // Unit tests
  64. __s = spline_args([[0,10,0], [10,0,0],[0,-5,2]], v1=[0,1,0], v2=[-1,0,0], closed=true);
  65. for(t=[0:0.01:len(__s)]) translate(spline(__s, t))
  66. cube([0.2,0.2,0.2], center=true);
  67. __s1=spline_args([[0,0,0],[0,0,15], [26,0,26+15]], /*v1=[0,0,100],*/ v2=[40,0,0]);
  68. for(t=[0:0.01:len(s1)]) translate(spline(__s1, t))
  69. cube([0.2,0.2,0.2], center=true);
  70. __s2=bezier3_args([[0,0,0],[0,0,10],[0,0,15],[0,0,26*0.552284],[26,0,41],[26*0.552284,0,0]],symmetric=true);
  71. echo(__s2);
  72. for(t=[0:0.01:len(__s2)]) translate(spline(__s2, t))
  73. cube([0.2,0.2,0.2], center=true);
  74. // Rotation methods taken from list-comprehension-demos/sweep.scad to demonstrate normal and binormal
  75. // Normally spline_transform is more convenient
  76. function __rotation_from_axis(x,y,z) = [[x[0],y[0],z[0]],[x[1],y[1],z[1]],[x[2],y[2],z[2]]];
  77. function __rotate_from_to(a,b,_axis=[]) =
  78. len(_axis) == 0
  79. ? __rotate_from_to(a,b,unit(cross(a,b)))
  80. : _axis*_axis >= 0.99 ? __rotation_from_axis(unit(b),_axis,cross(_axis,unit(b))) *
  81. transpose_3(__rotation_from_axis(unit(a),_axis,cross(_axis,unit(a)))) : identity3();
  82. __s3 = spline_args([[0,10,0], [6,6,0], [10,0,0],[0,-5,4]], v1=[0,1,0], v2=[-1,0,0], closed=true);
  83. for(t=[0:0.05:len(__s3)]) translate(spline(__s3, t)) {
  84. translate([0,0,3]) multmatrix(m=__rotate_from_to([0,0,1],spline_normal_unit(__s3,t)))
  85. cylinder(r1=0.1, r2=0, h=1, $fn=3);
  86. translate([0,0,6]) multmatrix(m=__rotate_from_to([0,0,1],spline_binormal_unit(__s3,t)))
  87. cylinder(r1=0.1, r2=0, h=1, $fn=3);
  88. }
  89. translate([0,0,9]) for(t=[0:0.025:len(__s3)])
  90. multmatrix(spline_transform(__s3,t)) cube([1,1,0.1],center=true);