Skip to content

Instantly share code, notes, and snippets.

@usr-ein
Last active October 4, 2021 10:02
Show Gist options
  • Save usr-ein/47661f123f055fda3ed73e2e3bce20ad to your computer and use it in GitHub Desktop.
Save usr-ein/47661f123f055fda3ed73e2e3bce20ad to your computer and use it in GitHub Desktop.
L-Systems in pure PostScript, because I like pain. Written on 2020-01-26
%!PS
% Author: Samuel Prevost <[email protected]>
% To view the result, run ps2pdf file.gs output.pdf
% Ref
% https://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF
% https://www.math.ubc.ca/~cass/courses/ps.html
% https://ift2015h20code.files.wordpress.com/2020/01/ift2015h20-tp1-postscript.pdf
% Convention:
% Les procédures sont précédé de % entrée -> sortie (ou void si pas d'entrée/sortie)
% Pour les suites d'instructions un peu mystiques,
% un commentaire en dessous explique l'état de la pile après execution
% la première ligne de la procédure, si commentée, indique la pile d'entrée
% N.B. Il n'est pas possible de répondre à la question 7. Domaine vital des testudinés
% avec cette implementation puisqu'on ne connais pas le chemin tracé à l'avance, et donc
% impossible de mettre un %%BoundingBox
/VERBOSE false def
/RANDOM_COLOUR false def
/MAX_DEPTH 6 def
/LINEWIDTH 0.7 def
/DIST 3 def
/ANGLE 20 def
% Alphabet
/DRAW 70 def % F
/MOVE 102 def % f
/TURNL 43 def % +
/TURNR 45 def % -
/PUSH 91 def % [
/POP 93 def % ]
% Axiom
/START_TOKEN [ DRAW ] def
% Where to start on the page, one of:
% - startcenterbottom
% - startmiddle
% - starttopquarter
/STARTPOS (startcenterbottom) def
% ######### RULES ###########################
/RULES_TREE % Up to 10 iterations
<< DRAW [
[ DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW ]
[ DRAW PUSH TURNL DRAW POP DRAW ]
[ DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW ]
]
>>
def
/RULES_BUSH % Up to 8 iterations
<< DRAW [
[ DRAW DRAW TURNR PUSH TURNR DRAW TURNL DRAW TURNL DRAW POP TURNL PUSH TURNL DRAW TURNR DRAW TURNR DRAW POP ]
]
>>
def
/RULES_SIMPLE % Useful for debugging
<< DRAW [
[ DRAW TURNR DRAW ]
[ TURNL DRAW ]
]
>>
def
% Set your rule here
/RULES RULES_BUSH def
% ###########################################
% void -> void
/startcenterbottom {
306 0 moveto
90 rotate
} def
% void -> void
/startmiddle {
306 396 moveto
90 rotate
} def
% void -> void
/starttopquarter {
306 594 moveto
-90 rotate
} def
%############## TURTLE ACTIONS ##############
/draw {
DIST 0 rlineto
} def
/move {
DIST 0 rmoveto
} def
/turnL {
ANGLE rotate
} def
/turnR {
ANGLE neg rotate
} def
% Will count the depth as we progress in stacking graphic states
/PUSH_DEPTH 0 def
/push_state {
% ########################## ADVICES FROM SOMEONE WHO HAS DONE TOO MUCH POSTSCRIPT ###########################
% # From experiment, I've deduced that "graphic state" includes:
% # - The currentpoint which is to be considered w.r.t. the Coordinate Transformation Matrix (CTM)
% # - The Coordinate Transformation Matrix (CTM) which contains:
% # - translations of the origin (we don't do that in this script)
% # - scale of the origin (we don't do that in this script)
% # - rotation of the origin (we do that a lot)
% #
% # gsave saves the graphic state, but also "changes" the currentpoint coordinates
% # to match where we were but in device coordinates instead of in rotated coordinates.
% #
% # rlineto and rmoveto moves the currentpoint into the space but their
% # coordinate system is relative to the CTM AND the current point ofc
% #
% # When calling grestore, a few things happen:
% # - Any unstroke path disapear for eternity, so beware that.
% # - The currentpoint is reset to the old currentpoint IN THE SAVED CTM'S COORDINATE SYSTEM !!!
% # - The saved CTM is restored from the deads (any rotation that was in it is now effective)
% #
% # when calling newpath (to allow us to change colour and stuff), the currentpoint is unset,
% # that is, if you try to do a rlineto you'll be met with /nocurrentpoint exception. Therefore you should:
% # 1) put the currentpoint on the stack
% # 2) call newpath
% # 3) call moveto so that now you *have* a currentpoint which is the same as before the newpath.
% #
% # That's what we do in /pop_state
% #
% ############################## THAT'S ALL FOLKS ############################################################
gsave
/PUSH_DEPTH PUSH_DEPTH 1 add def
} def
/pop_state {
/PUSH_DEPTH PUSH_DEPTH 1 sub def
RANDOM_COLOUR {
% let's add some random colour to the mix !!!
3 { rand 10 mod 10 div } repeat setrgbcolor
}{
/DEPTH_RATIO PUSH_DEPTH MAX_DEPTH div def
% When DEPTH_RATIO=1 we want
% 0 0.75 0 (leaf colour)
% When DEPTH_RATIO=0 we want
% 0.5 0.25 0 (wood colour)
0.5 DEPTH_RATIO 2 div sub
0.25 DEPTH_RATIO 2 div add
0
setrgbcolor
} ifelse
LINEWIDTH setlinewidth
stroke % first stroke the path to avoid it disapearing
grestore % then restore the CTM and previous current point
currentpoint % get that point
newpath % create a new path, now the currentpoint has vanished...
moveto % OR HAS IT ?! move to it since it was on the stack all along !
} def
% Maps symbols to actions (cvx: literal to executable)
/ACTIONS
<<
DRAW /draw cvx
MOVE /move cvx
TURNR /turnR cvx
TURNL /turnL cvx
PUSH /push_state cvx
POP /pop_state cvx
>>
def
%############ FIN TURTLE ACTIONS #############
% Replaces a symbol by those from a matching rule in RULES
% sym -> [ sym1 sym2 sym2 ]
/replace_once {
% sym
dup RULES exch known {
% sym
RULES exch get
% rules
dup length rand
% rules l r
exch mod
% rules r%l
get
% rule
VERBOSE {
(rule is ) =
dup ==
} if
} {
[ exch ]
} ifelse
} def
% sym -> does action
/tell {
VERBOSE {
(drawing) = dup =
} if
ACTIONS exch get % gets the actions' executable
exec
} def
% sym n -> void
/gogo_power_rangers {
VERBOSE {(---) = pstack } if
dup 0 eq {
% sym n
pop tell
} {
% sym n
1 sub
% sym n-1
exch
% n-1 sym
replace_once
% n-1 [ sym1 sym2 .. ]
{
% n-1 symi
exch dup 3 1 roll
% n-1 symi n-1
gogo_power_rangers
% n-1
} forall
pop % Ce pop m'as pris 3 jours à debugger ;w;
% Explications du pop:
% Après que tout les appelles recursives ait été faits,
% il est nécessaire de pop le n-1 qu'on se trimbalait
% sinon le prochain dans la profondeur de l'arbre d'appel vera 0 au sommet de la pile
% et pensera que c'est la fin et va juste dessiner .__.
} ifelse
} def
42 srand
0 0 newpath moveto
STARTPOS cvx exec
%###################### TESTS ######################
%% Sequential drawing to verify the procedure replace_once
% /SEQ [ DRAW ] def
% 4 {
% /SEQ [
% SEQ {
% replace_once {} forall
% } forall
% ] def
% } repeat
% SEQ { tell } forall
%%% Tests to check if the turtle draws correctly
%% Python generated, should be perfect
% [
% DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNL DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNR DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNL DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNL DRAW
% PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNR DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNL DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW
% POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNR DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNR DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNL DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNR DRAW PUSH TURNL DRAW POP
% DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNL DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW PUSH TURNR DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW POP DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW
% ]
% { tell } forall
%###################################################
START_TOKEN
{
MAX_DEPTH gogo_power_rangers
} forall
stroke
%!PS
% Refs:
% Liste des prototypes de fonctions:
% https://www.math.ubc.ca/~cass/courses/ps.html
% Documentation du cours:
% https://ift2015h20code.files.wordpress.com/2020/01/ift2015h20-tp1-postscript.pdf
% (612,792) is the top right corner of the page
% (0,0) is the bottom left
%F = draw = 70
%f = move = 102
%+ = turnL = 43
%- = turnR = 45
%[ = push = 91
%] = pop = 93
% c.f. [(Ff+-[]) {} forall] {=} forall
% Liste des symboles traduits en entiers ASCII equiv
/DRAW 70 def
/MOVE 102 def
/TURNL 43 def
/TURNR 45 def
/PUSH 91 def
/POP 93 def
/LINEWIDTH 0.3 def
/MAX_ITERATION 7 def % For now stack overflows at >= 8 for the tree
/DELTA_DIST 1 def
/DELTA_ANGLE 45 def
/STARTING_ANGLE 90 def
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% TURTLE PART %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%void -> void
/initturtle {
LINEWIDTH setlinewidth
0 0 newpath moveto
} def
%void -> void
/drawturtle {
0 setgray
stroke
} def
% void -> void
/startcenterbottom {
306 0 moveto
} def
% void -> void
/startmiddle {
306 396 moveto
} def
% void -> void
/starttopquarter {
306 594 moveto
} def
% dist angle -> dist angle
/draw {
dup dup cos exch sin % dist angle cos sin
3 index mul % dist angle cos dist*sin
exch 3 index mul % dist angle dist*sin dist*cos
exch % dist angle dist*cos dist*sin
rlineto % dist angle
} def
% dist angle -> dist angle
/move {
dup dup cos exch sin % dist angle cos sin
3 index mul % dist angle cos dist*sin
exch 3 index mul % dist angle dist*sin dist*cos
exch % dist angle dist*cos dist*sin
rmoveto % dist angle
} def
% angle -> new_angle
/turnL {
DELTA_ANGLE add
} def
% angle -> new_angle
/turnR {
DELTA_ANGLE sub
} def
% dist angle -> [dist angle x y] dist angle (x y is currentpoint)
/push_state {
[ 2 index 2 index currentpoint ] 3 1 roll
} def
% [dist1 angle1 x y] dist2 angle2 -> dist1 angle1 (and move to x y)
/pop_state {
pop pop {} forall moveto
} def
%dist angle instructions_iterable -> dist_after_exec angle_after_exec
/interpret {
{
dup DRAW eq {
pop draw
}{
dup MOVE eq {
pop move
}{
dup TURNR eq {
pop turnR
}{
dup TURNL eq {
pop turnL
}{
dup PUSH eq {
pop push_state
}{
dup POP eq {
pop pop_state
}{
(====\nAh bah non\n====) =
(caractère) = =
(pas reconnus, et donc ignoré) =
} ifelse % switch doesn't exist in postscript
} ifelse % switch doesn't exist in postscript
} ifelse % switch doesn't exist in postscript
} ifelse % switch doesn't exist in postscript
} ifelse % switch doesn't exist in postscript
} ifelse % switch doesn't exist in postscript
} forall % iterates over all the letters in the string
} def
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% L-SYS PART %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% string -> [s,t,r,i,n,g] (as ints)
/stoa {
[ exch {} forall ]
} def
% array -> array
/revarray { % fait maison :)
%% TO READER:
%% a: input data array
%% r: tab that will contain the reverse
%% i: index from last to first in a
%% j: index from first to last in r
%% l: length of a (or r, since they're the same length)
%% v: the value retreived from a to be put in r
%% ==> : state of the stack after the executing the preceeding line
% ==> a
dup length array
% ==> a r
dup 3 -1 roll
% ==> r r a
dup length 1 sub -1 0 {
% ==> r r a i
1 index 1 index
% ==> r r a i a i
get
% ==> r r a i v
exch
% ==> r r a v i
2 index length 1 sub
% ==> r r a v i l-1
exch sub exch
% ==> r r a j v
3 -1 roll
% ==> r r j v a
4 1 roll
% ==> r a r j v
put
% ==> r a
exch dup
3 -1 roll
% ==> r r a
} for
pop pop
% ==> r
} def
% [[1 2 3 4] [5 6 7 8]] -> [ 1 2 3 4 5 6 7 8 ]
/flattenarray {
% ==> [[1 2 3 4] [5 6 7 8]]
[ exch
{
% ==> [1 2 3 4]
aload pop
% 1 2 3 4
} forall
% ==> 1 2 3 4 5 6 7 8
]
} def
% rules instructions_array -> rules applied_instructions
/rules {
%% r: rules array
%% a: instruction array
%% vi: i-th applied rule from left to right in a
%% vN: the new deduced rule
[ 3 1 roll
{
% ==> v1 ... vn r a
dup DRAW eq {
pop
% ==> v1 ... vn r
dup
dup length rand exch mod % gen random valid index
% ==> v1 ... vn r r
get % get that index in the rule array
% ==> v1 ... vn r vN
exch
% ==> v1 ... vn vN r
}{
% adds the current instruction to the output as a standalone array
[ exch ] exch
}
ifelse
} forall
]
% ==> [v1 ... vn r]
dup dup length 1 sub get
% ==> [v1 ... vn r] r
exch dup length 1 sub 0 exch getinterval
% ==> r [v1 ... vn]
} def
%% Rules
% Tree
[[ DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW ]
[ DRAW PUSH TURNL DRAW POP DRAW ]
[ DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW ]]
% Bush
% [ [
% DRAW DRAW TURNR PUSH TURNR DRAW TURNL DRAW TURNL DRAW POP TURNL PUSH TURNL DRAW TURNR DRAW TURNR DRAW POP
% ] ]
% Starting chain
[ DRAW ]
42 srand % seeds PRNG
MAX_ITERATION {
rules flattenarray
} repeat
exch pop % pops the rules off
initturtle
%% Where to start in the page
% startmiddle
startcenterbottom
% starttopquarter
% Interpretation
DELTA_DIST STARTING_ANGLE
3 -1 roll
interpret
drawturtle
%!PS
% Refs:
% Liste des prototypes de fonctions:
% https://www.math.ubc.ca/~cass/courses/ps.html
% Documentation du cours:
% https://ift2015h20code.files.wordpress.com/2020/01/ift2015h20-tp1-postscript.pdf
% (612,792) is the top right corner of the page
% (0,0) is the bottom left
%F = draw = 70
%f = move = 102
%+ = turnL = 43
%- = turnR = 45
%[ = push = 91
%] = pop = 93
% c.f. [(Ff+-[]) {} forall] {=} forall
% Liste des symboles traduits en entiers ASCII equiv
/DRAW 70 def
/MOVE 102 def
/TURNL 43 def
/TURNR 45 def
/PUSH 91 def
/POP 93 def
/LINEWIDTH 0.3 def
/MAX_ITERATION 5 def % For now stack overflows at >= 8
/DELTA_DIST 4 def
/DELTA_ANGLE 45 def
/STARTING_ANGLE 90 def
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% TURTLE PART %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%void -> void
/initturtle {
LINEWIDTH setlinewidth
0 0 newpath moveto
} def
%void -> void
/drawturtle {
0 setgray
stroke
} def
% void -> void
/startcenterbottom {
306 0 moveto
} def
% void -> void
/startmiddle {
306 396 moveto
} def
% void -> void
/starttopquarter {
306 594 moveto
} def
% dist angle -> dist angle
/draw {
dup dup cos exch sin % dist angle cos sin
3 index mul % dist angle cos dist*sin
exch 3 index mul % dist angle dist*sin dist*cos
exch % dist angle dist*cos dist*sin
rlineto % dist angle
} def
% dist angle -> dist angle
/move {
dup dup cos exch sin % dist angle cos sin
3 index mul % dist angle cos dist*sin
exch 3 index mul % dist angle dist*sin dist*cos
exch % dist angle dist*cos dist*sin
rmoveto % dist angle
} def
% angle -> new_angle
/turnL {
DELTA_ANGLE add
} def
% angle -> new_angle
/turnR {
DELTA_ANGLE sub
} def
% dist angle -> [dist angle x y] dist angle (x y is currentpoint)
/push_state {
[ 2 index 2 index currentpoint ] 3 1 roll
} def
% [dist1 angle1 x y] dist2 angle2 -> dist1 angle1 (and move to x y)
/pop_state {
pop pop {} forall moveto
} def
%dist angle instructions_iterable -> dist_after_exec angle_after_exec
/interpret {
{
dup DRAW eq {
pop draw
}{
dup MOVE eq {
pop move
}{
dup TURNR eq {
pop turnR
}{
dup TURNL eq {
pop turnL
}{
dup PUSH eq {
pop push_state
}{
dup POP eq {
pop pop_state
}{
(====\nAh bah non\n====) =
(charactère) = =
(pas reconnus, et donc ignoré) =
} ifelse % switch doesn't exist in postscript
} ifelse % switch doesn't exist in postscript
} ifelse % switch doesn't exist in postscript
} ifelse % switch doesn't exist in postscript
} ifelse % switch doesn't exist in postscript
} ifelse % switch doesn't exist in postscript
} forall % iterates over all the letters in the string
} def
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% L-SYS PART %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% string -> [s,t,r,i,n,g] (as ints)
/stoa {
[ exch {} forall ]
} def
% array -> array
/revarray { % fait maison :)
%% TO READER:
%% a: input data array
%% r: tab that will contain the reverse
%% i: index from last to first in a
%% j: index from first to last in r
%% l: length of a (or r, since they're the same length)
%% v: the value retreived from a to be put in r
%% ==> : state of the stack after the executing the preceeding line
% ==> a
dup length array
% ==> a r
dup 3 -1 roll
% ==> r r a
dup length 1 sub -1 0 {
% ==> r r a i
1 index 1 index
% ==> r r a i a i
get
% ==> r r a i v
exch
% ==> r r a v i
2 index length 1 sub
% ==> r r a v i l-1
exch sub exch
% ==> r r a j v
3 -1 roll
% ==> r r j v a
4 1 roll
% ==> r a r j v
put
% ==> r a
exch dup
3 -1 roll
% ==> r r a
} for
pop pop
% ==> r
} def
% [[1 2 3 4] [5 6 7 8]] -> [ 1 2 3 4 5 6 7 8 ]
/flattenarray {
% ==> [[1 2 3 4] [5 6 7 8]]
[ exch
{
% ==> [1 2 3 4]
aload pop
% 1 2 3 4
} forall
% ==> 1 2 3 4 5 6 7 8
]
} def
% rules instructions_array -> rules applied_instructions
/rules {
%% r: rules array
%% a: instruction array
%% vi: i-th applied rule from left to right in a
%% vN: the new deduced rule
[ 3 1 roll
{
% ==> v1 ... vn r a
dup DRAW eq {
pop
% ==> v1 ... vn r
dup
dup length rand exch mod % gen random valid index
% ==> v1 ... vn r r
get % get that index in the rule array
% ==> v1 ... vn r vN
exch
% ==> v1 ... vn vN r
}{
% adds the current instruction to the output as a standalone array
[ exch ] exch
}
ifelse
} forall
]
% ==> [v1 ... vn r]
dup dup length 1 sub get
% ==> [v1 ... vn r] r
exch dup length 1 sub 0 exch getinterval
% ==> r [v1 ... vn]
} def
%% Rules
% Tree
% [[ DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW ]
% [ DRAW PUSH TURNL DRAW POP DRAW ]
% [ DRAW PUSH TURNL DRAW POP DRAW PUSH TURNR DRAW POP DRAW ]]
% Bush
[ [
DRAW DRAW TURNR PUSH TURNR DRAW TURNL DRAW TURNL DRAW POP TURNL PUSH TURNL DRAW TURNR DRAW TURNR DRAW POP
] ]
% Starting chain
[ DRAW ]
42 srand % seeds PRNG
MAX_ITERATION {
rules flattenarray
} repeat
exch pop % pops the rules off
initturtle
startmiddle
% startcenterbottom
% starttopquarter
% Interpretation
DELTA_DIST STARTING_ANGLE
3 -1 roll
interpret
drawturtle
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment