├── racket-lightsaber.png ├── README.md └── lightsaber.rkt /racket-lightsaber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zamora/lightsaber/HEAD/racket-lightsaber.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lightsaber 2 | Draw lightsabers using Racket's pict library (https://docs.racket-lang.org/pict/index.html) 3 | 4 | Created for Stephen De Gabrielle's Summer 2019 standard-fish competition 5 | 6 | The lightsaber function produces a pict of a lightsaber. The only required argument is a color, which can be either a color name or a color% object. A length can be provided as an optional argument, as well as a style for the lightsaber hilt. The default hilt is Luke Skywalker's (#:style 'luke), but you can also select Darth Vader's (#:style 'vader), Kylo Ren's (#:style 'kylo), or Darth Maul's (#:style 'maul). See racket-lightsaber.png for examples of each. 7 | 8 | Modification History 9 | ------------------------------------------------- 10 | 07/27/2019 Justin Zamora Initial creation 11 | -------------------------------------------------------------------------------- /lightsaber.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | ; lightsaber.rkt 4 | ; 5 | ; Draw lightsabers using Racket's pict library 6 | ; Created for Stephen De Gabrielle's Summer 2019 standard-fish competition 7 | ; 8 | ; Modification History 9 | ; ------------------------------------------------- 10 | ; 07/27/2019 Justin Zamora Initial creation 11 | 12 | (provide lightsaber) 13 | 14 | (require racket/contract) 15 | (require racket/draw) 16 | (require pict) 17 | 18 | ;------------------------------------------------------- 19 | ; 20 | ; Helper Functions 21 | 22 | ; Predicate for contract checking the supported hilt styles 23 | (define hilt-style? (one-of/c 'luke 'vader 'kylo 'maul)) 24 | 25 | ; Generate a shade of gray 26 | (define (gray level) 27 | (make-color level level level)) 28 | 29 | ; Flip a pict horizontally. This should really be a part of the pict library 30 | (define (flip-horizontal pict) 31 | (inset (scale pict -1 1) (pict-width pict) 0)) 32 | 33 | ; Flip a pict vertically. This should really be a part of the pict library 34 | (define (flip-vertical pict) 35 | (inset (scale pict 1 -1) 0 (pict-height pict))) 36 | 37 | ;------------------------------------------------------- 38 | ; 39 | ; Main Function 40 | 41 | ; Create a pict of a lightsaber. Accept a color object or a color name. 42 | ; An optional length can be specified (useful for animating blade extension) 43 | (define/contract (lightsaber color 44 | #:length [length 400] 45 | #:style [hilt-style 'luke]) 46 | (->* ((or/c string? (is-a?/c color%))) (#:length nonnegative-integer? 47 | #:style hilt-style?) pict?) 48 | 49 | ; Convert color to a color%, if necessary 50 | (define color-obj (cond 51 | [(string? color) (send the-color-database find-color color)] 52 | [else color])) 53 | 54 | ; Generate the hilt and blade and combine them. 55 | (let ([the-hilt (hilt hilt-style)]) 56 | (cond 57 | ; This uses knowledge about how Kylo's saber is drawn that it 58 | ; probably shouldn't know in order to draw the cross-blades, but 59 | ; drawing the cross-blades in the hilt routine has its own 60 | ; disadvantages. 61 | [(eq? hilt-style 'kylo) (cond 62 | [(zero? length) the-hilt] 63 | [else (let ([cross (blade color-obj (/ length 2.75))]) 64 | (lc-superimpose (inset (rotate cross (/ pi 2)) 65 | 100 7 0 0) 66 | (inset (blade color-obj length) 67 | (hilt-horiz-offset hilt-style) 68 | (hilt-vert-offset hilt-style) 69 | 0 0) 70 | the-hilt))])] 71 | ; If the blade length is zero, use a null blade so that the pict height 72 | ; stays the same regardless of length 73 | [else (lc-superimpose (inset (if (zero? length) 74 | (inset (hline 0 0) 0 50 0 0) 75 | (blade color-obj length)) 76 | (hilt-horiz-offset hilt-style) 77 | (hilt-vert-offset hilt-style) 78 | 0 0) 79 | the-hilt)]))) 80 | 81 | ; Create a pict of the lightsaber blade 82 | (define (blade color length) 83 | (define r (send color red)) 84 | (define g (send color green)) 85 | (define b (send color blue)) 86 | 87 | ; Produce glows with transparency and offset from the blade center 88 | (define (glow alpha offset) 89 | (filled-rounded-rectangle (+ length offset) offset -0.5 90 | #:color (make-color r g b alpha) 91 | #:draw-border? #false)) 92 | 93 | ; Pict of the white core 94 | (define core (filled-rounded-rectangle (+ length 10) 10 -0.5 95 | #:color "White" 96 | #:draw-border? #false)) 97 | 98 | ; Stack the glows and the core to create the blade 99 | (for/fold ([pict (glow 0.1 50)] 100 | [offset 40] 101 | #:result (cc-superimpose pict core)) 102 | ([alpha (list 0.2 0.4 0.8)]) 103 | (values (cc-superimpose (glow alpha offset) pict) 104 | (- offset 10)))) 105 | 106 | ;------------------------------------------------------- 107 | ; 108 | ; Functions for drawing hilts 109 | 110 | ; Helper macro for filled-rectangle 111 | (define-syntax filled-rect 112 | (syntax-rules () 113 | [(filled-rect w h c) (filled-rectangle w h #:color c #:draw-border? #false)])) 114 | 115 | ; Helper macro for filled-rounded-rectangle 116 | (define-syntax filled-rounded-rect 117 | (syntax-rules () 118 | [(filled-rounded-rect w h c) (filled-rounded-rectangle w h 5 #:color c #:draw-border? #false)])) 119 | 120 | ; Draw the hilt, using the given style 121 | (define/contract (hilt style) 122 | (-> hilt-style? pict?) 123 | (case style 124 | [(luke) luke-hilt] 125 | [(vader) vader-hilt] 126 | [(kylo) kylo-hilt] 127 | [(maul) maul-hilt] 128 | [else (error "Unrecognized style: ~a" style)])) 129 | 130 | ; How much to offset the blade horizontally for the specific style of hilt 131 | (define (hilt-horiz-offset style) 132 | (case style 133 | [(luke) 111] 134 | [(vader) 111] 135 | [(kylo) 111] 136 | [(maul) 135] 137 | [else (error "Unrecognized style: ~a" style)])) 138 | 139 | ; How much to offset the blade vertically for the specific style of hilt 140 | (define (hilt-vert-offset style) 141 | (case style 142 | [(luke) 7] 143 | [(vader) 1] 144 | [(kylo) 0] 145 | [(maul) 0] 146 | [else (error "Unrecognized style: ~a" style)])) 147 | 148 | ;------------------------------------------------------- 149 | ; 150 | ; Draw Luke Skywalker's lightsaber hilt 151 | (define luke-hilt 152 | (let ([hilt-background 153 | (lc-superimpose 154 | ; Grip and Switch 155 | (inset (rb-superimpose 156 | (inset (filled-rounded-rect 68 40 "Black") 0 0 0 0) ; Grip 157 | (inset (filled-rounded-rect 31 20 "Black") 0 0 5 27)) ; Switch 158 | 0 0 0 0) 159 | ; Middle Tube 160 | (inset (filled-rounded-rect 41 30 "Black") 63 7 0 0) 161 | ; Gold Connector 1 162 | (inset (filled-rect 8 29 "Black") 106 6 0 0) 163 | ; Gold Connector 2 164 | (inset (filled-rect 8 21 "Black") 110 7 0 0) 165 | ; Emitter Base 166 | (inset (filled-rect 10 29 "Black") 117 8 0 1) 167 | ; Emitter Plate 168 | (inset (filled-rounded-rect 9 43 "Black") 126.5 7 0 0))] 169 | [hilt-foreground 170 | (hc-append (vl-append (filled-rect 2 10 (gray 90)) 171 | (filled-rect 2 7 (gray 125)) 172 | (filled-rect 2 10 (gray 90)) 173 | (filled-rect 2 7 (gray 30))) 174 | (vl-append (filled-rect 5 10 (gray 140)) 175 | (filled-rect 5 7 (gray 185)) 176 | (filled-rect 5 10 (gray 140)) 177 | (filled-rect 5 7 (gray 45))) 178 | ; Grip and Switch 179 | (inset (rb-superimpose 180 | ; Grip 181 | (vl-append (filled-rect 55 10 (gray 175)) 182 | (filled-rect 55 7 (gray 245)) 183 | (filled-rect 55 10 (gray 175)) 184 | (filled-rect 55 7 (gray 95))) 185 | ; Switch and Shadow 186 | (inset (vl-append (filled-rect 25 7 (make-color 165 95 35)) 187 | (filled-rect 25 2 (make-color 90 40 5)) 188 | (filled-rect 25 5 (gray 128))) 189 | 0 0 5 27)) 190 | 0 0 0 6) 191 | ; Middle part of hilt 192 | (vl-append (filled-rect 2 10 (gray 124)) 193 | (filled-rect 2 7 (gray 95)) 194 | (filled-rect 2 7 (gray 30))) 195 | (vl-append (filled-rect 2 10 (gray 184)) 196 | (filled-rect 2 7 (gray 140)) 197 | (filled-rect 2 7 (gray 44))) 198 | (vl-append (filled-rect 32 10 (gray 245)) 199 | (filled-rect 32 7 (gray 185)) 200 | (filled-rect 32 7 (gray 100))) 201 | ; Emitter section 202 | (filled-rect 7 15 (gray 25)) 203 | (filled-rect 2 25 (make-color 135 85 30)) ; Gold connector 204 | (filled-rect 2 25 (make-color 185 135 85)) ; Gold connector 205 | (filled-rect 5 17 (make-color 135 85 30)) ; Gold connector 206 | (filled-rect 2 17 (make-color 100 60 10)) ; Gold connector 207 | (filled-rect 5 25 (gray 155)) 208 | (filled-rect 3 25 (gray 130)) 209 | (filled-rect 2 25 (gray 90)) 210 | (filled-rect 2 37 (gray 150)) 211 | (filled-rect 2 37 (gray 230)))]) 212 | (lc-superimpose hilt-background 213 | (inset hilt-foreground 3 7 0 0)))) 214 | 215 | ;------------------------------------------------------- 216 | ; 217 | ; Draw Darth Vader's lightsaber hilt 218 | 219 | (define vader-hilt 220 | (lc-superimpose 221 | (hc-append 222 | ; Main Tube 223 | (cc-superimpose (filled-rounded-rectangle 117 35 5 #:color "Black") 224 | (vl-append (filled-rect 110 11 (gray 160)) 225 | (filled-rect 110 2 (gray 90)) 226 | (filled-rect 110 8(gray 245)) 227 | (filled-rect 110 8 (gray 80)))) 228 | ; Emitter 229 | (inset/clip (shear (vl-append (filled-rect 87 15 (gray 40)) 230 | (filled-rect 87 11 (gray 60)) 231 | (filled-rect 87 9 (gray 20))) 232 | -0.75 0) 233 | -75 0 0 0)) 234 | ; Grips 235 | (inset (vl-append 9 (filled-rect 40 7 (gray 50)) 236 | (filled-rect 40 7 (gray 50)) 237 | (filled-rect 40 7 (gray 50))) 238 | 8 0 0 0) 239 | ; Center Band 240 | (inset (vl-append (filled-rect 25 15 (gray 40)) 241 | (filled-rect 25 11 (gray 60)) 242 | (filled-rect 25 9 (gray 20)) 243 | (filled-rect 25 7 (gray 40))) 244 | 65 7 0 0) 245 | ; Top Button 246 | (inset (filled-rect 15 5 (gray 60)) 247 | 105 0 0 34) 248 | ; Bottom Greeble 249 | (inset (filled-rect 10 6 "Black") 250 | 110 34 0 0))) 251 | 252 | ;------------------------------------------------------- 253 | ; 254 | ; Draw Kylo Ren's lightsaber hilt 255 | 256 | (define kylo-hilt 257 | (lc-superimpose 258 | ; Pommel and Grips 259 | (inset (filled-rectangle 6 35 #:color "Red" #:border-width 2) 1 1 0 0) 260 | (inset (rotate (filled-rectangle 25 5 #:color (gray 30) #:border-width 2) 261 | (/ pi -24)) 262 | 11 0 0 40) 263 | (inset (rotate (filled-rectangle 25 5 #:color (gray 80) #:border-width 2) 264 | (/ pi 24)) 265 | 11 43 0 0) 266 | 267 | ; Main Tube 268 | (inset (filled-rectangle 92 40 #:color "Black" #:border-width 2) 7 1 0 0) 269 | (inset (vl-append (filled-rect 92 10 (gray 30)) 270 | (filled-rect 92 10 (gray 60)) 271 | (filled-rect 92 10(gray 90)) 272 | (filled-rect 92 10 (gray 70))) 273 | 7 0 0 0) 274 | 275 | ; Greebles 276 | (inset (filled-rectangle 12 12 #:color (gray 220) #:border-width 2) 9 0 0 0) 277 | (inset (filled-rectangle 30 10 #:color "LemonChiffon" #:border-width 2) 20 0 0 0) 278 | (inset (filled-rectangle 30 10 #:color (gray 235) #:border-width 2) 65 0 0 0) 279 | 280 | ; Quillons 281 | (let ([quillon (lt-superimpose (inset (shear (filled-rectangle 13 10 282 | #:color (gray 110) 283 | #:border-width 1) 284 | 0 0.75) 285 | -0.5 0 0 0) 286 | (inset (filled-rectangle 22 19 #:color "Black" 287 | #:border-width 2) 288 | 0 8 2 0) 289 | (inset (vl-append (filled-rect 22 5 (gray 130)) 290 | (filled-rect 22 9 (gray 80)) 291 | (filled-rect 22 5 (gray 30))) 292 | 0 8 0 0))]) 293 | 294 | (inset (vc-append 45.25 quillon (flip-vertical quillon)) 295 | 114 0 0 0)) 296 | 297 | ; Emitter 298 | (inset (filled-rectangle 45 44 #:color "Black" #:border-width 3) 99 0 0 0) 299 | (inset (vl-append (filled-rect 45 8 (gray 30)) 300 | (filled-rect 45 8 (gray 60)) 301 | (filled-rect 45 16(gray 90)) 302 | (filled-rect 45 12 (gray 70))) 303 | 99 0 0 0) 304 | (inset (inset (filled-rectangle 10 44 305 | #:color (make-color 150 150 150 0.6) 306 | #:border-width 1) 1) 307 | 100 0 0 0) 308 | (inset (linewidth 2 (vline 5 45)) 130 0 0 0) 309 | 310 | ; Red Wire 311 | (inset (colorize (linewidth 2 (hline 85 5)) "Firebrick") 14 0 0 32))) 312 | 313 | 314 | ;------------------------------------------------------- 315 | ; 316 | ; Draw Darth Maul's lightsaber hilt 317 | (define maul-hilt 318 | (let ([segment (lambda (width) ; Draw shaded gradient for the hilt 319 | (cc-superimpose (filled-rectangle (+ width 2) 36 320 | #:color "Black" 321 | #:border-width 2) 322 | (vc-append (filled-rect width 7 (gray 210)) 323 | (filled-rect width 10 (gray 240)) 324 | (filled-rect width 4 (gray 160)) 325 | (filled-rect width 6 (gray 190)) 326 | (filled-rect width 7 (gray 220)))))]) 327 | (lt-superimpose 328 | (hc-append 329 | ; Main Tube 330 | (filled-rectangle 3 36 #:color (gray 30) #:border-width 2) 331 | (segment 15) 332 | (segment 50) 333 | (segment 15) 334 | (filled-rectangle 5 36 #:color (gray 30) #:border-width 2) 335 | 336 | ; Tapered part of saber; 337 | ; It shouldn't be this hard to draw a trapezoid. 338 | (dc (lambda (dc dx dy) 339 | (define old-brush (send dc get-brush)) 340 | (define old-pen (send dc get-pen)) 341 | (send dc set-brush (new brush% [style 'solid] 342 | [color (gray 210)])) 343 | (send dc set-pen (new pen% [width 2])) 344 | (define path (new dc-path%)) 345 | (send path move-to 0 0) 346 | (send path line-to 38 5) 347 | (send path line-to 38 30) 348 | (send path line-to 0 35) 349 | (send path close) 350 | (send dc draw-path path dx dy) 351 | (send dc set-brush old-brush) 352 | (send dc set-pen old-pen)) 353 | 38 36) 354 | 355 | ; Emitter Rings 356 | (filled-rectangle 4 25 #:color (gray 30) #:border-width 1) 357 | (filled-rectangle 4 38 #:color (gray 210) #:border-width 1) 358 | (filled-rectangle 4 25 #:color (gray 30) #:border-width 1) 359 | (filled-rectangle 4 38 #:color (gray 210) #:border-width 1) 360 | (filled-rectangle 4 25 #:color (gray 30) #:border-width 1) 361 | (filled-rectangle 4 38 #:color (gray 210) #:border-width 1) 362 | (filled-rectangle 4 32 #:color "Peru" #:border-width 1)) 363 | 364 | ; Tapered Lines 365 | (inset (rotate (linewidth 2 (hline 29 4)) -0.0875) 98 7 0 0) 366 | (inset (linewidth 2 (hline 29 2)) 98 17 0 0) 367 | (inset (rotate (linewidth 2 (hline 29 4)) 0.0875) 98 23 0 0) 368 | 369 | ; Buttons 370 | (inset (filled-rectangle 12 5 #:color (gray 235) #:border-width 2) 28 9 0 0) 371 | (inset (filled-rectangle 12 5 #:color "Red" #:border-width 2) 55 9 0 0)))) 372 | 373 | (module+ main 374 | ; Test cases 375 | (lightsaber "DodgerBlue") 376 | (lightsaber "Lime" #:length 250) 377 | (lightsaber "Crimson" #:style 'vader) 378 | (lightsaber (make-color 144 67 202) #:style 'kylo) 379 | (lightsaber "DeepPink" #:style 'maul) 380 | 381 | ; Let's make a double-bladed lightsaber! 382 | (define single (lightsaber "Gold" #:length 200 #:style 'maul)) 383 | (define double (hc-append (flip-horizontal single) single)) 384 | double) 385 | 386 | #| 387 | Thoughts 388 | -------- 389 | pict was clearly designed for Slideshow and text with decorations; 390 | using it for drawing complex pictures seems a little like an abuse. 391 | Animating these in slideshow/play is awkward 392 | flip-horizontal and flip-vertical should be part of the pict library 393 | Sometimes gaps appear in the combiners 394 | Difficult to make non-rectangular shapes (e.g. a trapezoid) dc is overkill 395 | It would be nice to be able to set the alpha independently of the color (non-functional) 396 | |# --------------------------------------------------------------------------------