Improvements to namespaces and file inclusion
[shapes.git] / examples / showcase / kjmrobot.shape
blob7c16949733e9d8efc57991f989ef375479efe68a
1 /** This file is part of Shapes.
2  **
3  ** Shapes is free software: you can redistribute it and/or modify
4  ** it under the terms of the GNU General Public License as published by
5  ** the Free Software Foundation, either version 3 of the License, or
6  ** any later version.
7  **
8  ** Shapes is distributed in the hope that it will be useful,
9  ** but WITHOUT ANY WARRANTY; without even the implied warranty of
10  ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  ** GNU General Public License for more details.
12  **
13  ** You should have received a copy of the GNU General Public License
14  ** along with Shapes.  If not, see <http://www.gnu.org/licenses/>.
15  **
16  ** Copyright 2008, 2010, 2014 Henrik Tidefelt
17  **/
19 /** This example is included to compare Shapes with Sketch.
20  ** Kjell Magne Fauske draw a nice robot arm using Sketch, and I want to see
21  ** what the corresponding Shapes source could look like (if possible to reproduce at all).
22  ** Many thanks to Kjell Magne for letting me use his example.
23  **
24  ** Unfortunately, I created this example at a time when I was not familiar with the
25  ** Denavit-Hartenberg convention, so I am afraid that the source is not fully
26  ** utilizing the beauty of the convention, even though it produces the correct picture.
27  **/
29 ##needs shapes2D
30 ##needs shapes3D
32 ##lookin ..Shapes
35 ##unit u = 1cm
37 cylinderLid: \ radius height sides smallRadius →
38   [shift (0cm,0cm,height)] [] ( newGroup3D << [facet [immerse [polyDisc radius sides]]]
39                                            << [stroke [immerse [polyDisc radius sides]]]
40                                            << @nonstroking:[gray 0.3] | [facet [immerse [polyDisc smallRadius sides]] tiebreaker:1bp]
41                                 )
43 |** First object to be defined is a joint.
45 jointRadius: 0.7u
46 jointThickness: 0.5u  |** Take this times two to obtain the total height.
47 jointSurface:
48     @nonstroking:[gray 0.8]
49   & @width:0.01u            |** used to fill in object edges
50   & @stroking:[gray 0.8]    |** used to fill in object edges
51   & @reflections:0.3*[phong 25] + 0.7*[phong 0.5]
52   & @facetresolution:0.5u
53   & @shadeorder:'0          |** use '2 when targeting Adobe Reader or other viewers that can deal with gradient fills.
56 jointSides: '30
58 joint: jointSurface | ( newGroup3D << [shift (0m,0m,~jointThickness)] [] [cylinderWall jointRadius 2*jointThickness jointSides]
59                                    << [cylinderLid jointRadius ~jointThickness jointSides 0.3*jointRadius]
60                                    << [cylinderLid jointRadius jointThickness jointSides 0.3*jointRadius]
61                       )
63 jointYSeparation: 2*jointThickness + 0.1u
65 armTwist: 60°
66 armPath: \ tfStart tfEnd →
68   pStart: tfStart [] (0u,0u,0u)
69   pEnd: tfEnd [] (0u,0u,0u)
70   hr: 0.4 * [abs pEnd - pStart]
72   pStart > [tfStart [rotate3D (0,0,1) ~armTwist] [] (hr,0u,0u)]
73   --
74   [tfEnd [rotate3D (0,0,1) armTwist] [] (~hr,0u,0u)] < pEnd
77 edgyCylinder: \ pth0 pth1 →
78 [if [duration pth0] ≠ [duration pth1]
79     [error `edgyCylinder: Paths don't have the same duration.´]
81   •res: newGroup3D
83   •res <<
84     [[range '0 [duration pth0]-'1].foldl
85        \ p e →
86          p
87          &
88          ( [facet [pth0 e*1].p--[pth0 (e+'1)*1].p--[pth1 (e+'1)*1].p--cycle] )
89          &
90          ( [facet [pth0 e*1].p--[pth1 (e+'1)*1].p--[pth1 e*1].p--cycle] )
91        null3D]
93   •res <<
94     [[range '0 [duration pth0]].foldl
95       \ p e →
96         p & [stroke [pth0 e*1].p--[pth1 e*1].p]
97       null3D]
99   •res;
102 paintArm: \ pth width height steps →
104   lStep: [abs pth] / steps
106   [[range '0 steps-'1].foldl
107    \ p e →
108      p
109      &
110      {
111        pth0:
112        {
113          sl: [pth e*lStep]
114          dx: sl.N * 0.5 * width
115          dy: sl.B * 0.5 * height
116          [shift sl.p] [] ( (~dx+~dy)--(~dx+dy)--(dx+dy)--(dx+~dy)--cycle )
117        }
119        pth1:
120        {
121          sl: [pth (e*1+0.999)*lStep]
122          dx: sl.N * 0.5 * width
123          dy: sl.B * 0.5 * height
124          [shift sl.p] [] ( (~dx+~dy)--(~dx+dy)--(dx+dy)--(dx+~dy)--cycle )
125        }
127       [edgyCylinder pth0 pth1]
128     }
129    null3D]
132 myArrowHead: [MetaPostArrow3D ahLength:8bp ...]
134 frameArrows:
136   •res: newGroup3D
138   l: 2u
140   x: (l,0m,0m)
141   y: (0m,l,0m)
142   z: (0m,0m,l)
144   •res << [tag 'x x]
145        << [tag 'y y]
146        << [tag 'z z]
148   @stroking:RGB_BLUE
149   & @nonstroking:RGB_BLUE
150   |
151   {
152     •res << [stroke (0m,0m,0m)--x head:[myArrowHead (0,~1,0) ...]]
153     •res << [stroke (0m,0m,0m)--y head:[myArrowHead (0,0,1) ...]]
154     •res << [stroke (0m,0m,0m)--z head:[myArrowHead (0,~1,0) ...]]
155   }
157   •res;
160 /** The arguments <d1> and <d2> will be normalized, so they can be either float triples or coordinates to be interpreted
161  ** as relative to to <p>.  <r> is just the radius, measured along <d1> and <d2>.
162  ** Although <p> only shifts the result, it is taken as an argument as a convenience for the caller.
163  **/
164 rightAnglePath: \ p d1 d2 r →
166   p1: [normalized d1]*r
167   p2: [normalized d2]*r
168   [shift p] [] ( p1--(p1+p2)--p2 )
170 myRightAnglePath: [rightAnglePath r:0.5u ...]
171 myDash: @dash:[dashpattern 0.1u 0.05u]
173 |** The Denavit-Hartenberg parameters seen in the figure are set here.
174 |** The index $i-1$ is replaced by 0, $i$ by 1, and so forth.
175 alpha0: 25°
176 a0: 5u
177 d1: 2*jointThickness + 0.7u
178 theta1: 25°
179 alpha1: 0°
180 a1: 4u
181 d2: 2*jointThickness + 0.1u
182 theta2: 0° |** Never actually seen.
184 /** Setup frames according to figure.  The Denavit-Hartenberg parameters do not directly
185  ** correspond to the pieces of machinery that shall be drawn, but almost.  What they do not give
186  ** is the location of the upstream half of the joint, and since the rotation in the joint
187  ** applies only to the downstream half of the joint, it is more natural to define the frame of the
188  ** downstream half in terms of the upstream half, than vice versa.  The tf_d transforms define the
189  ** frames of the downstream half of the joints, that is, the frames that the parameterization cares
190  ** about.  The tf_u transforms define the frames of the upstream half of the joints, and are only
191  ** needed to draw the machinery (besides being used to define the downstream frames).
192  ** The difference between adjacent tf_u and tf_d is thus only a shift in the z-direction.
193  **/
194 tf0d: [rotate3D (1,0,0) ~75°] * [rotate3D (0,0,1) ~20°]
195 tf1u: tf0d * [shift (a0,0u,0u)] * [rotate3D (1,0,0) alpha0] * [shift (0u,0u,d1-jointYSeparation)]
196 tf1d: tf1u * [shift (0u,0u,jointYSeparation)] * [rotate3D (0,0,1) theta1]
197 tf2u: tf1d * [shift (a1,0u,0u)] * [rotate3D (1,0,0) alpha1] * [shift (0u,0u,d2-jointYSeparation)]
198 tf2d: tf2u * [shift (0u,0u,jointYSeparation)] * [rotate3D (0,0,1) theta2]
200 frame0: tf0d [] frameArrows
201 frame1: tf1d [] frameArrows
203 |** This is the part of the picture where the machinery is drawn.
204 •zbuf: newZSorter
205 •zbuf << [shift (0cm,0cm,10cm)] [] [specular_light [gray 0.9]]
206 •zbuf << [ambient_light [gray 0.3]]
208 |** Annotations to be placed on top of the z-buffer goes here.
209 •zbufAnnot: newGroup3D
211 |** This is the part of the picture where parameters are shown.
212 •pbuf: newGroup3D
214 •zbuf << tf0d [] joint
215 •zbuf << tf1u [] joint
216 •zbufAnnot << @nonstroking:GRAY_WHITE | tf1u [] [facing [center [TeX `\small Link $i$´] (0,2)]]
217 •zbuf << tf1d [] joint
218 •zbuf << tf2u [] joint
219 •zbufAnnot << @nonstroking:GRAY_WHITE | tf2u [] [facing [center [TeX `\small Link $i+1$´] (0,1.2)]]
221 •zbuf << jointSurface | [paintArm [armPath tf0d tf1u] 0.5*jointRadius 0.2*jointRadius '10]
222 •zbuf << jointSurface | [paintArm [armPath tf1d tf2u] 0.5*jointRadius 0.2*jointRadius '10]
224 /** The axes defined here turn out to be very useful, since they provide
225  ** a compact way to refer to certain directions in the picture.
226  **/
227 axis0: tf0d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
228 axis1: tf1d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
229 axis2: tf2d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
232 •zbuf << @stroking:RGB_RED | [stroke axis0]
233 •zbuf << frame0
234 •zbuf << @stroking:RGB_RED | [stroke axis1]
235 •zbuf << frame1
236 •zbuf << @stroking:RGB_RED | [stroke axis2]
238 •pbuf << @stroking:RGB_RED | [stroke axis0]
239 •pbuf << frame0
240 •pbuf << [shift [find frame0 'x]] [] [facing [center_wlm [TeX `$x_{i-1}$´] (0,~1)]]
241 •pbuf << [shift [find frame0 'y]] [] [facing [center_wlm [TeX `$y_{i-1}$´] (0,~1)]]
242 •pbuf << [shift [find frame0 'z]] [] [facing [center_wlm [TeX `$z_{i-1}$´] (1,0)]]
243 •pbuf << @stroking:RGB_RED | [stroke axis1]
244 •pbuf << frame1
245 •pbuf << [shift [find frame1 'x]] [] [facing [center_wlm [TeX `$x_{i}$´] (0,~1)]]
246 •pbuf << [shift [find frame1 'y]] [] [facing [center_wlm [TeX `$y_{i}$´] (1,0)]]
247 •pbuf << [shift [find frame1 'z]] [] [facing [center_wlm [TeX `$z_{i}$´] (1,0)]]
248 •pbuf << @stroking:RGB_RED | [stroke axis2]
249 @width: 0.5bp
252   angleR: 2u
253   angleExtra: 0.3u
254   {
255     |** This is the distance $a_{i-1}$ along with angle marks.
256     pth: tf0d [] ( (0u,0u,0u)--(a0,0u,0u) )
257     •pbuf << stroke [] pth
258     •pbuf << [shift [pth 0.5*[abs pth]].p] [] [facing [center_wlm [TeX `$a_{i-1}$´] (0,1)]]
259     •pbuf << stroke [] [myRightAnglePath [tf0d (0u,0u,0u)] [axis0 0].T [pth 0].T]
260     •pbuf << stroke [] [myRightAnglePath [tf0d (a0,0u,0u)] [axis1 0].T [pth 1].rT]
261   }
262   {
263     |** This is the distance $a_{i}$ along with angle marks.
264     pth: tf1d [] ( (0u,0u,0u)--(a1,0u,0u) )
265     •pbuf << stroke [] pth
266     •pbuf << [shift [pth 0.5*[abs pth]].p] [] [facing [center_wlm [TeX `$a_{i}$´] (0,1)]]
267     •pbuf << stroke [] [myRightAnglePath [tf1d (0u,0u,0u)] [axis1 0].T [pth 0].T]
268     •pbuf << stroke [] [myRightAnglePath [tf1d (a1,0u,0u)] [axis2 0].T [pth 1].rT]
270     |** This is the angle $\theta_{i}$ along helpers and angle marks.
271     helper1: [shift [tf0d (a0,0u,0u)]] [] ( (0u,0u,0u)--((angleR+angleExtra)*[pth 0].T) )
272     helper2: tf0d [] ( (a0,0u,0u)--(a0+angleR+angleExtra,0u,0u) )
273     |** At the moment, the easy way to define circular arcs in 3D is to immerse a 2D arc.
274     /** In the choice of using tf1d or tf1u, I chose tf1d, which makes the positioning rather natural,
275      ** although the angles do not really correspond to the figure.
276      **/
277     arc: tf1d*[shift (0u,0u,~d1)] [] [immerse (angleR*[dir ~theta1])>(1%c^90°-theta1)--(1%c^~90°)<(angleR,0)]
278     •pbuf << myDash | stroke [] helper1
279     •pbuf << myDash | stroke [] helper2
280     •pbuf << [stroke arc head:[MetaPostArrow3D (0,0,1) ahLength:5bp ...]]
281     •pbuf << [shift [arc 0.5*[abs arc]].p] [] [facing [center_wlm [TeX `$\theta_{i}$´] (~1,0)]]
282   }
283   {
284     |** This is the angle $\alpha_{i-1}$ along helpers and angle marks.
285     helper: [shift [tf0d (0u,0u,0u)]] [] ( (0u,0u,0u)--((angleR+angleExtra)*[axis1 0].T) )
286     |** At the moment, the easy way to define circular arcs in 3D is to immerse a 2D arc.
287     arc: tf0d*[rotate3D (0,0,1) 90°]*[rotate3D (1,0,0) 90°] [] [immerse (0,~angleR)>(1%c^0°)--(1%c^180°+alpha0)<(angleR*[dir ~90°+alpha0])]
288     •pbuf << myDash | stroke [] helper
289     •pbuf << [stroke arc head:[MetaPostArrow3D [axis1 0].T ahLength:5bp ...]]
290     •pbuf << [shift [arc 0.5*[abs arc]].p] [] [facing [center_wlm [TeX `$\alpha_{i-1}$´] (~1,1)]]
291   }
293 •pbuf << [shift 0.5 * ([tf0d (a0,0u,0u)] + [tf1d (0u,0u,0u)])] [] [facing [center_wlm [TeX `$d_{i}$´] (~1,0)]]
295 zbuf: •zbuf;
296 zbufAnnot: •zbufAnnot;
297 pbuf: •pbuf;
299 |** Finally, the two buffers are drawn.  The parameters are moved down a bit to avoid cluttering.
300 @eyez:40u
303   •page << [view zbuf]
304         << [view zbufAnnot]
305   •page << [shift (0,~5u)] [] [view pbuf]