Merge branch 'ht/graphs'
[shapes.git] / examples / showcase / kjmrobot.shape
blob9d8d9db704213a378238db7e810373478b2d9032
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 ..Shapes..Graphics / elementary
30 ##needs ..Shapes..Graphics3D / elementary
31 ##needs ..Shapes..Graphics3D / metapostarrow
33 ##lookin ..Shapes
34 ##lookin ..Shapes..Geometry3D
37 ##unit u = 1cm
39 cylinderLid: \ radius height sides smallRadius →
40   [shift (0cm,0cm,height)] [] ( Graphics3D..newGroup << [Graphics3D..facet [immerse [Graphics..polyDisc radius sides]]]
41                                            << [Graphics..stroke [immerse [Graphics..polyDisc radius sides]]]
42                                            << Traits..@nonstroking:[Traits..gray 0.3] | [Graphics3D..facet [immerse [Graphics..polyDisc smallRadius sides]] tiebreaker:1bp]
43                                 )
45 |** First object to be defined is a joint.
47 jointRadius: 0.7u
48 jointThickness: 0.5u  |** Take this times two to obtain the total height.
49 jointSurface:
50     Traits..@nonstroking:[Traits..gray 0.8]
51   & Traits..@width:0.01u            |** used to fill in object edges
52   & Traits..@stroking:[Traits..gray 0.8]    |** used to fill in object edges
53   & @reflections:0.3*[phong 25] + 0.7*[phong 0.5]
54   & @facetresolution:0.5u
55   & @shadeorder:'0          |** use '2 when targeting Adobe Reader or other viewers that can deal with gradient fills.
58 jointSides: '30
60 joint: jointSurface | ( Graphics3D..newGroup << [shift (0m,0m,~jointThickness)] [] [Graphics3D..cylinderWall jointRadius 2*jointThickness jointSides]
61                                    << [cylinderLid jointRadius ~jointThickness jointSides 0.3*jointRadius]
62                                    << [cylinderLid jointRadius jointThickness jointSides 0.3*jointRadius]
63                       )
65 jointYSeparation: 2*jointThickness + 0.1u
67 armTwist: 60°
68 armPath: \ tfStart tfEnd →
70   pStart: tfStart [] (0u,0u,0u)
71   pEnd: tfEnd [] (0u,0u,0u)
72   hr: 0.4 * [Numeric..Math..abs pEnd - pStart]
74   pStart > [tfStart [Geometry3D..rotate (0,0,1) ~armTwist] [] (hr,0u,0u)]
75   --
76   [tfEnd [Geometry3D..rotate (0,0,1) armTwist] [] (~hr,0u,0u)] < pEnd
79 edgyCylinder: \ pth0 pth1 →
80 [if [Geometry..duration pth0] ≠ [Geometry..duration pth1]
81     [error `edgyCylinder: Paths don't have the same duration.´]
83   •res: Graphics3D..newGroup
85   •res <<
86     [[Data..range '0 [Geometry..duration pth0]-'1].foldl
87        \ p e →
88          p
89          &
90          ( [Graphics3D..facet [pth0 e*1].p--[pth0 (e+'1)*1].p--[pth1 (e+'1)*1].p--cycle] )
91          &
92          ( [Graphics3D..facet [pth0 e*1].p--[pth1 (e+'1)*1].p--[pth1 e*1].p--cycle] )
93        Graphics3D..null]
95   •res <<
96     [[Data..range '0 [Geometry..duration pth0]].foldl
97       \ p e →
98         p & [Graphics..stroke [pth0 e*1].p--[pth1 e*1].p]
99       Graphics3D..null]
101   freeze •res
104 paintArm: \ pth width height steps →
106   lStep: [Numeric..Math..abs pth] / steps
108   [[Data..range '0 steps-'1].foldl
109    \ p e →
110      p
111      &
112      {
113        pth0:
114        {
115          sl: [pth e*lStep]
116          dx: sl.N * 0.5 * width
117          dy: sl.B * 0.5 * height
118          [shift sl.p] [] ( (~dx+~dy)--(~dx+dy)--(dx+dy)--(dx+~dy)--cycle )
119        }
121        pth1:
122        {
123          sl: [pth (e*1+0.999)*lStep]
124          dx: sl.N * 0.5 * width
125          dy: sl.B * 0.5 * height
126          [shift sl.p] [] ( (~dx+~dy)--(~dx+dy)--(dx+dy)--(dx+~dy)--cycle )
127        }
129       [edgyCylinder pth0 pth1]
130     }
131    Graphics3D..null]
134 myArrowHead: [Graphics3D..MetaPostArrow ahLength:8bp ...]
136 frameArrows:
138   •res: Graphics3D..newGroup
140   l: 2u
142   x: (l,0m,0m)
143   y: (0m,l,0m)
144   z: (0m,0m,l)
146   •res << [Graphics..Tag..tag 'x x]
147        << [Graphics..Tag..tag 'y y]
148        << [Graphics..Tag..tag 'z z]
150   Traits..@stroking:Traits..RGB..BLUE
151   & Traits..@nonstroking:Traits..RGB..BLUE
152   |
153   {
154     •res << [Graphics..stroke (0m,0m,0m)--x head:[myArrowHead (0,~1,0) ...]]
155     •res << [Graphics..stroke (0m,0m,0m)--y head:[myArrowHead (0,0,1) ...]]
156     •res << [Graphics..stroke (0m,0m,0m)--z head:[myArrowHead (0,~1,0) ...]]
157   }
159   freeze •res
162 /** The arguments <d1> and <d2> will be normalized, so they can be either float triples or coordinates to be interpreted
163  ** as relative to to <p>.  <r> is just the radius, measured along <d1> and <d2>.
164  ** Although <p> only shifts the result, it is taken as an argument as a convenience for the caller.
165  **/
166 rightAnglePath: \ p d1 d2 r →
168   p1: [normalized d1]*r
169   p2: [normalized d2]*r
170   [shift p] [] ( p1--(p1+p2)--p2 )
172 myRightAnglePath: [rightAnglePath r:0.5u ...]
173 myDash: Traits..@dash:[Traits..dashpattern 0.1u 0.05u]
175 |** The Denavit-Hartenberg parameters seen in the figure are set here.
176 |** The index $i-1$ is replaced by 0, $i$ by 1, and so forth.
177 alpha0: 25°
178 a0: 5u
179 d1: 2*jointThickness + 0.7u
180 theta1: 25°
181 alpha1: 0°
182 a1: 4u
183 d2: 2*jointThickness + 0.1u
184 theta2: 0° |** Never actually seen.
186 /** Setup frames according to figure.  The Denavit-Hartenberg parameters do not directly
187  ** correspond to the pieces of machinery that shall be drawn, but almost.  What they do not give
188  ** is the location of the upstream half of the joint, and since the rotation in the joint
189  ** applies only to the downstream half of the joint, it is more natural to define the frame of the
190  ** downstream half in terms of the upstream half, than vice versa.  The tf_d transforms define the
191  ** frames of the downstream half of the joints, that is, the frames that the parameterization cares
192  ** about.  The tf_u transforms define the frames of the upstream half of the joints, and are only
193  ** needed to draw the machinery (besides being used to define the downstream frames).
194  ** The difference between adjacent tf_u and tf_d is thus only a shift in the z-direction.
195  **/
196 tf0d: [Geometry3D..rotate (1,0,0) ~75°] * [Geometry3D..rotate (0,0,1) ~20°]
197 tf1u: tf0d * [shift (a0,0u,0u)] * [Geometry3D..rotate (1,0,0) alpha0] * [shift (0u,0u,d1-jointYSeparation)]
198 tf1d: tf1u * [shift (0u,0u,jointYSeparation)] * [Geometry3D..rotate (0,0,1) theta1]
199 tf2u: tf1d * [shift (a1,0u,0u)] * [Geometry3D..rotate (1,0,0) alpha1] * [shift (0u,0u,d2-jointYSeparation)]
200 tf2d: tf2u * [shift (0u,0u,jointYSeparation)] * [Geometry3D..rotate (0,0,1) theta2]
202 frame0: tf0d [] frameArrows
203 frame1: tf1d [] frameArrows
205 |** This is the part of the picture where the machinery is drawn.
206 •zbuf: Graphics3D..newZSorter
207 •zbuf << [shift (0cm,0cm,10cm)] [] [specular_light [Traits..gray 0.9]]
208 •zbuf << [ambient_light [Traits..gray 0.3]]
210 |** Annotations to be placed on top of the z-buffer goes here.
211 •zbufAnnot: Graphics3D..newGroup
213 |** This is the part of the picture where parameters are shown.
214 •pbuf: Graphics3D..newGroup
216 •zbuf << tf0d [] joint
217 •zbuf << tf1u [] joint
218 •zbufAnnot << Traits..@nonstroking:Traits..BW..WHITE | tf1u [] [facing [Layout..center [Graphics..TeX `\small Link $i$´] (0,2)]]
219 •zbuf << tf1d [] joint
220 •zbuf << tf2u [] joint
221 •zbufAnnot << Traits..@nonstroking:Traits..BW..WHITE | tf2u [] [facing [Layout..center [Graphics..TeX `\small Link $i+1$´] (0,1.2)]]
223 •zbuf << jointSurface | [paintArm [armPath tf0d tf1u] 0.5*jointRadius 0.2*jointRadius '10]
224 •zbuf << jointSurface | [paintArm [armPath tf1d tf2u] 0.5*jointRadius 0.2*jointRadius '10]
226 /** The axes defined here turn out to be very useful, since they provide
227  ** a compact way to refer to certain directions in the picture.
228  **/
229 axis0: tf0d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
230 axis1: tf1d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
231 axis2: tf2d [] (0u,0u,0u)--(0u,0u,~5*jointThickness)
234 •zbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis0]
235 •zbuf << frame0
236 •zbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis1]
237 •zbuf << frame1
238 •zbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis2]
240 •pbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis0]
241 •pbuf << frame0
242 •pbuf << [shift [Graphics..Tag..find frame0 'x]] [] [facing [Layout..center_wlm [Graphics..TeX `$x_{i-1}$´] (0,~1)]]
243 •pbuf << [shift [Graphics..Tag..find frame0 'y]] [] [facing [Layout..center_wlm [Graphics..TeX `$y_{i-1}$´] (0,~1)]]
244 •pbuf << [shift [Graphics..Tag..find frame0 'z]] [] [facing [Layout..center_wlm [Graphics..TeX `$z_{i-1}$´] (1,0)]]
245 •pbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis1]
246 •pbuf << frame1
247 •pbuf << [shift [Graphics..Tag..find frame1 'x]] [] [facing [Layout..center_wlm [Graphics..TeX `$x_{i}$´] (0,~1)]]
248 •pbuf << [shift [Graphics..Tag..find frame1 'y]] [] [facing [Layout..center_wlm [Graphics..TeX `$y_{i}$´] (1,0)]]
249 •pbuf << [shift [Graphics..Tag..find frame1 'z]] [] [facing [Layout..center_wlm [Graphics..TeX `$z_{i}$´] (1,0)]]
250 •pbuf << Traits..@stroking:Traits..RGB..RED | [Graphics..stroke axis2]
251 Traits..@width: 0.5bp
254   angleR: 2u
255   angleExtra: 0.3u
256   {
257     |** This is the distance $a_{i-1}$ along with angle marks.
258     pth: tf0d [] ( (0u,0u,0u)--(a0,0u,0u) )
259     •pbuf << stroke [] pth
260     •pbuf << [shift [pth 0.5*[Numeric..Math..abs pth]].p] [] [facing [Layout..center_wlm [Graphics..TeX `$a_{i-1}$´] (0,1)]]
261     •pbuf << stroke [] [myRightAnglePath [tf0d (0u,0u,0u)] [axis0 0].T [pth 0].T]
262     •pbuf << stroke [] [myRightAnglePath [tf0d (a0,0u,0u)] [axis1 0].T [pth 1].rT]
263   }
264   {
265     |** This is the distance $a_{i}$ along with angle marks.
266     pth: tf1d [] ( (0u,0u,0u)--(a1,0u,0u) )
267     •pbuf << stroke [] pth
268     •pbuf << [shift [pth 0.5*[Numeric..Math..abs pth]].p] [] [facing [Layout..center_wlm [Graphics..TeX `$a_{i}$´] (0,1)]]
269     •pbuf << stroke [] [myRightAnglePath [tf1d (0u,0u,0u)] [axis1 0].T [pth 0].T]
270     •pbuf << stroke [] [myRightAnglePath [tf1d (a1,0u,0u)] [axis2 0].T [pth 1].rT]
272     |** This is the angle $\theta_{i}$ along helpers and angle marks.
273     helper1: [shift [tf0d (a0,0u,0u)]] [] ( (0u,0u,0u)--((angleR+angleExtra)*[pth 0].T) )
274     helper2: tf0d [] ( (a0,0u,0u)--(a0+angleR+angleExtra,0u,0u) )
275     |** At the moment, the easy way to define circular arcs in 3D is to immerse a 2D arc.
276     /** In the choice of using tf1d or tf1u, I chose tf1d, which makes the positioning rather natural,
277      ** although the angles do not really correspond to the figure.
278      **/
279     arc: tf1d*[shift (0u,0u,~d1)] [] [immerse (angleR*[dir ~theta1])>(1%c^90°-theta1)--(1%c^~90°)<(angleR,0)]
280     •pbuf << myDash | stroke [] helper1
281     •pbuf << myDash | stroke [] helper2
282     •pbuf << [Graphics..stroke arc head:[Graphics3D..MetaPostArrow (0,0,1) ahLength:5bp ...]]
283     •pbuf << [shift [arc 0.5*[Numeric..Math..abs arc]].p] [] [facing [Layout..center_wlm [Graphics..TeX `$\theta_{i}$´] (~1,0)]]
284   }
285   {
286     |** This is the angle $\alpha_{i-1}$ along helpers and angle marks.
287     helper: [shift [tf0d (0u,0u,0u)]] [] ( (0u,0u,0u)--((angleR+angleExtra)*[axis1 0].T) )
288     |** At the moment, the easy way to define circular arcs in 3D is to immerse a 2D arc.
289     arc: tf0d*[Geometry3D..rotate (0,0,1) 90°]*[Geometry3D..rotate (1,0,0) 90°] [] [immerse (0,~angleR)>(1%c^0°)--(1%c^180°+alpha0)<(angleR*[dir ~90°+alpha0])]
290     •pbuf << myDash | stroke [] helper
291     •pbuf << [Graphics..stroke arc head:[Graphics3D..MetaPostArrow [axis1 0].T ahLength:5bp ...]]
292     •pbuf << [shift [arc 0.5*[Numeric..Math..abs arc]].p] [] [facing [Layout..center_wlm [Graphics..TeX `$\alpha_{i-1}$´] (~1,1)]]
293   }
295 •pbuf << [shift 0.5 * ([tf0d (a0,0u,0u)] + [tf1d (0u,0u,0u)])] [] [facing [Layout..center_wlm [Graphics..TeX `$d_{i}$´] (~1,0)]]
297 zbuf: freeze •zbuf
298 zbufAnnot: freeze •zbufAnnot
299 pbuf: freeze •pbuf
301 |** Finally, the two buffers are drawn.  The parameters are moved down a bit to avoid cluttering.
302 @eyez:40u
305   IO..•page << [view zbuf]
306         << [view zbufAnnot]
307   IO..•page << [shift (0,~5u)] [] [view pbuf]