├── LICENSE.txt ├── README.md ├── closepoints.scad ├── demo_3D_art.scad ├── demo_gear_thing.scad ├── demo_roller_coaster.scad ├── demo_roller_coaster2.scad ├── demo_wavy_donut.scad └── images └── demo_images.gif /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenSCAD ClosePoints Library 2 | 3 |

Demo image

4 | 5 | This is a general purpose OpenSCAD library for easily creating diverse shapes 6 | by simply creating lists of points which trace out layers in an outline of the 7 | desired shape. The library consists of modules for creating polyhedrons from 8 | these lists of points, as well as functions to assist in specifying the points 9 | using transformations. 10 | 11 | The file names starting with "demo" provide various examples of usage. 12 | 13 | # closepoints.scad API 14 | 15 | ClosePoints and CloseLoop are the two modules for creating a polyhedron. 16 | ClosePoints is for creating a polyhedron with no holes (e.g., a ball, or a 17 | cup), while CloseLoop is for creating a polyhedron which topologically contains 18 | one hole (e.g., a donut). To achieve this difference, ClosePoints auto-closes 19 | the top and bottom of the provided layer loops, while CloseLoop connects the 20 | last layer loop to the first layer loop to close the polyhedron. 21 | 22 | Following these are a number of functions for working with affine 23 | transformations, which can help significantly in tracing out the surface layers 24 | of a desired polyhedron. 25 | 26 | The API is as follows: 27 | 28 | 29 | ``` 30 | // This generates a closed polyhedron from an array of arrays of points, 31 | // with each inner array tracing out one loop outlining the polyhedron. 32 | // pointarrays should contain an array of N arrays each of size P outlining 33 | // a closed manifold. The points must obey the right-hand rule. Point your 34 | // right-hand thumb in the direction the N point arrays travel, and then the 35 | // P points in the inner arrays must loop in the direction the fingers curl. 36 | // For example, looking down, the P points in the inner arrays are 37 | // counter-clockwise in a loop, while the N point arrays increase in height. 38 | // Points in each inner array do not need to be equal height, but they 39 | // usually should not meet or cross the line segments from the adjacent 40 | // points in the other arrays. 41 | // (N>=2, P>=3) 42 | // Core triangles: 43 | // [j][i], [j+1][i], [j+1][(i+1)%P] 44 | // [j][i], [j+1][(i+1)%P], [j][(i+1)%P] 45 | // Then triangles are formed in a loop with the middle point of the first 46 | // and last array. To override this middle closure point, specify a 47 | // coordinate position for close_top_pt and/or close_bot_pt. 48 | module ClosePoints(pointarrays, close_top_pt=undef, close_bot_pt=undef) 49 | 50 | // This generates a looped polyhedron from an array of arrays of points, with 51 | // each inner array tracing out one layer loop outlining the polyhedron. 52 | // pointarrays should contain an array of N arrays each of size P outlining a 53 | // closed manifold. The points must obey the right-hand rule. For example, 54 | // looking down, the P points in the inner arrays are counter-clockwise in a 55 | // loop, while the N point arrays increase in height. Points in each inner 56 | // array do not need to be equal height, but they usually should not meet or 57 | // cross the line segments from the adjacent points in the other arrays. The 58 | // last layer loop should geometrically lead into the first when it is closed. 59 | // (N>=2, P>=3) 60 | // Core triangles: 61 | // [j][i], [j+1][i], [j+1][(i+1)%P] 62 | // [j][i], [j+1][(i+1)%P], [j][(i+1)%P] 63 | module CloseLoop(pointarrays) 64 | 65 | // Perform an affine transformation of matrix M on coordinate v. 66 | // 67 | // [Scale X] [Shear X along Y] [Shear X along Z] [Translate X] 68 | // [Shear Y along X] [Scale Y] [Shear Y along Z] [Translate Y] 69 | // [Shear Z along X] [Shear Z along Y] [Scale Z] [Translate Z] 70 | // or rotation matrix [[cos,-sin],[sin,cos]] in the 2 axes for a plane. 71 | function Affine(M, v) 72 | 73 | // Combine a list of affine transformation matrices into one. 74 | function AffMerge(Mlist) 75 | 76 | // Prepare a matrix to rotate around the x-axis. 77 | function RotX(a) 78 | 79 | // Prepare a matrix to rotate around the y-axis. 80 | function RotY(a) 81 | 82 | // Prepare a matrix to rotate around the z-axis. 83 | function RotZ(a) 84 | 85 | // Prepare a matrix to rotate around x, then y, then z. 86 | function Rotate(rotvec) 87 | 88 | // Prepare a matrix to translate by vector v. 89 | function Translate(v) 90 | 91 | // Prepare a matrix to scale by vector v. 92 | function Scale(v) 93 | 94 | // Find the bounding box of pointarrays. 95 | // Returns [[min_x, min_y, min_z], [max_x, max_y, max_z]] 96 | function BBox(pointarrays) 97 | ``` 98 | 99 | -------------------------------------------------------------------------------- /closepoints.scad: -------------------------------------------------------------------------------- 1 | // Created 2021-2022 by Ryan A. Colyer. 2 | // This work is released with CC0 into the public domain. 3 | // https://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | 6 | // This generates a closed polyhedron from an array of arrays of points, 7 | // with each inner array tracing out one loop outlining the polyhedron. 8 | // pointarrays should contain an array of N arrays each of size P outlining 9 | // a closed manifold. The points must obey the right-hand rule. Point your 10 | // right-hand thumb in the direction the N point arrays travel, and then the 11 | // P points in the inner arrays must loop in the direction the fingers curl. 12 | // For example, looking down, the P points in the inner arrays are 13 | // counter-clockwise in a loop, while the N point arrays increase in height. 14 | // Points in each inner array do not need to be equal height, but they 15 | // usually should not meet or cross the line segments from the adjacent 16 | // points in the other arrays. 17 | // (N>=2, P>=3) 18 | // Core triangles: 19 | // [j][i], [j+1][i], [j+1][(i+1)%P] 20 | // [j][i], [j+1][(i+1)%P], [j][(i+1)%P] 21 | // Then triangles are formed in a loop with the middle point of the first 22 | // and last array. To override this middle closure point, specify a 23 | // coordinate position for close_top_pt and/or close_bot_pt. 24 | module ClosePoints(pointarrays, close_top_pt=undef, close_bot_pt=undef) { 25 | function recurse_avg(arr, n=0, p=[0,0,0]) = (n>=len(arr)) ? p : 26 | recurse_avg(arr, n+1, p+(arr[n]-p)/(n+1)); 27 | 28 | N = len(pointarrays); 29 | P = len(pointarrays[0]); 30 | NP = N*P; 31 | midbot = is_undef(close_bot_pt) ? 32 | recurse_avg(pointarrays[0]) : 33 | close_bot_pt; 34 | midtop = is_undef(close_top_pt) ? 35 | recurse_avg(pointarrays[N-1]) : 36 | close_top_pt; 37 | 38 | faces_bot = [ 39 | for (i=[0:P-1]) 40 | [0,i+1,1+(i+1)%len(pointarrays[0])] 41 | ]; 42 | 43 | loop_offset = 1; 44 | 45 | faces_loop = [ 46 | for (j=[0:N-2], i=[0:P-1], t=[0:1]) 47 | [loop_offset, loop_offset, loop_offset] + (t==0 ? 48 | [j*P+i, (j+1)*P+i, (j+1)*P+(i+1)%P] : 49 | [j*P+i, (j+1)*P+(i+1)%P, j*P+(i+1)%P]) 50 | ]; 51 | 52 | top_offset = loop_offset + NP - P; 53 | midtop_offset = top_offset + P; 54 | 55 | faces_top = [ 56 | for (i=[0:P-1]) 57 | [midtop_offset,top_offset+(i+1)%P,top_offset+i] 58 | ]; 59 | 60 | points = [ 61 | for (i=[-1:NP]) 62 | (i<0) ? midbot : 63 | ((i==NP) ? midtop : 64 | pointarrays[floor(i/P)][i%P]) 65 | ]; 66 | faces = concat(faces_bot, faces_loop, faces_top); 67 | 68 | polyhedron(points=points, faces=faces, convexity=8); 69 | } 70 | 71 | 72 | // This generates a looped polyhedron from an array of arrays of points, with 73 | // each inner array tracing out one layer loop outlining the polyhedron. 74 | // pointarrays should contain an array of N arrays each of size P outlining a 75 | // closed manifold. The points must obey the right-hand rule. For example, 76 | // looking down, the P points in the inner arrays are counter-clockwise in a 77 | // loop, while the N point arrays increase in height. Points in each inner 78 | // array do not need to be equal height, but they usually should not meet or 79 | // cross the line segments from the adjacent points in the other arrays. The 80 | // last layer loop should geometrically lead into the first when it is closed. 81 | // (N>=2, P>=3) 82 | // Core triangles: 83 | // [j][i], [j+1][i], [j+1][(i+1)%P] 84 | // [j][i], [j+1][(i+1)%P], [j][(i+1)%P] 85 | module CloseLoop(pointarrays) { 86 | function recurse_avg(arr, n=0, p=[0,0,0]) = (n>=len(arr)) ? p : 87 | recurse_avg(arr, n+1, p+(arr[n]-p)/(n+1)); 88 | 89 | N = len(pointarrays); 90 | P = len(pointarrays[0]); 91 | NP = N*P; 92 | 93 | faces_loop = [ 94 | for (j=[0:N-1], i=[0:P-1], t=[0:1]) 95 | t==0 ? 96 | [j*P+i, ((j+1)%N)*P+i, ((j+1)%N)*P+(i+1)%P] : 97 | [j*P+i, ((j+1)%N)*P+(i+1)%P, j*P+(i+1)%P] 98 | ]; 99 | 100 | points = [ 101 | for (i=[0:NP-1]) 102 | pointarrays[floor(i/P)][i%P] 103 | ]; 104 | 105 | polyhedron(points=points, faces=faces_loop, convexity=8); 106 | } 107 | 108 | 109 | // Perform an affine transformation of matrix M on coordinate v. 110 | // 111 | // [Scale X] [Shear X along Y] [Shear X along Z] [Translate X] 112 | // [Shear Y along X] [Scale Y] [Shear Y along Z] [Translate Y] 113 | // [Shear Z along X] [Shear Z along Y] [Scale Z] [Translate Z] 114 | // or rotation matrix [[cos,-sin],[sin,cos]] in the 2 axes for a plane. 115 | function Affine(M, v) = M * [v[0], v[1], v[2], 1]; 116 | 117 | 118 | // Combine a list of affine transformation matrices into one. 119 | function AffMerge(Mlist, i=0) = i >= len(Mlist) ? 120 | [[1,0,0,0],[0,1,0,0],[0,0,1,0]] : 121 | let ( 122 | rest = AffMerge(Mlist, i+1), 123 | prod = Mlist[i] * [rest[0], rest[1], rest[2], [0,0,0,1]] 124 | ) 125 | [prod[0], prod[1], prod[2]]; 126 | 127 | 128 | // Prepare a matrix to rotate around the x-axis. 129 | function RotX(a) = 130 | [[ 1, 0, 0, 0], 131 | [ 0, cos(a), -sin(a), 0], 132 | [ 0, sin(a), cos(a), 0]]; 133 | 134 | // Prepare a matrix to rotate around the y-axis. 135 | function RotY(a) = 136 | [[ cos(a), 0, sin(a), 0], 137 | [ 0, 1, 0, 0], 138 | [-sin(a), 0, cos(a), 0]]; 139 | 140 | // Prepare a matrix to rotate around the z-axis. 141 | function RotZ(a) = 142 | [[cos(a), -sin(a), 0, 0], 143 | [sin(a), cos(a), 0, 0], 144 | [ 0, 0, 1, 0]]; 145 | 146 | // Prepare a matrix to rotate around x, then y, then z. 147 | function Rotate(rotvec) = 148 | AffMerge([RotZ(rotvec[0]), RotY(rotvec[1]), RotX(rotvec[2])]); 149 | 150 | // Prepare a matrix to translate by vector v. 151 | function Translate(v) = 152 | [[1, 0, 0, v[0]], 153 | [0, 1, 0, v[1]], 154 | [0, 0, 1, v[2]]]; 155 | 156 | // Prepare a matrix to scale by vector v. 157 | function Scale(v) = 158 | [[v[0], 0, 0, 0], 159 | [ 0, v[1], 0, 0], 160 | [ 0, 0, v[2], 0]]; 161 | 162 | // Find the bounding box of pointarrays. 163 | // Returns [[min_x, min_y, min_z], [max_x, max_y, max_z]] 164 | function BBox(pointarrays) = let( 165 | inf = 1e300*1e300, 166 | minmax = function(p, a=0, b=0, res=[[inf, inf, inf], [-inf, -inf, -inf]]) 167 | a >= len(p) ? res : 168 | minmax(p, b >= len(p[a])-1 ? a+1 : a, (b+1) % len(p[a]), 169 | [[min(res[0][0], p[a][b][0]), min(res[0][1], p[a][b][1]), 170 | min(res[0][2], p[a][b][2])], 171 | [max(res[1][0], p[a][b][0]), max(res[1][1], p[a][b][1]), 172 | max(res[1][2], p[a][b][2])]]) 173 | ) 174 | minmax(pointarrays); 175 | 176 | -------------------------------------------------------------------------------- /demo_3D_art.scad: -------------------------------------------------------------------------------- 1 | // Created in 2018 by Ryan A. Colyer. 2 | // This work is released with CC0 into the public domain. 3 | // https://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | use 6 | 7 | layer_step = 0.2; 8 | 9 | pedestal_top = 60; 10 | pedestal_ripple = 1; 11 | pedestal_ripple_rad = 4; 12 | pedestal_base_rad = 20; 13 | pedestal_base_thickness = 10; 14 | pedestal_connect_rad = 1.5; 15 | 16 | art_top = 30; 17 | art_radial_ripple = 1; 18 | art_height_ripple = 0.2; 19 | art_ztilt_freq = 3; 20 | art_ztilt_fact = 1; 21 | art_radtilt_freq = 1; 22 | art_radtilt_fact = 1; 23 | 24 | function OneLayer(rad, ztilt, h_off) = 25 | [ 26 | for (a=[0:360]) 27 | [ rad*cos(a), 28 | rad*sin(a), 29 | art_radtilt_fact*rad*sin(art_radtilt_freq*a) 30 | + art_ztilt_fact*ztilt*(cos(art_ztilt_freq*a)+1) + h_off 31 | ] 32 | ]; 33 | 34 | artpointarrays = 35 | [for (h=[0:layer_step:art_top]) 36 | OneLayer( 37 | h*abs(sin(art_radial_ripple*360/art_top)+1.1) + pedestal_connect_rad, 38 | h, 39 | pedestal_top+pedestal_connect_rad 40 | ) 41 | ]; 42 | 43 | 44 | function Pedestal(h) = 45 | let( 46 | r = pedestal_base_rad*exp(-h*h/pedestal_base_thickness) + 47 | pedestal_ripple_rad*pow(sin(h*pedestal_ripple*180/pedestal_top),2) + 48 | pedestal_connect_rad 49 | ) 50 | [ for (a=[0:360]) 51 | [ r*cos(a), r*sin(a), h ] 52 | ]; 53 | 54 | basepointarrays = [for (h=[0:layer_step:pedestal_top]) Pedestal(h)]; 55 | 56 | ClosePoints(concat(basepointarrays, artpointarrays)); 57 | 58 | -------------------------------------------------------------------------------- /demo_gear_thing.scad: -------------------------------------------------------------------------------- 1 | // Created in 2021 by Ryan A. Colyer. 2 | // This work is released with CC0 into the public domain. 3 | // https://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | 6 | use 7 | 8 | // Used to rotate the points in a complete circle around the z-axis. 9 | function GearRotXY(t) = RotZ(360*t); 10 | // This spirals the protruding ridges around the donut-shaped ring. 11 | // Note that this rotation around the y-axis is the first operation below. 12 | function GearRotXZ(t) = RotY(8*360*t); 13 | // Establishes the dimensions of the ellipse, setting the distance away 14 | // from the origin. 15 | function Ellipse(t) = Translate([50*cos(360*t), 40*sin(360*t), 0]); 16 | 17 | // Combine all of the above operations, with the rightmost applied first. 18 | function PathMatrix(t) = AffMerge([Ellipse(t), GearRotXY(t), GearRotXZ(t)]); 19 | 20 | // Defines the cross section of the "gear thing". The ternary operator 21 | // is used to increase the radius of the cross-section for two sets of 22 | // angle values around the cross-section to establish the protruding 23 | // ridges. 24 | function ThePolygon(t) = 25 | [for (a=[0:2:359.99]) 26 | ((a < 6 || (a >= 180 && a < 186)) ? 7 : 5)*[cos(a), 0, -sin(a)] 27 | ]; 28 | 29 | pointarrays = 30 | [for (t=[0:0.002:0.99999]) 31 | [for (p=ThePolygon(t)) 32 | Affine(PathMatrix(t), p) 33 | ] 34 | ]; 35 | 36 | CloseLoop(pointarrays); 37 | 38 | -------------------------------------------------------------------------------- /demo_roller_coaster.scad: -------------------------------------------------------------------------------- 1 | // Created in 2021 by Ryan A. Colyer. 2 | // This work is released with CC0 into the public domain. 3 | // https://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | use 6 | 7 | // Used to form the track in a complete circle around the z-axis. 8 | function RotZt(t) = RotZ(360*t); 9 | 10 | // Provides the variation in height of the roller coaster track. 11 | function hillf(t) = sin(-2*t*360-115)+sin(-3*t*360-57)+2; 12 | function Hills(t) = Translate([0, 0, 20*hillf(t)]); 13 | 14 | // Used to tilt the track inward when it is lower down. 15 | // This reuses the hill value as an input. 16 | // Note that this is applied below before rotation around the circle, so 17 | // the rotation in y is always toward the middle. 18 | function RotXZ(t) = let(a = -45*(4 - hillf(t))/4) RotY(a); 19 | 20 | // Establishes the radius of the circle. 21 | function ShiftX(t) = Translate([60, 0, 0]); 22 | 23 | // Combine all of the above operations, with the rightmost applied first. 24 | function PathMatrix(t) = AffMerge([RotZt(t), Hills(t), ShiftX(t), RotXZ(t)]); 25 | 26 | // Defines the cross section of the track. 27 | function ThePolygon(t) = 28 | [[-5,0,0], [-5,0,3], [-3,0,3], [-3,0,1], [3,0,1], [3,0,3], [5,0,3], [5,0,0]]; 29 | 30 | pointarrays = 31 | [for (t=[0:0.002:0.99999]) 32 | [for (p=ThePolygon(t)) 33 | Affine(PathMatrix(t), p) 34 | ] 35 | ]; 36 | 37 | CloseLoop(pointarrays); 38 | 39 | -------------------------------------------------------------------------------- /demo_roller_coaster2.scad: -------------------------------------------------------------------------------- 1 | // Created in 2021 by Ryan A. Colyer. 2 | // This work is released with CC0 into the public domain. 3 | // https://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | use 6 | 7 | // Used to form the track in a complete circle around the z-axis. 8 | function RotZt(t) = RotZ(360*t); 9 | 10 | // Provides the variation in height of the roller coaster track. 11 | function hillf(t) = sin(-2*t*360-115)+sin(-3*t*360-57)+2; 12 | function Hills(t) = Translate([0, 0, 20*hillf(t)]); 13 | 14 | // Used to tilt the track inward when it is lower down. 15 | // This reuses the hill value as an input. 16 | // Note that this is applied below before rotation around the circle, so 17 | // the rotation in y is always toward the middle. 18 | function RotXZ(t) = let(a = -45*(4 - hillf(t))/4) RotY(a); 19 | 20 | // Establishes the radius of the circle. 21 | function ShiftX(t) = Translate([60, 0, 0]); 22 | 23 | // Combine all of the above operations, with the rightmost applied first. 24 | function PathMatrix(t) = AffMerge([RotZt(t), Hills(t), ShiftX(t), RotXZ(t)]); 25 | 26 | // Defines the cross section of the track. 27 | function ThePolygon(t) = 28 | [[-5,0,0], [-5,0,3], [-3,0,3], [-3,0,1], [3,0,1], [3,0,3], [5,0,3], [5,0,0]]; 29 | 30 | pointarrays = 31 | [for (t=[0:0.002:0.99999]) 32 | [for (p=ThePolygon(t)) 33 | Affine(PathMatrix(t), p) 34 | ] 35 | ]; 36 | 37 | CloseLoop(pointarrays); 38 | 39 | // Everything below this point places the animated train on the track. 40 | $fa = 4; $fs = 0.4; 41 | 42 | module wheel() { 43 | color("DimGray") 44 | rotate_extrude() 45 | translate([15, 0]) 46 | offset(2) 47 | square([3, 12], center = true); 48 | color("Silver") 49 | for (a = [0:20:179]) 50 | rotate([90, 0, a]) 51 | scale([0.2, 1, 1]) 52 | cylinder(r = 4, h = 30, center = true); 53 | } 54 | 55 | module train() { 56 | color("Black") linear_extrude(10, center = true) offset(5) square([40, 200], center = true); 57 | for (x = [-80, -40, 40, 80]) translate([30, x, 0]) rotate([90, 0, 90]) wheel(); 58 | for (x = [-80, -40, 40, 80]) translate([-30, x, 0]) rotate([90, 0, 90]) wheel(); 59 | translate([0, 0, 22]) { 60 | color("Red") { 61 | hull() { 62 | scale([1, 0.3, 1]) sphere(20); 63 | translate([0, -100, 0]) scale([1, 0.3, 1]) sphere(20); 64 | } 65 | translate([0, -50, 0]) cylinder(r = 6, h = 25); 66 | translate([0, -50, 25]) sphere(6); 67 | translate([0, -80, 0]) cylinder(r = 6, h = 40); 68 | translate([0, -80, 50]) difference() { 69 | sphere(r = 13); 70 | translate([0, 0, 25]) cube(50, center = true); 71 | } 72 | difference() { 73 | translate([0, 40, 30]) cube([42, 70, 100], center = true); 74 | translate([0, 40, 50]) cube([44, 40, 40], center = true); 75 | translate([0, 55, 25]) cube([36, 90, 100], center = true); 76 | } 77 | translate([0, 50, 80]) linear_extrude(8, scale = 1.1) square([42, 90], center = true); 78 | } 79 | color("Black") for(x = [-92, -62, -32, -2]) 80 | translate([0, x, 0]) rotate([90, 0, 0]) cylinder(r = 20.2, h = 5); 81 | } 82 | } 83 | 84 | // Reuses the same functions that make the track to orient the train on 85 | // top of the track. This step does a numerical derivative of the height 86 | // of the track at the train's location to find the slope of the track. 87 | ztilt = atan2(20*(hillf($t+0.002)-hillf($t-0.002)), 60*0.004*6.28); 88 | // Note that multmatrix takes the same form of input as the Affine call 89 | // above, so we can apply these to geometries the same way as points. 90 | multmatrix(AffMerge([RotZt($t), Hills($t), ShiftX($t)])) 91 | translate([0,0,3]) 92 | rotate([ztilt, 0, 0]) 93 | multmatrix(RotXZ($t)) 94 | scale(0.06) mirror([0,1,0]) translate([0,0,18]) train(); 95 | 96 | // Train written in 2019 by Torsten Paul 97 | // 98 | // To the extent possible under law, the author(s) have dedicated all 99 | // copyright and related and neighboring rights to this software to the 100 | // public domain worldwide. This software is distributed without any 101 | // warranty. 102 | // 103 | // You should have received a copy of the CC0 Public Domain 104 | // Dedication along with this software. 105 | // If not, see . 106 | -------------------------------------------------------------------------------- /demo_wavy_donut.scad: -------------------------------------------------------------------------------- 1 | // Created in 2021 by Ryan A. Colyer. 2 | // This work is released with CC0 into the public domain. 3 | // https://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | use 6 | 7 | 8 | // [Scale X] [Shear X along Y] [Shear X along Z] [Translate X] 9 | // [Shear Y along X] [Scale Y] [Shear Y along Z] [Translate Y] 10 | // [Shear Z along X] [Shear Z along Y] [Scale Z] [Translate Z] 11 | // or rotation matrix [[cos,-sin],[sin,cos]] in the 2 axes for a plane. 12 | function PathMatrix(t) = 13 | [[cos(t*360), -sin(t*360), 0, 50*cos(t*360)], 14 | [sin(t*360), cos(t*360), 0, 40*sin(t*360)], 15 | [ 0, 0, 1, 0]]; 16 | 17 | function ThePolygon(t) = 18 | [for (a=[0:2:359.99]) 19 | (5+5*(1+cos(8*t*360))/2)*[cos(a), 0, -sin(a)] 20 | ]; 21 | 22 | 23 | pointarrays = 24 | [for (t=[0:0.002:0.99999]) 25 | [for (p=ThePolygon(t)) 26 | Affine(PathMatrix(t), p) 27 | ] 28 | ]; 29 | 30 | CloseLoop(pointarrays); 31 | 32 | -------------------------------------------------------------------------------- /images/demo_images.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcolyer/closepoints/8ce827f5a7fec8afe4ab9a13a3427d685bca2ce0/images/demo_images.gif --------------------------------------------------------------------------------