chiark / gitweb /
objs etc., nyt
[trains.git] / layout / layout
1 #!/usr/bin/perl -w
2
3 use POSIX;
4
5 # Data structures:
6 #  $ctx->{CmdLog}= undef                  } not in defobj
7 #  $ctx->{CmdLog}[]= [ command args ]     } in defobj
8 #  $ctx->{LocsMade}[]= $id
9 #  $ctx->{Loc}{$id}{X}
10 #  $ctx->{Loc}{$id}{Y}
11 #  $ctx->{Loc}{$id}{A}  may be undef
12 #  $ctx->{Trans}{X0}                  } transformation
13 #  $ctx->{Trans}{Y0}                  }  matrix
14 #  $ctx->{Trans}{XY}                  }
15 #  $ctx->{Trans}{YX}                  }
16 #  $ctx->{Trans}{XX}                  }
17 #  $ctx->{Trans}{YY}                  }
18 #  $ctx->{Trans}{AA}                  }
19 #  $ctx->{Trans}{AS}                  }
20 #
21 #  $objs{$id}{CmdLog}
22 #  $objs{$id}{Loc}
23
24 #$debug=1;
25 open DEBUG, ($debug ? ">&2" : ">/dev/null") or die $!;
26
27 if ($debug) {
28     select(DEBUG); $|=1;
29     select(STDOUT); $|=1;
30 }
31
32 sub canf ($$) {
33     my ($converter,$defaulter)=@_;
34     my ($spec,$v);
35     return &$defaulter unless @al;
36     $spec= shift @al;
37     $v= &$converter($spec);
38     dv('canf ','$spec',$spec, '$v',$v);
39     return $v;
40 }
41 sub can ($) { my ($c)=@_; canf($c, sub { die "too few args"; }); }
42 sub cano ($$) { my ($c,$def)=@_; canf($c, sub { return $def }); }
43
44 $pi= atan2(0,-1);
45
46 %units_len= qw(- mm  mm 1  cm 10  m 1000);
47 %units_ang= qw(- d   r 1); $units_ang{'d'}= 2*$pi / 360;
48
49 sub cva_len ($) { my ($sp)=@_; cva_units($sp,\%units_len); }
50 sub cva_ang ($) { my ($sp)=@_; cva_units($sp,\%units_ang); }
51 sub cva_absang ($) { input_absang(cva_ang($_[0])) }
52 sub cva_units ($$) {
53     my ($sp,$ua)=@_;
54     my ($n,$u,$r);
55     $sp =~ m/^([-0-9eE.]*[0-9.])([A-Za-z]*)$/
56         or die "lexically invalid quantity";
57     ($n,$u)= ($1,$2);
58     $u=$ua->{'-'} unless length $u;
59     defined $ua->{$u} or die "unknown unit $u";
60     $r= $n * $ua->{$u};
61     print DEBUG "cva_units($sp,)=$r ($n $u $ua->{$u})\n";
62     return $r;
63 }
64 sub cva_idstr ($) {
65     my ($sp)=@_;
66     die "invalid id" unless $sp =~ m/^[a-z][_0-9A-Za-z]*$/;
67     return $&;
68 }
69 sub cva_idex ($) {
70     my ($sp,$id)=@_;
71     my ($r,$d,$k);
72     $id=cva_idstr($sp);
73     die "unknown $id" unless defined $ctx->{Loc}{$id};
74     $r= $ctx->{Loc}{$id};
75     $d= "idex $id";
76     foreach $k (sort keys %$r) { $d .= " $k=$r->{$k}"; }
77     printf DEBUG "%s\n", $d;
78     return $r;
79 }
80 sub cva_idnew ($) {
81     my ($sp,$id)=@_;
82     $id=cva_idstr($sp);
83     die "duplicate $id" if exists $ctx->{Loc}{$id};
84     exists $ctx->{Loc}{$id}{X};
85     push @{ $ctx->{LocsMade} }, $id;
86     return $ctx->{Loc}{$id};
87 }
88 sub cva_cmd ($) { return cva_idstr($_); }
89 sub cva__enum ($$) {
90     my ($sp,$el)=@_;
91     return $sp if grep { $_ eq $sp } @$el;
92     die "invalid option (permitted: @$el)";
93 }
94 sub cvam_enum { my (@e) = @_; return sub { cva__enum($_[0],\@e); }; }
95
96 sub cmd_abs {
97     my ($x,$y);
98     $nl= can(\&cva_idnew);
99     $x= can(\&cva_len);
100     $y= can(\&cva_len);
101     ($nl->{X}, $nl->{Y})= input_abscoords($x,$y);
102     $nl->{A}= cano(\&cva_absang, undef);
103 }
104
105 sub cmd_rel {
106     $from= can(\&cva_idex);
107     $to= can(\&cva_idnew);
108     $len= can(\&cva_len);
109     $right= can(\&cva_len);
110     $turn= cano(\&cva_ang, 0);
111     $to->{X}= $from->{X} + $len * cos($from->{A}) + $right * sin($from->{A});
112     $to->{Y}= $from->{Y} + $len * sin($from->{A}) - $right * cos($from->{A});
113     $to->{A}= $from->{A} + $turn;
114 }
115
116 sub evreff ($) {
117     my ($pfx) = @_;
118     $pfx . ($pfx =~ m/\}$|\]$/ ? '' : '->');
119 }
120 sub evr ($) {
121     my ($v) = @_;
122     return $v if $v !~ m/\W/ && $v =~ m/[A-Z]/ && $v =~ m/^[a-z_]/i;
123     return $v if $v eq ($v+0.0);
124     $v =~ s/[\\\']/\\$&/g;
125     return "'$v'";
126 }
127 sub dv1 ($$$);
128 sub dv1_kind ($$$$$$$) {
129     my ($pfx,$expr,$ref,$ref_exp,$ixfmt,$ixesfn,$ixmapfn) = @_;
130     my ($ix,$any);
131     return 0 if $ref ne $ref_exp;
132     $any=0;
133     foreach $ix (&$ixesfn) {
134         $any=1;
135         my ($v)= &$ixmapfn($ix);
136 #print STDERR "dv1_kind($pfx,$expr,$ref,$ref_exp,$ixmapfn) ix=$ix v=$v\n";
137         dv1($pfx,$expr.sprintf($ixfmt,evr($ix)),$v);
138     }
139     if (!$any) {
140         printf DEBUG "%s%s= $ixfmt\n", $pfx, $expr, ' ';
141     }
142     1;
143 }    
144 sub dv1 ($$$) {
145     return ;0 unless $debug;
146     my ($pfx,$expr,$v) = @_;
147     $ref= ref $v;
148 #print STDERR "dv1 >$pfx|$ref<\n";
149     if (!$ref) {
150         printf DEBUG "%s%s= %s\n", $pfx,$expr, evr($v);
151         return;
152     } elsif ($ref eq 'SCALAR') {
153         dv1($pfx, ($expr =~ m/^\$/ ? "\$$expr" : '${'.$expr.'}'), $$v);
154         return;
155     }
156     $expr.='->' unless $expr =~ m/\]$|\}$/;
157     return if dv1_kind($pfx,$expr,$ref,'ARRAY','[%s]',
158                        sub { ($[ .. $#$v) },
159                        sub { $v->[$_[0]] });
160     return if dv1_kind($pfx,$expr,$ref,'HASH','{%s}',
161                        sub { sort keys %$v },
162                        sub { $v->{$_[0]} });
163     printf DEBUG "%s%s is %s\n", $pfx, $expr, $ref;
164 }
165     
166 sub dv {
167     my ($pfx,@l) = @_;
168     my ($expr,$v,$ref);
169     while (@l) {
170         ($expr,$v,@l)=@l;
171         dv1($pfx,$expr,$v);
172     }
173 }                   
174
175 sub loc_lin_comb ($$$) {
176     my ($a,$b,$p) = @_;
177     my ($q,$r) = 1.0-$p;
178     map { $r->{$_} = $q * $a->{$_} + $p * $b->{$_} } qw(X Y A);
179 #    dv("loc_lin_comb ",'$a',$a,'$b',$b,'$p',$p,'$r',$r);
180     return $r;
181 }
182
183 $psu_ulen= 4.5;
184 $psu_edgelw= 0.5;
185 $psu_ticklw= 0.1;
186 $psu_ticksperu= 3;
187 $psu_ticklen= 3.0;
188 $psu_allwidth= 37.0/2;
189 $psu_gauge= 9;
190 $psu_sleeperlen= 17;
191 $psu_sleeperlw= 15;
192 $psu_raillw= 1.0;
193
194 sub o ($) {
195     # fixme optional marking
196     print "$_[0]" or die $!;
197 }
198
199 sub o_path_begin () {
200     o("      newpath\n");
201     $o_path_verb= 'moveto';
202 }
203 sub o_path_point ($) {
204     my ($pt)=@_;
205     o("        $pt $o_path_verb\n");
206     $o_path_verb= 'lineto';
207 }
208 sub o_path_stroke ($) {
209     my ($width)=@_;
210     o("        $width setlinewidth stroke\n");
211 }    
212
213 sub o_line ($$$) {
214     my ($a,$b,$width)=@_;
215     o_path_begin();
216     o_path_point($a);
217     o_path_point($b);
218     o_path_stroke($width);
219 }
220
221 sub psu_coords ($$$) {
222     my ($ends,$inunit,$across)=@_;
223     # $ends->[0]{X} etc.; $inunit 0 to 1 (but go to 1.5);
224     # $across in mm, +ve to right.
225     my (%ea_zo);
226     $ea_zo{X}=$ea_zo{Y}=0;
227     foreach $zo (qw(0 1)) {
228         $prop= $zo ? $inunit : (1.0 - $inunit);
229         $ea_zo{X} += $prop * ($ends->[$zo]{X} - $across * sin($ends->[0]{A}));
230         $ea_zo{Y} += $prop * ($ends->[$zo]{Y} + $across * cos($ends->[0]{A}));
231     }
232 #    dv("psu_coords ", '$ends',$ends, '$inunit',$inunit, '$across',$across,
233 #       '\\%ea_zo', \%ea_zo);
234     return $ea_zo{X}." ".$ea_zo{Y};
235 }
236
237 sub parametric_segment ($$$$$) {
238     my ($endstatuses,$p0,$p1,$lenperp,$calcfn) = @_;
239     # makes $p (global) go from $p0 to $p1  ($p1>$p0)
240     # $ends is II, SI, IS, SS (I=actual lineobj end, S=in mid of lineobj)
241     # $lenperp is the length of one unit p, ie the curve
242     # must have a uniform `density' in parameter space
243     # $calcfn is invoked with $p set and should return a loc
244     # (ie, ref to X =>, Y =>, A =>).
245     my ($pa,$pb,@ends,$side,$ppu,$e,$v,$tick);
246     $ppu= $psu_ulen/$lenperp;
247     my ($railctr)=($psu_gauge + $psu_raillw)*0.5;
248     my ($tickend)=($psu_allwidth - $psu_ticklen);
249     my ($tickpitch)=($psu_ulen / $psu_ticksperu);
250     my ($sleeperctr)=($psu_ulen*0.5);
251     my ($sleeperend)=($psu_sleeperlen*0.5);
252 print DEBUG "ps $p0 $p1 $lenperp ($ppu)\n";
253     for ($pa= $p0; $pa<$p1; $pa=$pb) {
254         $pb= $pa + $ppu;
255         $p= $pa; $ends[0]= @ends ? $ends[1] : &$calcfn;
256         $p= $pb; $ends[1]= &$calcfn;
257 #print DEBUG "pa $pa $ends[0]{X} $ends[0]{Y} $ends[0]{A}\n";
258 #print DEBUG "pb $pb $ends[1]{X} $ends[1]{Y} $ends[1]{A}\n";
259         $e= $pb<=$p1 ? 1.0 : ($p1-$pa)/$ppu;
260         o("    gsave\n");
261         o_path_begin();
262         o_path_point(psu_coords(\@ends,0,-$psu_allwidth));
263         o_path_point(psu_coords(\@ends,0,$psu_allwidth));
264         o_path_point(psu_coords(\@ends,$e,$psu_allwidth));
265         o_path_point(psu_coords(\@ends,$e,-$psu_allwidth));
266         o("        closepath clip\n");
267         foreach $side qw(-1 1) {
268             o_line(psu_coords(\@ends,0,$side*$psu_allwidth),
269                    psu_coords(\@ends,1.5,$side*$psu_allwidth),
270                    $psu_edgelw);
271             o_line(psu_coords(\@ends,0,$side*$railctr),
272                    psu_coords(\@ends,1.5,$side*$railctr),
273                    $psu_raillw);
274             for ($tick=0; $tick<1.5; $tick+=$tickpitch/$psu_ulen) {
275                 o_line(psu_coords(\@ends,$tick,$side*$psu_allwidth),
276                        psu_coords(\@ends,$tick,$side*$tickend),
277                        $psu_ticklw);
278             }
279         }
280         o_line(psu_coords(\@ends,$sleeperctr,-$sleeperend),
281                psu_coords(\@ends,$sleeperctr,+$sleeperend),
282                $psu_sleeperlw);
283         o("      grestore\n");
284     }
285 }
286
287 sub cmd_extend {
288     my ($from,$to,$radius,$ctr,$beta,$ang,$how,$signum);
289     $from= can(\&cva_idex);
290     $to= can(\&cva_idnew);
291     printf DEBUG "from $from->{X} $from->{Y} $from->{A}\n";
292     die "no ang" unless defined $from->{A};
293     $how= can(cvam_enum(qw(len upto ang uptoang parallel)));
294     if ($how eq 'len') { $len= can(\&cva_len); }
295     elsif ($how =~ m/ang$/) { $ang= can(\&cva_ang); }
296     elsif ($how eq 'parallel' || $how eq 'upto') { $upto= can(\&cva_idex); }
297     $radius= cano(\&cva_len, 'Inf'); # +ve is right hand bend
298     if ($radius eq 'Inf') {
299         print DEBUG "extend inf $len\n";
300         if ($how eq 'ang') { die "len of straight spec by angle"; }
301         if ($how eq 'upto') {
302             $len= ($upto->{X} - $from->{X}) * cos($from->{A})
303                 + ($upto->{Y} - $from->{Y}) * sin($from->{A});
304         }
305         printf DEBUG "len $len\n";
306         $to->{X}= $from->{X} + $len * cos($from->{A});
307         $to->{Y}= $from->{Y} + $len * sin($from->{A});
308         $to->{A}= $from->{A};
309         parametric_segment(II, 0.0, 1.0, $len, sub {
310             loc_lin_comb($from, $to, $p);
311         });
312     } else {
313         print DEBUG "radius >$radius<\n";
314         $signum= $radius / abs($radius);
315         $ctr->{X}= $from->{X} + $radius * sin($from->{A});
316         $ctr->{Y}= $from->{Y} - $radius * cos($from->{A});
317         if ($how eq 'upto') {
318             $beta= atan2(-$signum * ($upto->{X} - $ctr->{X}),
319                          $signum * ($upto->{Y} - $ctr->{Y}));
320             $beta_interval= 1.0;
321         } elsif ($how eq 'parallel') {
322             $beta= $upto->{A};
323             $beta_interval= 1.0;
324         } elsif ($how eq 'uptoang') {
325             $beta= input_absang($ang);
326             $beta_interval= 2.0;
327         } elsif ($how eq 'len') {
328             $beta= $from->{A} - $signum * $len / abs($radius);
329             $beta_interval= 2.0;
330         } else {
331             $beta= $from->{A} - $signum * $ang;
332             $beta_interval= 2.0;
333         }
334     printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n";
335         $beta += $signum * 4.0 * $pi;
336         for (;;) {
337             $delta= $beta - $from->{A};
338             last if $signum * $delta <= 0;
339             $beta -= $signum * $beta_interval * $pi;
340         }       
341     printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n";
342         $to->{A}= $beta;
343         $to->{X}= $ctr->{X} - $radius * sin($beta);
344         $to->{Y}= $ctr->{Y} + $radius * cos($beta);
345         parametric_segment(II, 0.0, 1.0, abs($radius*$delta), sub {
346             my ($beta) = $from->{A} + $delta * $p;
347             return { X => $ctr->{X} - $radius * sin($beta),
348                      Y => $ctr->{Y} + $radius * cos($beta),
349                      A => $beta }
350         });
351     }
352     printf DEBUG "to $to->{X} $to->{Y} $to->{A}\n";
353 }
354
355 sub cmd__do {
356     my ($id, $cmd, $loc);
357     $ctx->{LocsMade}= [ ];
358     $cmd= can(\&cva_cmd);
359     &{ "cmd_$cmd" };
360     die "too many args" if @al;
361     foreach $id (@{ $ctx->{LocsMade} }) {
362         $loc= $ctx->{Locs}{$id};
363         o("%  point $id $loc->{X} $loc->{Y} ".ang2deg($loc->{A})."\n");
364     }
365 }
366
367 sub cmd__one {
368     cmd__do();
369 }
370
371 sub input_absang ($) {
372     return $_ * $ctx->{Trans}{AA} + $ctx->{Trans}{A0};
373 }
374 sub input_abscoords ($$) {
375     my ($in,$out, $i);
376     ($in->{X}, $in->{Y})= @_;
377     foreach $o (qw(X Y)) {
378         $out->{$o}= $ctx->{Trans}{$o.0};
379         foreach $i (qw(X Y)) {
380             $out->{$o} += $ctx->{Trans}{"$i$o"} * $in->{$i};
381         }
382     }
383     return ($out->{X}, $out->{Y});
384 }
385
386 sub newctx () {
387     $ctx= { Trans => { X0 => 0.0, Y0 => 0.0,
388                        XY => 0.0, YX => 0.0,
389                        A0 => 0.0, AA => 1.0,
390                        XX => 1.0, YY => 1.0; } }
391 }
392
393 sub cmd_defobj {
394     $defobj_id= can(\&cva_idstr);
395     die "nested defobj" if $defobj_save;
396     die "repeated defobj" if exists $objs{$defobj_id};
397     $defobj_save= $ctx;
398     newctx();
399     $ctx= { CmdLog => [ ] }
400 }
401
402 sub cmd_enddefobj {
403     die "unmatched enddefobj" unless $defobj_save;
404     my ($bit);
405     foreach $bit (qw(CmdLog Loc)) {
406         $objs{$defobj_id}{$bit}= $ctx->{$bit};
407     }
408     $ctx= $defobj_save;
409     $defobj_save= undef;
410 }
411
412 sub cmd_obj { cmd__obj(1); }
413 sub cmd_objflip { cmd__obj(-1); }
414 sub cmd__obj ($) {
415     my ($flipsignum)=@_;
416     my ($obj_id, $ctx_save, $pfx);
417     $obj_id= can(\&cva_idstr);
418     $actual= can(\&cva_idex);
419     $formal= can(\&cva_idstr);
420     $obj= $objs{$obj_id};
421     die "unknown obj $obj_id" unless $obj;
422     $ctx_save= $ctx;
423     newctx();
424     o("%  obj $obj_id\n");
425     $ctx->{Trans}{AA}= $flipsignum;
426     $ctx->{Trans}{A0}= $formal->{A} - $actual->{A}/$flipsignum;
427     $ctx->{Trans}{XX}= cos($ctx->{Trans}{A0});
428     $ctx->{Trans}{YY}= $flipsignum * cos($ctx->{Trans}{A0});
429     $ctx->{Trans}{XY}= sin($ctx->{Trans}{A0});
430     $ctx->{Trans}{YX}= -$flipsignum * sin($ctx->{Trans}{A0});
431     ($xformcv,$yformcv)= input_abscoords($formal->{X}, $formal->{Y});
432     $ctx->{Trans}{X0}= $actual->{X} - $xformcv;
433     $ctx->{Trans}{Y0}= $actual->{Y} - $yformcv;
434     {
435         local (@al);
436         foreach $c ($obj->{CmdLog}) {
437             @al= @$c;
438             cmd__one();
439         }
440     }
441     $pfx= cano(\&cva_idstr,'');
442     if (length $pfx) {
443         foreach $id (keys $ctx->{Loc}) {
444             $newid= $pfx.$id;
445             next if exists $ctx_save->{Loc}{$newid};
446             $pt= $ctx->{Loc}{$id};
447             $newpt= { A => input_absang($pt->{A}) };
448             ($newpt->{X}, $newpt->{Y})= input_abscoords($pt->{X}, $pt->{Y});
449             $ctx_save->{Loc}{$newid}= $newpt;
450         }
451     }
452     $ctx= $ctx_save;
453 }
454
455 $ptscale= 72/25.4 / 5.0;
456
457 o("%!\n".
458   "  $ptscale $ptscale scale\n");
459
460 newctx();
461     
462 while (<>) {
463     next if m/^\s*\#/;
464     chomp; s/^\s+//; s/\s+$//;
465     @al= split /\s+/, $_;
466     next unless @al;
467     print DEBUG "=== @al\n";
468     push @{ $ctx->{CmdLog} }, [ @al ] if exists $ctx->{CmdLog};
469     cmd__one();
470 }
471 o("  showpage\n");