├── 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 |

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
--------------------------------------------------------------------------------