Guest User

Subdivision in 2D

a guest
Nov 12th, 2024
19
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.47 KB | Source Code | 0 0
  1. // Subdivision in 2D.scad
  2. //
  3. // Version 0.7
  4. // November 13, 2024
  5. // By: Stone Age Sculptor
  6. // License: CC0 (Public Domain)
  7. //
  8. // This script is a test to explore subdivision.
  9. // My goal was something that feels like NURBS.
  10. // Easy: The control points are just points
  11. // without angle.
  12. // Smooth: The curve does not go through
  13. // the original points.
  14. // Simple: Instead of calculating the final spline,
  15. // the functions go through the basic
  16. // iterations for smoothing.
  17.  
  18. $fn = 100;
  19.  
  20. ShowTests(15,"1");
  21. ShowTests(-15,"weighted");
  22. ShowPathTest();
  23.  
  24. // The functions work in 3D as well,
  25. // but that is slow and can not be rendered
  26. // at the same time with 2D.
  27. *ShowTube();
  28. *translate([14,0,5])
  29. rotate([0,180,0])
  30. ShowTube();
  31.  
  32. module ShowTube()
  33. {
  34. tubepoints = [[-15,-2,5],[-3,-2,5],[0,-2,0],[10,-2,0],[18,-12,8],[18,12,8],[10,2,0],[0,2,0],[-3,2,5],[-15,2,5]];
  35. tubepath = Subdivision(tubepoints,4,method="weightedpath");
  36. tube_thickness = 3;
  37.  
  38. // the tube in 3D
  39. for(i=[0:len(tubepath)-2])
  40. {
  41. hull()
  42. {
  43. translate(tubepath[i])
  44. sphere(d=tube_thickness);
  45. translate(tubepath[i+1])
  46. sphere(d=tube_thickness);
  47. }
  48. }
  49. }
  50.  
  51. module ShowTests(y_offset, method)
  52. {
  53. // The original shape to start with.
  54. shape = [[-20,-10],[-12,15],[8,7],[13,-3]];
  55.  
  56. // The shape with points for explanation.
  57. translate([0,y_offset])
  58. {
  59. color("LightSteelBlue")
  60. translate([0,0,-2])
  61. polygon(shape);
  62.  
  63. // Show the points of the shape in Blue.
  64. color("Blue")
  65. for(i=[0:len(shape)-1])
  66. translate(shape[i])
  67. circle(1);
  68.  
  69. // Show the points in between in Purple.
  70. PurplePoints = [for(i=[0:len(shape)-1]) ((shape[i] + shape[i+1 > (len(shape)-1) ? 0 : i+1])/2)];
  71. color("Purple")
  72. for(i=[0:len(PurplePoints)-1])
  73. translate(PurplePoints[i])
  74. circle(1);
  75.  
  76. if(method=="1")
  77. {
  78. // Method "1" is the 1,1 weighted subdivision
  79.  
  80. // The text of the used method
  81. color("Black")
  82. translate([-65,0])
  83. text("\"1\"",size=6);
  84.  
  85. // Show the new points between the Blue and the Purple points.
  86. OrangePoints = [ for(i=[0:len(shape)-1]) each [ (shape[i] + PurplePoints[i])/2, (PurplePoints[i] + shape[i+1 > (len(shape)-1) ? 0 : i+1])/2] ];
  87. color("Orange")
  88. for(i=[0:len(OrangePoints)-1])
  89. translate(OrangePoints[i])
  90. circle(1);
  91. }
  92. else
  93. {
  94. // The other method is the weighted method.
  95.  
  96. // The text of the used method
  97. color("Black")
  98. translate([-65,0])
  99. text("\"weighted\"",size=6);
  100.  
  101. // Show the average of two Purple points in Black
  102. black_point = (PurplePoints[0]+PurplePoints[1])/2;
  103. color("Black")
  104. translate(black_point)
  105. circle(1);
  106.  
  107. // Show the line that the new points is on,
  108. // according to a weight variable.
  109. color("Gray")
  110. hull()
  111. {
  112. translate(black_point)
  113. circle(0.2);
  114. translate(shape[1])
  115. circle(0.2);
  116. }
  117.  
  118. // Show the new points.
  119. RedPoints = [for(i=[0:len(shape)-1]) ((shape[i] + (PurplePoints[i] + PurplePoints[(i-1) < 0 ? (len(shape)-1) : i-1])/2)/2)];
  120. color("Red")
  121. for(i=[0:len(RedPoints)-1])
  122. translate(RedPoints[i])
  123. circle(1);
  124.  
  125. // Show a Green triangle.
  126. color("LawnGreen")
  127. translate([0,0,-1])
  128. polygon([shape[1],PurplePoints[0],PurplePoints[1]]);
  129. }
  130. }
  131.  
  132. // Example with the shape.
  133. translate([35,y_offset])
  134. {
  135. color("LightSteelBlue")
  136. polygon(shape);
  137. color("Red")
  138. translate([0,0,2])
  139. polygon(Subdivision(shape,4,method=method));
  140. }
  141.  
  142. // Example with triangle.
  143. translate([65,y_offset])
  144. {
  145. triangle = [for(a=[0,120,240]) [15*sin(a),15*cos(a)] ];
  146.  
  147. color("LightSteelBlue")
  148. polygon(triangle);
  149.  
  150. color("Red")
  151. translate([0,0,1])
  152. polygon(Subdivision(triangle,4,method=method));
  153. }
  154.  
  155. // Example with square.
  156. translate([90,y_offset])
  157. {
  158. rectangle = [[-10,-10],[10,-10],[10,10],[-10,10]];
  159.  
  160. color("LightSteelBlue")
  161. polygon(rectangle);
  162.  
  163. color("Red")
  164. translate([0,0,2])
  165. polygon(Subdivision(rectangle,5,method=method));
  166.  
  167. color("Black")
  168. translate([0,0,4])
  169. difference()
  170. {
  171. circle(10.0);
  172. circle(9.0);
  173. }
  174. }
  175.  
  176. // Example with a more complex shape
  177. translate([115,y_offset])
  178. {
  179. testshape = [[-10,-10],[-10,10],[0,-10],[10,10],[10,-10]];
  180.  
  181. color("LightSteelBlue")
  182. polygon(testshape);
  183.  
  184. color("Red",0.6)
  185. translate([0,0,2])
  186. polygon(Subdivision(testshape,5,method=method));
  187. }
  188.  
  189. // Example with a heart shape
  190. translate([140,y_offset])
  191. {
  192. // Both sides are defined.
  193. // It is a single solid 2D object, therefor both
  194. // sides of the shape have to be defined.
  195. heartshape = [[0,6],[-0.5,10],[-10,9],[-10,0],[-1,-4],[0,-10],[0,-10],[1,-4],[10,0],[10,9],[0.5,10],[0,6]];
  196.  
  197. color("LightSteelBlue",0.2)
  198. polygon(heartshape);
  199.  
  200. color("Red",0.6)
  201. translate([0,0,2])
  202. polygon(Subdivision(heartshape,5,method=method));
  203. }
  204. }
  205.  
  206. module ShowPathTest()
  207. {
  208. // Examples with paths
  209. points = [[-20,10],[-20,-10],[0,-10],[-15,15],[5,15],[20,0]];
  210.  
  211. translate([0,-50])
  212. {
  213. // Show the text.
  214. color("Red")
  215. translate([-65,5])
  216. text("\"1path\"",size=5);
  217.  
  218. color("Blue")
  219. translate([-65,-5])
  220. text("\"weightedpath\"",size=5);
  221.  
  222. for(i=[0,1])
  223. {
  224. x_offset = i==0 ? 5 : 50;
  225. divisions = i==0 ? 1 : 3;
  226.  
  227. translate([x_offset,0])
  228. {
  229. // Show the points.
  230. color("Black")
  231. for(i=[0:len(points)-1])
  232. translate(points[i])
  233. circle(1);
  234.  
  235. // Show the number of divisions.
  236. color("Black")
  237. {
  238. t = str("divisions=", divisions);
  239. translate([-3,0])
  240. text(t,size=2.5);
  241. }
  242.  
  243. // Draw the path.
  244. path1 = Subdivision(points,divisions,method="1path");
  245. color("Red")
  246. {
  247. for(i=[0:len(path1)-2])
  248. {
  249. hull()
  250. {
  251. translate(path1[i])
  252. circle(0.2);
  253. translate(path1[i+1])
  254. circle(0.2);
  255. }
  256. }
  257. }
  258.  
  259. path2 = Subdivision(points,divisions,method="weightedpath");
  260. color("Blue")
  261. {
  262. for(i=[0:len(path2)-2])
  263. {
  264. hull()
  265. {
  266. translate(path2[i])
  267. circle(0.2);
  268. translate(path2[i+1])
  269. circle(0.2);
  270. }
  271. }
  272. }
  273. }
  274. }
  275.  
  276. // The shape of a horseshoe
  277. translate([90,-5])
  278. {
  279. horsepoints = [[-7.5,16],[-9,14],[-12,1],[0,-8],[12,1],[9,14],[7.5,16]];
  280. horsepath = Subdivision(horsepoints,4,method="weightedpath");
  281. color("Black")
  282. translate([0,0,1])
  283. for(i=[0:len(horsepoints)-1])
  284. translate(horsepoints[i])
  285. circle(0.5);
  286.  
  287. color("Brown",0.5)
  288. offset(1.8)
  289. for(i=[0:len(horsepath)-2])
  290. {
  291. hull()
  292. {
  293. translate(horsepath[i])
  294. circle(0.1);
  295. translate(horsepath[i+1])
  296. circle(0.1);
  297. }
  298. }
  299. }
  300.  
  301. // An arc
  302. translate([120,-10])
  303. {
  304. arcpoints = [[10,0],[10,12],[10,16],[1,21],[0,24]];
  305. arcpath = Subdivision(arcpoints,4,method="weightedpath");
  306. a2 = concat(MirrorList(arcpath),ReverseList(arcpath));
  307.  
  308. color("Olive",0.6)
  309. difference()
  310. {
  311. polygon(a2);
  312. offset(-2)
  313. polygon(a2);
  314. }
  315.  
  316. color("Black")
  317. translate([0,0,1])
  318. for(i=[0:len(arcpoints)-1])
  319. translate(arcpoints[i])
  320. circle(0.5);
  321.  
  322. color("Gray")
  323. for(i=[0:len(arcpath)-2])
  324. {
  325. hull()
  326. {
  327. translate(arcpath[i])
  328. circle(0.1);
  329. translate(arcpath[i+1])
  330. circle(0.1);
  331. }
  332. }
  333. }
  334. }
  335. }
  336.  
  337. // Reverse a list of points.
  338. function ReverseList (list) =
  339. let(n=len(list)-1)
  340. [for(i=[0:n]) list[n-i] ];
  341.  
  342. // Mirror a list of 2D points.
  343. // The points on the positive x-axis
  344. // are mirrored to the negative x-axis.
  345. function MirrorList (list) =
  346. let(n=len(list)-1)
  347. [for(i=[0:n]) [-list[i].x, list[i].y] ];
  348.  
  349.  
  350. // ------------------------------------------------
  351. // Subdivision functions
  352. // ------------------------------------------------
  353. // Version 1
  354. // November 13, 2024
  355. // By: Stone Age Sculptor
  356. // License: CC0 (Public Domain)
  357.  
  358. // Subdivision
  359. // -----------
  360. // Parameters:
  361. // list: A list of coordinates in 2D or 3D.
  362. // But not a 3D surface.
  363. // divisions: The number of divisions.
  364. // 0 for no smoothing, 5 is extra smooth.
  365. // method: The subdivision method:
  366. // "1" A basic 1,1 weighted subdivision
  367. // for a closed shape.
  368. // "weighted" A weighted average subdivision
  369. // for a closed shape.
  370. // "1path" A basic 1,1 weighted subdivision
  371. // for a path.
  372. // "weightedpath"
  373. // A weighted average subdivision
  374. // for a path.
  375. // Return:
  376. // A new list with a smoother shape.
  377. // The new list is often used with the polygon() function.
  378. function Subdivision(list,divisions,method) =
  379. divisions > 0 ?
  380. Subdivision(
  381. method=="1" ? _Subdivision11(list) :
  382. method=="1path" ? _SubdivisionPath11(list) :
  383. method=="weighted" ? _SubdivisionWeighted(list) :
  384. method=="weightedpath" ? _SubdivisionPathWeighted(list) :
  385. assert(false,"Unknown method in Subdivision"),
  386. divisions - 1,method) :
  387. list;
  388.  
  389. // This is the most basic subdivision with 1,1 weighting.
  390. // It will work in 2D and 3D.
  391. //
  392. // The average in OpenSCAD is: (current_point + next_point) / 2
  393. // The index of the list wraps around for the next point.
  394. //
  395. // The 'list2' is the average points between the original points.
  396. // The returned list is the new average between the average points
  397. // and the original points.
  398. function _Subdivision11(list) =
  399. let (n = len(list)-1)
  400. let (list2 = [ for(i=[0:n]) (list[i] + list[(i+1) > n ? 0 : i+1])/2 ])
  401. [ for(i=[0:n]) each [ (list[i] + list2[i])/2, (list2[i] + list[(i+1) > n ? 0 : i+1])/2 ]];
  402.  
  403. // My own attempt with variable weighting.
  404. // The goal was a smoothing algoritme that feels
  405. // like NURBS.
  406. // Explanation:
  407. // The average points between the original
  408. // points are calculated. These are kept.
  409. // A second set of average points between
  410. // those average points are temporarely calculated.
  411. // A new point is created on the line between
  412. // an original point and the temporarely point.
  413. // The position on that line is defined by
  414. // a 'weight' variable.
  415. // The result is the combination of the
  416. // kept points and the new points.
  417. //
  418. // When the 'weight' variable is set to
  419. // sqrt(2) - 1, then the result approximates
  420. // a circle when the control points is a square.
  421. // It is some kind of cubic B-spline, but I don't
  422. // know if it matches with one of the known algoritmes.
  423. function _SubdivisionWeighted(list) =
  424. let (weight = sqrt(2) - 1)
  425. let (n = len(list)-1)
  426. let (list2 = [ for(i=[0:n]) (list[i] + list[(i+1) > n ? 0 : i+1])/2 ])
  427. let (list3 = [ for(i=[0:n]) (weight*list[i] + (1-weight)/2*(list2[i] + list2[(i-1) < 0 ? n : i-1])) ])
  428. [ for(i=[0:n]) each [list3[i], list2[i]] ];
  429.  
  430. // The basic subdivision with 1,1 weighting.
  431. // But now for a path with a open begin and end.
  432. function _SubdivisionPath11(list) =
  433. let (n = len(list)-2)
  434. let (list2 = [ for(i=[0:n]) (list[i] + list[i+1])/2 ])
  435. [ list[0], for(i=[1:n]) each [ (list[i] + list2[i-1])/2, (list[i] + list2[i])/2 ], list[n+1]];
  436.  
  437. // My own attempt with variable weighting.
  438. // But now for a path with a open begin and end.
  439. function _SubdivisionPathWeighted(list) =
  440. let (weight = sqrt(2) - 1)
  441. let (n = len(list)-2)
  442. let (list2 = [ for(i=[0:n]) (list[i] + list[i+1])/2 ])
  443. let (list3 = [ list[0], for(i=[1:n]) (weight*list[i] + (1-weight)/2*(list2[i] + list2[i-1])), list[n+1] ])
  444. [ for(i=[0:n]) each [list3[i], list2[i]], list3[n+1] ];
  445.  
Tags: OpenSCAD
Add Comment
Please, Sign In to add comment