]> git.tdb.fi Git - r2c2.git/commitdiff
Initial revision
authorMikko Rasa <tdb@tdb.fi>
Tue, 10 Jul 2007 19:20:20 +0000 (19:20 +0000)
committerMikko Rasa <tdb@tdb.fi>
Tue, 10 Jul 2007 19:20:20 +0000 (19:20 +0000)
60 files changed:
Build [new file with mode: 0644]
dejavu-12.font [new file with mode: 0644]
dejavu-12.png [new file with mode: 0644]
dejavu-20.font [new file with mode: 0644]
dejavu-20.png [new file with mode: 0644]
digitalreadout-16.font [new file with mode: 0644]
digitalreadout-16.png [new file with mode: 0644]
engineer.res [new file with mode: 0644]
gui.blend [new file with mode: 0644]
gui.png [new file with mode: 0644]
source/3d/layout.cpp [new file with mode: 0644]
source/3d/layout.h [new file with mode: 0644]
source/3d/misc.h [new file with mode: 0644]
source/3d/track.cpp [new file with mode: 0644]
source/3d/track.h [new file with mode: 0644]
source/designer/designer.cpp [new file with mode: 0644]
source/designer/designer.h [new file with mode: 0644]
source/designer/input.cpp [new file with mode: 0644]
source/designer/input.h [new file with mode: 0644]
source/designer/manipulator.cpp [new file with mode: 0644]
source/designer/manipulator.h [new file with mode: 0644]
source/designer/measure.cpp [new file with mode: 0644]
source/designer/measure.h [new file with mode: 0644]
source/designer/selection.cpp [new file with mode: 0644]
source/designer/selection.h [new file with mode: 0644]
source/engineer/engineer.cpp [new file with mode: 0644]
source/engineer/engineer.h [new file with mode: 0644]
source/engineer/guicomponent.h [new file with mode: 0644]
source/engineer/guiresources.h [new file with mode: 0644]
source/engineer/mainpanel.cpp [new file with mode: 0644]
source/engineer/mainpanel.h [new file with mode: 0644]
source/engineer/trainpanel.cpp [new file with mode: 0644]
source/engineer/trainpanel.h [new file with mode: 0644]
source/libmarklin/catalogue.cpp [new file with mode: 0644]
source/libmarklin/catalogue.h [new file with mode: 0644]
source/libmarklin/command.h [new file with mode: 0644]
source/libmarklin/constants.h [new file with mode: 0644]
source/libmarklin/control.cpp [new file with mode: 0644]
source/libmarklin/control.h [new file with mode: 0644]
source/libmarklin/geometry.h [new file with mode: 0644]
source/libmarklin/layout.cpp [new file with mode: 0644]
source/libmarklin/layout.h [new file with mode: 0644]
source/libmarklin/locomotive.cpp [new file with mode: 0644]
source/libmarklin/locomotive.h [new file with mode: 0644]
source/libmarklin/route.cpp [new file with mode: 0644]
source/libmarklin/route.h [new file with mode: 0644]
source/libmarklin/section.cpp [new file with mode: 0644]
source/libmarklin/section.h [new file with mode: 0644]
source/libmarklin/sensor.cpp [new file with mode: 0644]
source/libmarklin/sensor.h [new file with mode: 0644]
source/libmarklin/track.cpp [new file with mode: 0644]
source/libmarklin/track.h [new file with mode: 0644]
source/libmarklin/trafficmanager.cpp [new file with mode: 0644]
source/libmarklin/trafficmanager.h [new file with mode: 0644]
source/libmarklin/train.cpp [new file with mode: 0644]
source/libmarklin/train.h [new file with mode: 0644]
source/libmarklin/turnout.cpp [new file with mode: 0644]
source/libmarklin/turnout.h [new file with mode: 0644]
source/shoppinglist/main.cpp [new file with mode: 0644]
tracks.dat [new file with mode: 0644]

diff --git a/Build b/Build
new file mode 100644 (file)
index 0000000..df11fe0
--- /dev/null
+++ b/Build
@@ -0,0 +1,67 @@
+package "märklin"
+{
+       require "mspcore";
+       require "sigc++-2.0";
+
+       program "shoppinglist"
+       {
+               source "source/shoppinglist";
+               require "mspparser";
+       };
+
+       library "marklin"
+       {
+               source "source/libmarklin";
+               require "mspparser";
+       };
+
+       library "marklin3d"
+       {
+               source "source/3d";
+               require "mspgl";
+               build_info
+               {
+                       incpath "source";
+                       library "marklin";
+                       libpath ".";
+               };
+       };
+
+       /*program "newconsole"
+       {
+               source "source/newconsole";
+               build_info
+               {
+                       library "marklincontrol";
+                       incpath "source";
+                       libpath ".";
+               };
+       };*/
+
+       program "designer"
+       {
+               source "source/designer";
+               require "mspstrings";
+               require "sdl";
+               build_info
+               {
+                       incpath "source";
+                       library "marklin3d";
+                       libpath ".";
+               };
+       };
+
+       program "engineer"
+       {
+               source "source/engineer";
+               require "mspstrings";
+               require "mspgltk";
+               require "sdl";
+               build_info
+               {
+                       incpath "source";
+                       library "marklin3d";
+                       libpath ".";
+               };
+       };
+};
diff --git a/dejavu-12.font b/dejavu-12.font
new file mode 100644 (file)
index 0000000..8d7e902
--- /dev/null
@@ -0,0 +1,1341 @@
+texture "dejavu-12.png";
+default_size 12;
+ascent 1.167;
+descent 0.083;
+glyph 32
+{
+       texcoords 0.035156 0.945312 0.035156 0.945312;
+       size 0.000 0.000;
+       offset 0.000 0.000;
+       advance 0.333;
+};
+glyph 33
+{
+       texcoords 0.101562 0.945312 0.105469 0.980469;
+       size 0.083 0.750;
+       offset 0.167 0.000;
+       advance 0.417;
+};
+glyph 34
+{
+       texcoords 0.167969 0.968750 0.179688 0.980469;
+       size 0.250 0.250;
+       offset 0.083 0.500;
+       advance 0.417;
+};
+glyph 35
+{
+       texcoords 0.230469 0.945312 0.261719 0.976562;
+       size 0.667 0.667;
+       offset 0.083 0.000;
+       advance 0.833;
+};
+glyph 36
+{
+       texcoords 0.304688 0.937500 0.328125 0.980469;
+       size 0.500 0.917;
+       offset 0.167 -0.167;
+       advance 0.667;
+};
+glyph 37
+{
+       texcoords 0.367188 0.945312 0.406250 0.980469;
+       size 0.833 0.750;
+       offset 0.000 0.000;
+       advance 0.917;
+};
+glyph 38
+{
+       texcoords 0.437500 0.945312 0.472656 0.980469;
+       size 0.750 0.750;
+       offset 0.083 0.000;
+       advance 0.833;
+};
+glyph 39
+{
+       texcoords 0.523438 0.968750 0.527344 0.980469;
+       size 0.083 0.250;
+       offset 0.083 0.500;
+       advance 0.250;
+};
+glyph 40
+{
+       texcoords 0.589844 0.941406 0.601562 0.984375;
+       size 0.250 0.917;
+       offset 0.083 -0.083;
+       advance 0.417;
+};
+glyph 41
+{
+       texcoords 0.660156 0.941406 0.671875 0.984375;
+       size 0.250 0.917;
+       offset 0.083 -0.083;
+       advance 0.417;
+};
+glyph 42
+{
+       texcoords 0.726562 0.957031 0.746094 0.980469;
+       size 0.417 0.500;
+       offset 0.083 0.250;
+       advance 0.500;
+};
+glyph 43
+{
+       texcoords 0.792969 0.945312 0.820312 0.972656;
+       size 0.583 0.583;
+       offset 0.083 0.000;
+       advance 0.833;
+};
+glyph 44
+{
+       texcoords 0.875000 0.941406 0.878906 0.953125;
+       size 0.083 0.250;
+       offset 0.083 -0.083;
+       advance 0.333;
+};
+glyph 45
+{
+       texcoords 0.941406 0.957031 0.953125 0.960938;
+       size 0.250 0.083;
+       offset 0.083 0.250;
+       advance 0.333;
+};
+glyph 46
+{
+       texcoords 0.031250 0.875000 0.035156 0.882812;
+       size 0.083 0.167;
+       offset 0.083 0.000;
+       advance 0.333;
+};
+glyph 47
+{
+       texcoords 0.097656 0.871094 0.113281 0.910156;
+       size 0.333 0.833;
+       offset 0.000 -0.083;
+       advance 0.333;
+};
+glyph 48
+{
+       texcoords 0.164062 0.875000 0.187500 0.910156;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 49
+{
+       texcoords 0.234375 0.875000 0.253906 0.910156;
+       size 0.417 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 50
+{
+       texcoords 0.300781 0.875000 0.328125 0.910156;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 51
+{
+       texcoords 0.375000 0.875000 0.398438 0.910156;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 52
+{
+       texcoords 0.445312 0.875000 0.468750 0.910156;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 53
+{
+       texcoords 0.515625 0.875000 0.539062 0.910156;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 54
+{
+       texcoords 0.585938 0.875000 0.609375 0.910156;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 55
+{
+       texcoords 0.656250 0.875000 0.679688 0.910156;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 56
+{
+       texcoords 0.726562 0.875000 0.750000 0.910156;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 57
+{
+       texcoords 0.796875 0.875000 0.820312 0.910156;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 58
+{
+       texcoords 0.875000 0.875000 0.878906 0.898438;
+       size 0.083 0.500;
+       offset 0.083 0.000;
+       advance 0.333;
+};
+glyph 59
+{
+       texcoords 0.945312 0.871094 0.949219 0.898438;
+       size 0.083 0.583;
+       offset 0.083 -0.083;
+       advance 0.333;
+};
+glyph 60
+{
+       texcoords 0.019531 0.808594 0.050781 0.832031;
+       size 0.667 0.500;
+       offset 0.083 0.083;
+       advance 0.833;
+};
+glyph 61
+{
+       texcoords 0.089844 0.812500 0.121094 0.824219;
+       size 0.667 0.250;
+       offset 0.083 0.167;
+       advance 0.833;
+};
+glyph 62
+{
+       texcoords 0.160156 0.808594 0.191406 0.832031;
+       size 0.667 0.500;
+       offset 0.083 0.083;
+       advance 0.833;
+};
+glyph 63
+{
+       texcoords 0.234375 0.804688 0.253906 0.839844;
+       size 0.417 0.750;
+       offset 0.000 0.000;
+       advance 0.500;
+};
+glyph 64
+{
+       texcoords 0.292969 0.796875 0.335938 0.839844;
+       size 0.917 0.917;
+       offset 0.083 -0.167;
+       advance 1.083;
+};
+glyph 65
+{
+       texcoords 0.371094 0.804688 0.402344 0.839844;
+       size 0.667 0.750;
+       offset 0.000 0.000;
+       advance 0.667;
+};
+glyph 66
+{
+       texcoords 0.445312 0.804688 0.468750 0.839844;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 67
+{
+       texcoords 0.515625 0.804688 0.539062 0.839844;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 68
+{
+       texcoords 0.582031 0.804688 0.609375 0.839844;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 69
+{
+       texcoords 0.656250 0.804688 0.679688 0.839844;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 70
+{
+       texcoords 0.726562 0.804688 0.746094 0.839844;
+       size 0.417 0.750;
+       offset 0.083 0.000;
+       advance 0.583;
+};
+glyph 71
+{
+       texcoords 0.792969 0.804688 0.820312 0.839844;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 72
+{
+       texcoords 0.863281 0.804688 0.890625 0.839844;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 73
+{
+       texcoords 0.945312 0.804688 0.949219 0.839844;
+       size 0.083 0.750;
+       offset 0.083 0.000;
+       advance 0.250;
+};
+glyph 74
+{
+       texcoords 0.027344 0.726562 0.039062 0.769531;
+       size 0.250 0.917;
+       offset -0.083 -0.167;
+       advance 0.250;
+};
+glyph 75
+{
+       texcoords 0.089844 0.734375 0.117188 0.769531;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.583;
+};
+glyph 76
+{
+       texcoords 0.164062 0.734375 0.183594 0.769531;
+       size 0.417 0.750;
+       offset 0.083 0.000;
+       advance 0.500;
+};
+glyph 77
+{
+       texcoords 0.230469 0.734375 0.261719 0.769531;
+       size 0.667 0.750;
+       offset 0.083 0.000;
+       advance 0.833;
+};
+glyph 78
+{
+       texcoords 0.300781 0.734375 0.328125 0.769531;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 79
+{
+       texcoords 0.371094 0.734375 0.398438 0.769531;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 80
+{
+       texcoords 0.445312 0.734375 0.468750 0.769531;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 81
+{
+       texcoords 0.511719 0.726562 0.539062 0.769531;
+       size 0.583 0.917;
+       offset 0.083 -0.167;
+       advance 0.750;
+};
+glyph 82
+{
+       texcoords 0.582031 0.734375 0.609375 0.769531;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 83
+{
+       texcoords 0.656250 0.734375 0.679688 0.769531;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 84
+{
+       texcoords 0.722656 0.734375 0.750000 0.769531;
+       size 0.583 0.750;
+       offset 0.000 0.000;
+       advance 0.583;
+};
+glyph 85
+{
+       texcoords 0.792969 0.734375 0.820312 0.769531;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 86
+{
+       texcoords 0.859375 0.734375 0.898438 0.769531;
+       size 0.833 0.750;
+       offset -0.083 0.000;
+       advance 0.667;
+};
+glyph 87
+{
+       texcoords 0.925781 0.734375 0.968750 0.769531;
+       size 0.917 0.750;
+       offset 0.000 0.000;
+       advance 0.917;
+};
+glyph 88
+{
+       texcoords 0.019531 0.664062 0.046875 0.699219;
+       size 0.583 0.750;
+       offset 0.000 0.000;
+       advance 0.583;
+};
+glyph 89
+{
+       texcoords 0.089844 0.664062 0.117188 0.699219;
+       size 0.583 0.750;
+       offset 0.000 0.000;
+       advance 0.583;
+};
+glyph 90
+{
+       texcoords 0.160156 0.664062 0.187500 0.699219;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 91
+{
+       texcoords 0.242188 0.656250 0.250000 0.699219;
+       size 0.167 0.917;
+       offset 0.167 -0.167;
+       advance 0.417;
+};
+glyph 92
+{
+       texcoords 0.308594 0.660156 0.324219 0.699219;
+       size 0.333 0.833;
+       offset 0.000 -0.083;
+       advance 0.333;
+};
+glyph 93
+{
+       texcoords 0.382812 0.656250 0.390625 0.699219;
+       size 0.167 0.917;
+       offset 0.083 -0.167;
+       advance 0.417;
+};
+glyph 94
+{
+       texcoords 0.441406 0.687500 0.472656 0.699219;
+       size 0.667 0.250;
+       offset 0.083 0.500;
+       advance 0.833;
+};
+glyph 95
+{
+       texcoords 0.515625 0.652344 0.539062 0.656250;
+       size 0.500 0.083;
+       offset 0.000 -0.250;
+       advance 0.500;
+};
+glyph 96
+{
+       texcoords 0.589844 0.695312 0.601562 0.703125;
+       size 0.250 0.167;
+       offset 0.083 0.667;
+       advance 0.500;
+};
+glyph 97
+{
+       texcoords 0.656250 0.664062 0.679688 0.691406;
+       size 0.500 0.583;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 98
+{
+       texcoords 0.726562 0.664062 0.750000 0.703125;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 99
+{
+       texcoords 0.796875 0.664062 0.816406 0.691406;
+       size 0.417 0.583;
+       offset 0.083 0.000;
+       advance 0.583;
+};
+glyph 100
+{
+       texcoords 0.867188 0.664062 0.890625 0.703125;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 101
+{
+       texcoords 0.937500 0.664062 0.960938 0.691406;
+       size 0.500 0.583;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 102
+{
+       texcoords 0.027344 0.593750 0.042969 0.632812;
+       size 0.333 0.833;
+       offset 0.000 0.000;
+       advance 0.333;
+};
+glyph 103
+{
+       texcoords 0.093750 0.582031 0.117188 0.621094;
+       size 0.500 0.833;
+       offset 0.083 -0.250;
+       advance 0.667;
+};
+glyph 104
+{
+       texcoords 0.164062 0.593750 0.187500 0.632812;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 105
+{
+       texcoords 0.242188 0.593750 0.246094 0.628906;
+       size 0.083 0.750;
+       offset 0.083 0.000;
+       advance 0.250;
+};
+glyph 106
+{
+       texcoords 0.312500 0.582031 0.320312 0.628906;
+       size 0.167 1.000;
+       offset 0.000 -0.250;
+       advance 0.250;
+};
+glyph 107
+{
+       texcoords 0.375000 0.593750 0.398438 0.632812;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.583;
+};
+glyph 108
+{
+       texcoords 0.453125 0.593750 0.457031 0.632812;
+       size 0.083 0.833;
+       offset 0.083 0.000;
+       advance 0.250;
+};
+glyph 109
+{
+       texcoords 0.507812 0.593750 0.542969 0.621094;
+       size 0.750 0.583;
+       offset 0.083 0.000;
+       advance 0.917;
+};
+glyph 110
+{
+       texcoords 0.585938 0.593750 0.609375 0.621094;
+       size 0.500 0.583;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 111
+{
+       texcoords 0.656250 0.593750 0.679688 0.621094;
+       size 0.500 0.583;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 112
+{
+       texcoords 0.726562 0.582031 0.750000 0.621094;
+       size 0.500 0.833;
+       offset 0.083 -0.250;
+       advance 0.667;
+};
+glyph 113
+{
+       texcoords 0.796875 0.582031 0.820312 0.621094;
+       size 0.500 0.833;
+       offset 0.083 -0.250;
+       advance 0.667;
+};
+glyph 114
+{
+       texcoords 0.871094 0.593750 0.886719 0.621094;
+       size 0.333 0.583;
+       offset 0.083 0.000;
+       advance 0.417;
+};
+glyph 115
+{
+       texcoords 0.937500 0.593750 0.960938 0.621094;
+       size 0.500 0.583;
+       offset 0.083 0.000;
+       advance 0.583;
+};
+glyph 116
+{
+       texcoords 0.027344 0.523438 0.042969 0.558594;
+       size 0.333 0.750;
+       offset 0.000 0.000;
+       advance 0.417;
+};
+glyph 117
+{
+       texcoords 0.093750 0.523438 0.117188 0.550781;
+       size 0.500 0.583;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 118
+{
+       texcoords 0.160156 0.523438 0.191406 0.550781;
+       size 0.667 0.583;
+       offset -0.083 0.000;
+       advance 0.500;
+};
+glyph 119
+{
+       texcoords 0.226562 0.523438 0.261719 0.550781;
+       size 0.750 0.583;
+       offset 0.000 0.000;
+       advance 0.750;
+};
+glyph 120
+{
+       texcoords 0.304688 0.523438 0.328125 0.550781;
+       size 0.500 0.583;
+       offset 0.000 0.000;
+       advance 0.500;
+};
+glyph 121
+{
+       texcoords 0.371094 0.511719 0.402344 0.550781;
+       size 0.667 0.833;
+       offset -0.083 -0.250;
+       advance 0.500;
+};
+glyph 122
+{
+       texcoords 0.445312 0.523438 0.464844 0.550781;
+       size 0.417 0.583;
+       offset 0.000 0.000;
+       advance 0.417;
+};
+glyph 123
+{
+       texcoords 0.515625 0.515625 0.535156 0.558594;
+       size 0.417 0.917;
+       offset 0.167 -0.167;
+       advance 0.667;
+};
+glyph 124
+{
+       texcoords 0.593750 0.511719 0.597656 0.558594;
+       size 0.083 1.000;
+       offset 0.167 -0.250;
+       advance 0.333;
+};
+glyph 125
+{
+       texcoords 0.656250 0.515625 0.675781 0.558594;
+       size 0.417 0.917;
+       offset 0.083 -0.167;
+       advance 0.667;
+};
+glyph 126
+{
+       texcoords 0.722656 0.535156 0.753906 0.546875;
+       size 0.667 0.250;
+       offset 0.083 0.250;
+       advance 0.833;
+};
+glyph 160
+{
+       texcoords 0.808594 0.523438 0.808594 0.523438;
+       size 0.000 0.000;
+       offset 0.000 0.000;
+       advance 0.333;
+};
+glyph 161
+{
+       texcoords 0.875000 0.523438 0.878906 0.558594;
+       size 0.083 0.750;
+       offset 0.167 0.000;
+       advance 0.417;
+};
+glyph 162
+{
+       texcoords 0.937500 0.515625 0.957031 0.558594;
+       size 0.417 0.917;
+       offset 0.083 -0.167;
+       advance 0.667;
+};
+glyph 163
+{
+       texcoords 0.023438 0.453125 0.042969 0.488281;
+       size 0.417 0.750;
+       offset 0.167 0.000;
+       advance 0.750;
+};
+glyph 164
+{
+       texcoords 0.089844 0.453125 0.117188 0.484375;
+       size 0.583 0.667;
+       offset 0.000 0.000;
+       advance 0.667;
+};
+glyph 165
+{
+       texcoords 0.160156 0.453125 0.187500 0.488281;
+       size 0.583 0.750;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 166
+{
+       texcoords 0.242188 0.445312 0.246094 0.484375;
+       size 0.083 0.833;
+       offset 0.167 -0.167;
+       advance 0.333;
+};
+glyph 167
+{
+       texcoords 0.304688 0.449219 0.324219 0.488281;
+       size 0.417 0.833;
+       offset 0.000 -0.083;
+       advance 0.417;
+};
+glyph 168
+{
+       texcoords 0.378906 0.484375 0.390625 0.488281;
+       size 0.250 0.083;
+       offset 0.167 0.667;
+       advance 0.583;
+};
+glyph 169
+{
+       texcoords 0.437500 0.453125 0.472656 0.488281;
+       size 0.750 0.750;
+       offset 0.167 0.000;
+       advance 1.000;
+};
+glyph 170
+{
+       texcoords 0.515625 0.460938 0.539062 0.488281;
+       size 0.500 0.583;
+       offset 0.083 0.167;
+       advance 0.500;
+};
+glyph 171
+{
+       texcoords 0.585938 0.457031 0.609375 0.476562;
+       size 0.500 0.417;
+       offset 0.083 0.083;
+       advance 0.583;
+};
+glyph 172
+{
+       texcoords 0.652344 0.460938 0.683594 0.472656;
+       size 0.667 0.250;
+       offset 0.083 0.167;
+       advance 0.833;
+};
+glyph 173
+{
+       texcoords 0.730469 0.464844 0.742188 0.468750;
+       size 0.250 0.083;
+       offset 0.083 0.250;
+       advance 0.333;
+};
+glyph 174
+{
+       texcoords 0.789062 0.453125 0.824219 0.488281;
+       size 0.750 0.750;
+       offset 0.167 0.000;
+       advance 1.000;
+};
+glyph 175
+{
+       texcoords 0.871094 0.484375 0.886719 0.488281;
+       size 0.333 0.083;
+       offset 0.083 0.667;
+       advance 0.500;
+};
+glyph 176
+{
+       texcoords 0.941406 0.472656 0.957031 0.488281;
+       size 0.333 0.333;
+       offset 0.083 0.417;
+       advance 0.500;
+};
+glyph 177
+{
+       texcoords 0.019531 0.382812 0.046875 0.410156;
+       size 0.583 0.583;
+       offset 0.167 0.000;
+       advance 0.833;
+};
+glyph 178
+{
+       texcoords 0.097656 0.398438 0.109375 0.417969;
+       size 0.250 0.417;
+       offset 0.083 0.333;
+       advance 0.417;
+};
+glyph 179
+{
+       texcoords 0.167969 0.398438 0.183594 0.417969;
+       size 0.333 0.417;
+       offset 0.083 0.333;
+       advance 0.417;
+};
+glyph 180
+{
+       texcoords 0.238281 0.414062 0.250000 0.421875;
+       size 0.250 0.167;
+       offset 0.167 0.667;
+       advance 0.500;
+};
+glyph 181
+{
+       texcoords 0.300781 0.371094 0.328125 0.410156;
+       size 0.583 0.833;
+       offset 0.083 -0.250;
+       advance 0.667;
+};
+glyph 182
+{
+       texcoords 0.375000 0.378906 0.394531 0.417969;
+       size 0.417 0.833;
+       offset 0.083 -0.083;
+       advance 0.667;
+};
+glyph 183
+{
+       texcoords 0.453125 0.394531 0.457031 0.402344;
+       size 0.083 0.167;
+       offset 0.083 0.250;
+       advance 0.333;
+};
+glyph 184
+{
+       texcoords 0.523438 0.375000 0.531250 0.382812;
+       size 0.167 0.167;
+       offset 0.167 -0.167;
+       advance 0.500;
+};
+glyph 185
+{
+       texcoords 0.589844 0.398438 0.601562 0.417969;
+       size 0.250 0.417;
+       offset 0.083 0.333;
+       advance 0.417;
+};
+glyph 186
+{
+       texcoords 0.656250 0.390625 0.675781 0.417969;
+       size 0.417 0.583;
+       offset 0.083 0.167;
+       advance 0.500;
+};
+glyph 187
+{
+       texcoords 0.726562 0.386719 0.750000 0.406250;
+       size 0.500 0.417;
+       offset 0.083 0.083;
+       advance 0.583;
+};
+glyph 188
+{
+       texcoords 0.785156 0.382812 0.828125 0.417969;
+       size 0.917 0.750;
+       offset 0.083 0.000;
+       advance 1.000;
+};
+glyph 189
+{
+       texcoords 0.859375 0.382812 0.898438 0.417969;
+       size 0.833 0.750;
+       offset 0.083 0.000;
+       advance 1.000;
+};
+glyph 190
+{
+       texcoords 0.925781 0.382812 0.968750 0.417969;
+       size 0.917 0.750;
+       offset 0.083 0.000;
+       advance 1.000;
+};
+glyph 191
+{
+       texcoords 0.023438 0.312500 0.042969 0.347656;
+       size 0.417 0.750;
+       offset 0.083 0.000;
+       advance 0.500;
+};
+glyph 192
+{
+       texcoords 0.089844 0.312500 0.121094 0.359375;
+       size 0.667 1.000;
+       offset 0.000 0.000;
+       advance 0.667;
+};
+glyph 193
+{
+       texcoords 0.160156 0.312500 0.191406 0.359375;
+       size 0.667 1.000;
+       offset 0.000 0.000;
+       advance 0.667;
+};
+glyph 194
+{
+       texcoords 0.230469 0.312500 0.261719 0.359375;
+       size 0.667 1.000;
+       offset 0.000 0.000;
+       advance 0.667;
+};
+glyph 195
+{
+       texcoords 0.300781 0.312500 0.332031 0.359375;
+       size 0.667 1.000;
+       offset 0.000 0.000;
+       advance 0.667;
+};
+glyph 196
+{
+       texcoords 0.371094 0.312500 0.402344 0.355469;
+       size 0.667 0.917;
+       offset 0.000 0.000;
+       advance 0.667;
+};
+glyph 197
+{
+       texcoords 0.441406 0.312500 0.472656 0.359375;
+       size 0.667 1.000;
+       offset 0.000 0.000;
+       advance 0.667;
+};
+glyph 198
+{
+       texcoords 0.503906 0.312500 0.546875 0.347656;
+       size 0.917 0.750;
+       offset 0.000 0.000;
+       advance 1.000;
+};
+glyph 199
+{
+       texcoords 0.585938 0.304688 0.609375 0.347656;
+       size 0.500 0.917;
+       offset 0.083 -0.167;
+       advance 0.667;
+};
+glyph 200
+{
+       texcoords 0.656250 0.312500 0.679688 0.359375;
+       size 0.500 1.000;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 201
+{
+       texcoords 0.726562 0.312500 0.750000 0.359375;
+       size 0.500 1.000;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 202
+{
+       texcoords 0.796875 0.312500 0.820312 0.359375;
+       size 0.500 1.000;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 203
+{
+       texcoords 0.867188 0.312500 0.890625 0.355469;
+       size 0.500 0.917;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 204
+{
+       texcoords 0.941406 0.312500 0.953125 0.359375;
+       size 0.250 1.000;
+       offset 0.083 0.000;
+       advance 0.250;
+};
+glyph 205
+{
+       texcoords 0.027344 0.242188 0.039062 0.289062;
+       size 0.250 1.000;
+       offset 0.083 0.000;
+       advance 0.250;
+};
+glyph 206
+{
+       texcoords 0.093750 0.242188 0.113281 0.289062;
+       size 0.417 1.000;
+       offset -0.083 0.000;
+       advance 0.250;
+};
+glyph 207
+{
+       texcoords 0.167969 0.242188 0.179688 0.285156;
+       size 0.250 0.917;
+       offset 0.000 0.000;
+       advance 0.250;
+};
+glyph 208
+{
+       texcoords 0.230469 0.242188 0.261719 0.277344;
+       size 0.667 0.750;
+       offset 0.000 0.000;
+       advance 0.750;
+};
+glyph 209
+{
+       texcoords 0.300781 0.242188 0.328125 0.289062;
+       size 0.583 1.000;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 210
+{
+       texcoords 0.371094 0.242188 0.398438 0.289062;
+       size 0.583 1.000;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 211
+{
+       texcoords 0.441406 0.242188 0.468750 0.289062;
+       size 0.583 1.000;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 212
+{
+       texcoords 0.511719 0.242188 0.539062 0.289062;
+       size 0.583 1.000;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 213
+{
+       texcoords 0.582031 0.242188 0.609375 0.289062;
+       size 0.583 1.000;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 214
+{
+       texcoords 0.652344 0.242188 0.679688 0.285156;
+       size 0.583 0.917;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 215
+{
+       texcoords 0.722656 0.242188 0.750000 0.269531;
+       size 0.583 0.583;
+       offset 0.167 0.000;
+       advance 0.833;
+};
+glyph 216
+{
+       texcoords 0.789062 0.238281 0.824219 0.281250;
+       size 0.750 0.917;
+       offset 0.000 -0.083;
+       advance 0.750;
+};
+glyph 217
+{
+       texcoords 0.863281 0.242188 0.890625 0.289062;
+       size 0.583 1.000;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 218
+{
+       texcoords 0.933594 0.242188 0.960938 0.289062;
+       size 0.583 1.000;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 219
+{
+       texcoords 0.019531 0.171875 0.046875 0.218750;
+       size 0.583 1.000;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 220
+{
+       texcoords 0.089844 0.171875 0.117188 0.214844;
+       size 0.583 0.917;
+       offset 0.083 0.000;
+       advance 0.750;
+};
+glyph 221
+{
+       texcoords 0.160156 0.171875 0.187500 0.218750;
+       size 0.583 1.000;
+       offset 0.000 0.000;
+       advance 0.583;
+};
+glyph 222
+{
+       texcoords 0.234375 0.171875 0.257812 0.207031;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 223
+{
+       texcoords 0.304688 0.171875 0.328125 0.210938;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 224
+{
+       texcoords 0.375000 0.171875 0.398438 0.210938;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 225
+{
+       texcoords 0.445312 0.171875 0.468750 0.210938;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 226
+{
+       texcoords 0.515625 0.171875 0.539062 0.210938;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 227
+{
+       texcoords 0.585938 0.171875 0.609375 0.210938;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 228
+{
+       texcoords 0.656250 0.171875 0.679688 0.207031;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 229
+{
+       texcoords 0.726562 0.171875 0.750000 0.218750;
+       size 0.500 1.000;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 230
+{
+       texcoords 0.789062 0.171875 0.828125 0.199219;
+       size 0.833 0.583;
+       offset 0.083 0.000;
+       advance 1.000;
+};
+glyph 231
+{
+       texcoords 0.867188 0.164062 0.886719 0.199219;
+       size 0.417 0.750;
+       offset 0.083 -0.167;
+       advance 0.583;
+};
+glyph 232
+{
+       texcoords 0.937500 0.171875 0.960938 0.210938;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 233
+{
+       texcoords 0.023438 0.101562 0.046875 0.140625;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 234
+{
+       texcoords 0.093750 0.101562 0.117188 0.140625;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 235
+{
+       texcoords 0.164062 0.101562 0.187500 0.136719;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 236
+{
+       texcoords 0.238281 0.101562 0.250000 0.140625;
+       size 0.250 0.833;
+       offset 0.000 0.000;
+       advance 0.250;
+};
+glyph 237
+{
+       texcoords 0.308594 0.101562 0.320312 0.140625;
+       size 0.250 0.833;
+       offset 0.083 0.000;
+       advance 0.250;
+};
+glyph 238
+{
+       texcoords 0.375000 0.101562 0.394531 0.140625;
+       size 0.417 0.833;
+       offset -0.083 0.000;
+       advance 0.250;
+};
+glyph 239
+{
+       texcoords 0.449219 0.101562 0.460938 0.136719;
+       size 0.250 0.750;
+       offset 0.000 0.000;
+       advance 0.250;
+};
+glyph 240
+{
+       texcoords 0.515625 0.101562 0.539062 0.140625;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 241
+{
+       texcoords 0.585938 0.101562 0.609375 0.140625;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 242
+{
+       texcoords 0.656250 0.101562 0.679688 0.140625;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 243
+{
+       texcoords 0.726562 0.101562 0.750000 0.140625;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 244
+{
+       texcoords 0.796875 0.101562 0.820312 0.140625;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 245
+{
+       texcoords 0.867188 0.101562 0.890625 0.140625;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 246
+{
+       texcoords 0.937500 0.101562 0.960938 0.136719;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 247
+{
+       texcoords 0.019531 0.035156 0.046875 0.054688;
+       size 0.583 0.417;
+       offset 0.083 0.083;
+       advance 0.833;
+};
+glyph 248
+{
+       texcoords 0.089844 0.027344 0.121094 0.062500;
+       size 0.667 0.750;
+       offset 0.000 -0.083;
+       advance 0.667;
+};
+glyph 249
+{
+       texcoords 0.164062 0.031250 0.187500 0.070312;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 250
+{
+       texcoords 0.234375 0.031250 0.257812 0.070312;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 251
+{
+       texcoords 0.304688 0.031250 0.328125 0.070312;
+       size 0.500 0.833;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 252
+{
+       texcoords 0.375000 0.031250 0.398438 0.066406;
+       size 0.500 0.750;
+       offset 0.083 0.000;
+       advance 0.667;
+};
+glyph 253
+{
+       texcoords 0.441406 0.019531 0.472656 0.070312;
+       size 0.667 1.083;
+       offset -0.083 -0.250;
+       advance 0.500;
+};
+glyph 254
+{
+       texcoords 0.515625 0.019531 0.539062 0.070312;
+       size 0.500 1.083;
+       offset 0.083 -0.250;
+       advance 0.667;
+};
+glyph 255
+{
+       texcoords 0.582031 0.019531 0.613281 0.066406;
+       size 0.667 1.000;
+       offset -0.083 -0.250;
+       advance 0.500;
+};
diff --git a/dejavu-12.png b/dejavu-12.png
new file mode 100644 (file)
index 0000000..66fc386
Binary files /dev/null and b/dejavu-12.png differ
diff --git a/dejavu-20.font b/dejavu-20.font
new file mode 100644 (file)
index 0000000..17170ce
--- /dev/null
@@ -0,0 +1,1339 @@
+texture "dejavu-20.png";
+default_size 20.000000;
+glyph 32
+{
+       texcoords 0.023438 0.906250 0.023438 0.906250;
+       size 0.000000 0.000000;
+       offset 0.000000 0.000000;
+       advance 0.300000;
+};
+glyph 33
+{
+       texcoords 0.070312 0.906250 0.074219 0.964844;
+       size 0.100000 0.750000;
+       offset 0.150000 0.000000;
+       advance 0.400000;
+};
+glyph 34
+{
+       texcoords 0.115234 0.945312 0.126953 0.964844;
+       size 0.300000 0.250000;
+       offset 0.100000 0.500000;
+       advance 0.500000;
+};
+glyph 35
+{
+       texcoords 0.156250 0.906250 0.183594 0.964844;
+       size 0.700000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.850000;
+};
+glyph 36
+{
+       texcoords 0.208984 0.894531 0.228516 0.964844;
+       size 0.500000 0.900000;
+       offset 0.100000 -0.150000;
+       advance 0.650000;
+};
+glyph 37
+{
+       texcoords 0.251953 0.906250 0.285156 0.964844;
+       size 0.850000 0.750000;
+       offset 0.000000 0.000000;
+       advance 0.950000;
+};
+glyph 38
+{
+       texcoords 0.302734 0.906250 0.330078 0.964844;
+       size 0.700000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.750000;
+};
+glyph 39
+{
+       texcoords 0.363281 0.945312 0.367188 0.964844;
+       size 0.100000 0.250000;
+       offset 0.100000 0.500000;
+       advance 0.300000;
+};
+glyph 40
+{
+       texcoords 0.410156 0.894531 0.417969 0.964844;
+       size 0.200000 0.900000;
+       offset 0.100000 -0.150000;
+       advance 0.400000;
+};
+glyph 41
+{
+       texcoords 0.458984 0.894531 0.466797 0.964844;
+       size 0.200000 0.900000;
+       offset 0.100000 -0.150000;
+       advance 0.400000;
+};
+glyph 42
+{
+       texcoords 0.503906 0.925781 0.521484 0.964844;
+       size 0.450000 0.500000;
+       offset 0.000000 0.250000;
+       advance 0.500000;
+};
+glyph 43
+{
+       texcoords 0.548828 0.906250 0.572266 0.953125;
+       size 0.600000 0.600000;
+       offset 0.100000 0.000000;
+       advance 0.850000;
+};
+glyph 44
+{
+       texcoords 0.607422 0.894531 0.613281 0.914062;
+       size 0.150000 0.250000;
+       offset 0.050000 -0.150000;
+       advance 0.300000;
+};
+glyph 45
+{
+       texcoords 0.654297 0.925781 0.664062 0.933594;
+       size 0.250000 0.100000;
+       offset 0.050000 0.250000;
+       advance 0.350000;
+};
+glyph 46
+{
+       texcoords 0.705078 0.906250 0.708984 0.914062;
+       size 0.100000 0.100000;
+       offset 0.100000 0.000000;
+       advance 0.300000;
+};
+glyph 47
+{
+       texcoords 0.750000 0.902344 0.763672 0.964844;
+       size 0.350000 0.800000;
+       offset 0.000000 -0.050000;
+       advance 0.350000;
+};
+glyph 48
+{
+       texcoords 0.794922 0.906250 0.814453 0.964844;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 49
+{
+       texcoords 0.845703 0.906250 0.863281 0.964844;
+       size 0.450000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 50
+{
+       texcoords 0.892578 0.906250 0.912109 0.964844;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 51
+{
+       texcoords 0.941406 0.906250 0.960938 0.964844;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 52
+{
+       texcoords 0.013672 0.808594 0.035156 0.867188;
+       size 0.550000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 53
+{
+       texcoords 0.064453 0.808594 0.082031 0.867188;
+       size 0.450000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 54
+{
+       texcoords 0.111328 0.808594 0.130859 0.867188;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 55
+{
+       texcoords 0.160156 0.808594 0.179688 0.867188;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 56
+{
+       texcoords 0.208984 0.808594 0.228516 0.867188;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 57
+{
+       texcoords 0.257812 0.808594 0.277344 0.867188;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 58
+{
+       texcoords 0.314453 0.808594 0.318359 0.847656;
+       size 0.100000 0.500000;
+       offset 0.100000 0.000000;
+       advance 0.350000;
+};
+glyph 59
+{
+       texcoords 0.363281 0.796875 0.369141 0.847656;
+       size 0.150000 0.650000;
+       offset 0.050000 -0.150000;
+       advance 0.350000;
+};
+glyph 60
+{
+       texcoords 0.402344 0.812500 0.427734 0.855469;
+       size 0.650000 0.550000;
+       offset 0.100000 0.050000;
+       advance 0.850000;
+};
+glyph 61
+{
+       texcoords 0.451172 0.820312 0.476562 0.843750;
+       size 0.650000 0.300000;
+       offset 0.100000 0.150000;
+       advance 0.850000;
+};
+glyph 62
+{
+       texcoords 0.500000 0.812500 0.525391 0.855469;
+       size 0.650000 0.550000;
+       offset 0.100000 0.050000;
+       advance 0.850000;
+};
+glyph 63
+{
+       texcoords 0.552734 0.808594 0.568359 0.867188;
+       size 0.400000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.550000;
+};
+glyph 64
+{
+       texcoords 0.591797 0.796875 0.626953 0.867188;
+       size 0.900000 0.900000;
+       offset 0.050000 -0.150000;
+       advance 1.000000;
+};
+glyph 65
+{
+       texcoords 0.646484 0.808594 0.671875 0.867188;
+       size 0.650000 0.750000;
+       offset 0.000000 0.000000;
+       advance 0.700000;
+};
+glyph 66
+{
+       texcoords 0.697266 0.808594 0.716797 0.867188;
+       size 0.500000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.700000;
+};
+glyph 67
+{
+       texcoords 0.744141 0.808594 0.767578 0.867188;
+       size 0.600000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.700000;
+};
+glyph 68
+{
+       texcoords 0.792969 0.808594 0.816406 0.867188;
+       size 0.600000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.750000;
+};
+glyph 69
+{
+       texcoords 0.845703 0.808594 0.863281 0.867188;
+       size 0.450000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 70
+{
+       texcoords 0.894531 0.808594 0.910156 0.867188;
+       size 0.400000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.600000;
+};
+glyph 71
+{
+       texcoords 0.939453 0.808594 0.964844 0.867188;
+       size 0.650000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.800000;
+};
+glyph 72
+{
+       texcoords 0.013672 0.710938 0.035156 0.769531;
+       size 0.550000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.750000;
+};
+glyph 73
+{
+       texcoords 0.070312 0.710938 0.074219 0.769531;
+       size 0.100000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.300000;
+};
+glyph 74
+{
+       texcoords 0.117188 0.695312 0.126953 0.769531;
+       size 0.250000 0.950000;
+       offset -0.050000 -0.200000;
+       advance 0.300000;
+};
+glyph 75
+{
+       texcoords 0.158203 0.710938 0.181641 0.769531;
+       size 0.600000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 76
+{
+       texcoords 0.210938 0.710938 0.228516 0.769531;
+       size 0.450000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.550000;
+};
+glyph 77
+{
+       texcoords 0.255859 0.710938 0.281250 0.769531;
+       size 0.650000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.850000;
+};
+glyph 78
+{
+       texcoords 0.306641 0.710938 0.328125 0.769531;
+       size 0.550000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.750000;
+};
+glyph 79
+{
+       texcoords 0.351562 0.710938 0.378906 0.769531;
+       size 0.700000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.800000;
+};
+glyph 80
+{
+       texcoords 0.406250 0.710938 0.423828 0.769531;
+       size 0.450000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.600000;
+};
+glyph 81
+{
+       texcoords 0.449219 0.699219 0.476562 0.769531;
+       size 0.700000 0.900000;
+       offset 0.050000 -0.150000;
+       advance 0.800000;
+};
+glyph 82
+{
+       texcoords 0.501953 0.710938 0.523438 0.769531;
+       size 0.550000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.700000;
+};
+glyph 83
+{
+       texcoords 0.550781 0.710938 0.572266 0.769531;
+       size 0.550000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 84
+{
+       texcoords 0.597656 0.710938 0.621094 0.769531;
+       size 0.600000 0.750000;
+       offset 0.000000 0.000000;
+       advance 0.600000;
+};
+glyph 85
+{
+       texcoords 0.648438 0.710938 0.669922 0.769531;
+       size 0.550000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.750000;
+};
+glyph 86
+{
+       texcoords 0.695312 0.710938 0.720703 0.769531;
+       size 0.650000 0.750000;
+       offset 0.000000 0.000000;
+       advance 0.700000;
+};
+glyph 87
+{
+       texcoords 0.738281 0.710938 0.773438 0.769531;
+       size 0.900000 0.750000;
+       offset 0.050000 0.000000;
+       advance 1.000000;
+};
+glyph 88
+{
+       texcoords 0.792969 0.710938 0.816406 0.769531;
+       size 0.600000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.700000;
+};
+glyph 89
+{
+       texcoords 0.841797 0.710938 0.865234 0.769531;
+       size 0.600000 0.750000;
+       offset 0.000000 0.000000;
+       advance 0.600000;
+};
+glyph 90
+{
+       texcoords 0.890625 0.710938 0.914062 0.769531;
+       size 0.600000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.700000;
+};
+glyph 91
+{
+       texcoords 0.947266 0.699219 0.955078 0.769531;
+       size 0.200000 0.900000;
+       offset 0.100000 -0.150000;
+       advance 0.400000;
+};
+glyph 92
+{
+       texcoords 0.017578 0.609375 0.031250 0.671875;
+       size 0.350000 0.800000;
+       offset 0.000000 -0.050000;
+       advance 0.350000;
+};
+glyph 93
+{
+       texcoords 0.068359 0.601562 0.076172 0.671875;
+       size 0.200000 0.900000;
+       offset 0.100000 -0.150000;
+       advance 0.400000;
+};
+glyph 94
+{
+       texcoords 0.109375 0.652344 0.134766 0.671875;
+       size 0.650000 0.250000;
+       offset 0.100000 0.500000;
+       advance 0.850000;
+};
+glyph 95
+{
+       texcoords 0.160156 0.593750 0.179688 0.601562;
+       size 0.500000 0.100000;
+       offset 0.000000 -0.250000;
+       advance 0.500000;
+};
+glyph 96
+{
+       texcoords 0.214844 0.660156 0.224609 0.675781;
+       size 0.250000 0.200000;
+       offset 0.050000 0.600000;
+       advance 0.500000;
+};
+glyph 97
+{
+       texcoords 0.259766 0.613281 0.277344 0.656250;
+       size 0.450000 0.550000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 98
+{
+       texcoords 0.306641 0.613281 0.326172 0.671875;
+       size 0.500000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 99
+{
+       texcoords 0.357422 0.613281 0.375000 0.656250;
+       size 0.450000 0.550000;
+       offset 0.050000 0.000000;
+       advance 0.550000;
+};
+glyph 100
+{
+       texcoords 0.404297 0.613281 0.423828 0.671875;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.650000;
+};
+glyph 101
+{
+       texcoords 0.453125 0.613281 0.472656 0.656250;
+       size 0.500000 0.550000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 102
+{
+       texcoords 0.505859 0.613281 0.519531 0.671875;
+       size 0.350000 0.750000;
+       offset 0.000000 0.000000;
+       advance 0.350000;
+};
+glyph 103
+{
+       texcoords 0.550781 0.597656 0.570312 0.656250;
+       size 0.500000 0.750000;
+       offset 0.050000 -0.200000;
+       advance 0.650000;
+};
+glyph 104
+{
+       texcoords 0.601562 0.613281 0.619141 0.671875;
+       size 0.450000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 105
+{
+       texcoords 0.656250 0.613281 0.660156 0.671875;
+       size 0.100000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.300000;
+};
+glyph 106
+{
+       texcoords 0.703125 0.597656 0.710938 0.671875;
+       size 0.200000 0.950000;
+       offset 0.000000 -0.200000;
+       advance 0.300000;
+};
+glyph 107
+{
+       texcoords 0.746094 0.613281 0.765625 0.671875;
+       size 0.500000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.600000;
+};
+glyph 108
+{
+       texcoords 0.802734 0.613281 0.806641 0.671875;
+       size 0.100000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.300000;
+};
+glyph 109
+{
+       texcoords 0.837891 0.613281 0.869141 0.656250;
+       size 0.800000 0.550000;
+       offset 0.100000 0.000000;
+       advance 1.000000;
+};
+glyph 110
+{
+       texcoords 0.894531 0.613281 0.912109 0.656250;
+       size 0.450000 0.550000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 111
+{
+       texcoords 0.941406 0.613281 0.960938 0.656250;
+       size 0.500000 0.550000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 112
+{
+       texcoords 0.013672 0.500000 0.033203 0.558594;
+       size 0.500000 0.750000;
+       offset 0.100000 -0.200000;
+       advance 0.650000;
+};
+glyph 113
+{
+       texcoords 0.062500 0.500000 0.082031 0.558594;
+       size 0.500000 0.750000;
+       offset 0.050000 -0.200000;
+       advance 0.650000;
+};
+glyph 114
+{
+       texcoords 0.115234 0.515625 0.126953 0.558594;
+       size 0.300000 0.550000;
+       offset 0.100000 0.000000;
+       advance 0.400000;
+};
+glyph 115
+{
+       texcoords 0.162109 0.515625 0.179688 0.558594;
+       size 0.450000 0.550000;
+       offset 0.050000 0.000000;
+       advance 0.500000;
+};
+glyph 116
+{
+       texcoords 0.212891 0.515625 0.226562 0.570312;
+       size 0.350000 0.700000;
+       offset 0.050000 0.000000;
+       advance 0.400000;
+};
+glyph 117
+{
+       texcoords 0.259766 0.515625 0.277344 0.558594;
+       size 0.450000 0.550000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 118
+{
+       texcoords 0.306641 0.515625 0.328125 0.558594;
+       size 0.550000 0.550000;
+       offset 0.000000 0.000000;
+       advance 0.550000;
+};
+glyph 119
+{
+       texcoords 0.351562 0.515625 0.380859 0.558594;
+       size 0.750000 0.550000;
+       offset 0.050000 0.000000;
+       advance 0.850000;
+};
+glyph 120
+{
+       texcoords 0.404297 0.515625 0.425781 0.558594;
+       size 0.550000 0.550000;
+       offset 0.000000 0.000000;
+       advance 0.550000;
+};
+glyph 121
+{
+       texcoords 0.453125 0.500000 0.474609 0.558594;
+       size 0.550000 0.750000;
+       offset 0.000000 -0.200000;
+       advance 0.550000;
+};
+glyph 122
+{
+       texcoords 0.503906 0.515625 0.521484 0.558594;
+       size 0.450000 0.550000;
+       offset 0.050000 0.000000;
+       advance 0.550000;
+};
+glyph 123
+{
+       texcoords 0.552734 0.503906 0.568359 0.574219;
+       size 0.400000 0.900000;
+       offset 0.150000 -0.150000;
+       advance 0.650000;
+};
+glyph 124
+{
+       texcoords 0.607422 0.496094 0.611328 0.574219;
+       size 0.100000 1.000000;
+       offset 0.150000 -0.250000;
+       advance 0.350000;
+};
+glyph 125
+{
+       texcoords 0.650391 0.503906 0.666016 0.574219;
+       size 0.400000 0.900000;
+       offset 0.100000 -0.150000;
+       advance 0.650000;
+};
+glyph 126
+{
+       texcoords 0.695312 0.531250 0.720703 0.550781;
+       size 0.650000 0.250000;
+       offset 0.100000 0.200000;
+       advance 0.850000;
+};
+glyph 160
+{
+       texcoords 0.755859 0.515625 0.755859 0.515625;
+       size 0.000000 0.000000;
+       offset 0.000000 0.000000;
+       advance 0.300000;
+};
+glyph 161
+{
+       texcoords 0.802734 0.515625 0.806641 0.574219;
+       size 0.100000 0.750000;
+       offset 0.150000 0.000000;
+       advance 0.400000;
+};
+glyph 162
+{
+       texcoords 0.845703 0.503906 0.863281 0.570312;
+       size 0.450000 0.850000;
+       offset 0.100000 -0.150000;
+       advance 0.650000;
+};
+glyph 163
+{
+       texcoords 0.894531 0.515625 0.912109 0.574219;
+       size 0.450000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 164
+{
+       texcoords 0.941406 0.519531 0.962891 0.562500;
+       size 0.550000 0.550000;
+       offset 0.000000 0.050000;
+       advance 0.650000;
+};
+glyph 165
+{
+       texcoords 0.011719 0.417969 0.035156 0.476562;
+       size 0.600000 0.750000;
+       offset 0.000000 0.000000;
+       advance 0.650000;
+};
+glyph 166
+{
+       texcoords 0.070312 0.406250 0.074219 0.472656;
+       size 0.100000 0.850000;
+       offset 0.150000 -0.150000;
+       advance 0.350000;
+};
+glyph 167
+{
+       texcoords 0.113281 0.410156 0.128906 0.476562;
+       size 0.400000 0.850000;
+       offset 0.050000 -0.100000;
+       advance 0.500000;
+};
+glyph 168
+{
+       texcoords 0.164062 0.468750 0.175781 0.476562;
+       size 0.300000 0.100000;
+       offset 0.100000 0.650000;
+       advance 0.500000;
+};
+glyph 169
+{
+       texcoords 0.205078 0.417969 0.232422 0.476562;
+       size 0.700000 0.750000;
+       offset 0.150000 0.000000;
+       advance 1.000000;
+};
+glyph 170
+{
+       texcoords 0.259766 0.437500 0.277344 0.476562;
+       size 0.450000 0.500000;
+       offset 0.050000 0.250000;
+       advance 0.450000;
+};
+glyph 171
+{
+       texcoords 0.306641 0.421875 0.326172 0.457031;
+       size 0.500000 0.450000;
+       offset 0.100000 0.050000;
+       advance 0.600000;
+};
+glyph 172
+{
+       texcoords 0.353516 0.425781 0.378906 0.449219;
+       size 0.650000 0.300000;
+       offset 0.100000 0.100000;
+       advance 0.850000;
+};
+glyph 173
+{
+       texcoords 0.410156 0.437500 0.419922 0.445312;
+       size 0.250000 0.100000;
+       offset 0.050000 0.250000;
+       advance 0.350000;
+};
+glyph 174
+{
+       texcoords 0.449219 0.417969 0.478516 0.476562;
+       size 0.750000 0.750000;
+       offset 0.150000 0.000000;
+       advance 1.000000;
+};
+glyph 175
+{
+       texcoords 0.505859 0.468750 0.517578 0.476562;
+       size 0.300000 0.100000;
+       offset 0.100000 0.650000;
+       advance 0.500000;
+};
+glyph 176
+{
+       texcoords 0.554688 0.453125 0.566406 0.476562;
+       size 0.300000 0.300000;
+       offset 0.100000 0.450000;
+       advance 0.500000;
+};
+glyph 177
+{
+       texcoords 0.597656 0.417969 0.621094 0.468750;
+       size 0.600000 0.650000;
+       offset 0.150000 0.000000;
+       advance 0.850000;
+};
+glyph 178
+{
+       texcoords 0.652344 0.445312 0.664062 0.476562;
+       size 0.300000 0.400000;
+       offset 0.050000 0.350000;
+       advance 0.400000;
+};
+glyph 179
+{
+       texcoords 0.701172 0.445312 0.712891 0.476562;
+       size 0.300000 0.400000;
+       offset 0.050000 0.350000;
+       advance 0.400000;
+};
+glyph 180
+{
+       texcoords 0.751953 0.464844 0.761719 0.480469;
+       size 0.250000 0.200000;
+       offset 0.150000 0.600000;
+       advance 0.500000;
+};
+glyph 181
+{
+       texcoords 0.794922 0.402344 0.814453 0.460938;
+       size 0.500000 0.750000;
+       offset 0.100000 -0.200000;
+       advance 0.650000;
+};
+glyph 182
+{
+       texcoords 0.845703 0.410156 0.863281 0.476562;
+       size 0.450000 0.850000;
+       offset 0.100000 -0.100000;
+       advance 0.650000;
+};
+glyph 183
+{
+       texcoords 0.900391 0.441406 0.904297 0.449219;
+       size 0.100000 0.100000;
+       offset 0.100000 0.300000;
+       advance 0.300000;
+};
+glyph 184
+{
+       texcoords 0.947266 0.402344 0.955078 0.417969;
+       size 0.200000 0.200000;
+       offset 0.150000 -0.200000;
+       advance 0.500000;
+};
+glyph 185
+{
+       texcoords 0.019531 0.347656 0.029297 0.378906;
+       size 0.250000 0.400000;
+       offset 0.100000 0.350000;
+       advance 0.400000;
+};
+glyph 186
+{
+       texcoords 0.064453 0.339844 0.080078 0.378906;
+       size 0.400000 0.500000;
+       offset 0.050000 0.250000;
+       advance 0.450000;
+};
+glyph 187
+{
+       texcoords 0.111328 0.324219 0.130859 0.359375;
+       size 0.500000 0.450000;
+       offset 0.100000 0.050000;
+       advance 0.600000;
+};
+glyph 188
+{
+       texcoords 0.154297 0.320312 0.185547 0.378906;
+       size 0.800000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.950000;
+};
+glyph 189
+{
+       texcoords 0.203125 0.320312 0.234375 0.378906;
+       size 0.800000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.950000;
+};
+glyph 190
+{
+       texcoords 0.251953 0.320312 0.285156 0.378906;
+       size 0.850000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.950000;
+};
+glyph 191
+{
+       texcoords 0.308594 0.320312 0.324219 0.378906;
+       size 0.400000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.550000;
+};
+glyph 192
+{
+       texcoords 0.353516 0.320312 0.378906 0.394531;
+       size 0.650000 0.950000;
+       offset 0.000000 0.000000;
+       advance 0.700000;
+};
+glyph 193
+{
+       texcoords 0.402344 0.320312 0.427734 0.394531;
+       size 0.650000 0.950000;
+       offset 0.000000 0.000000;
+       advance 0.700000;
+};
+glyph 194
+{
+       texcoords 0.451172 0.320312 0.476562 0.394531;
+       size 0.650000 0.950000;
+       offset 0.000000 0.000000;
+       advance 0.700000;
+};
+glyph 195
+{
+       texcoords 0.500000 0.320312 0.525391 0.390625;
+       size 0.650000 0.900000;
+       offset 0.000000 0.000000;
+       advance 0.700000;
+};
+glyph 196
+{
+       texcoords 0.548828 0.320312 0.574219 0.394531;
+       size 0.650000 0.950000;
+       offset 0.000000 0.000000;
+       advance 0.700000;
+};
+glyph 197
+{
+       texcoords 0.597656 0.320312 0.623047 0.394531;
+       size 0.650000 0.950000;
+       offset 0.000000 0.000000;
+       advance 0.700000;
+};
+glyph 198
+{
+       texcoords 0.640625 0.320312 0.675781 0.378906;
+       size 0.900000 0.750000;
+       offset 0.000000 0.000000;
+       advance 1.000000;
+};
+glyph 199
+{
+       texcoords 0.695312 0.304688 0.718750 0.378906;
+       size 0.600000 0.950000;
+       offset 0.050000 -0.200000;
+       advance 0.700000;
+};
+glyph 200
+{
+       texcoords 0.748047 0.320312 0.765625 0.394531;
+       size 0.450000 0.950000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 201
+{
+       texcoords 0.796875 0.320312 0.814453 0.394531;
+       size 0.450000 0.950000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 202
+{
+       texcoords 0.845703 0.320312 0.863281 0.394531;
+       size 0.450000 0.950000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 203
+{
+       texcoords 0.894531 0.320312 0.912109 0.394531;
+       size 0.450000 0.950000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 204
+{
+       texcoords 0.947266 0.320312 0.955078 0.394531;
+       size 0.200000 0.950000;
+       offset 0.050000 0.000000;
+       advance 0.300000;
+};
+glyph 205
+{
+       texcoords 0.019531 0.222656 0.027344 0.296875;
+       size 0.200000 0.950000;
+       offset 0.100000 0.000000;
+       advance 0.300000;
+};
+glyph 206
+{
+       texcoords 0.066406 0.222656 0.078125 0.296875;
+       size 0.300000 0.950000;
+       offset 0.000000 0.000000;
+       advance 0.300000;
+};
+glyph 207
+{
+       texcoords 0.115234 0.222656 0.126953 0.296875;
+       size 0.300000 0.950000;
+       offset 0.000000 0.000000;
+       advance 0.300000;
+};
+glyph 208
+{
+       texcoords 0.156250 0.222656 0.183594 0.281250;
+       size 0.700000 0.750000;
+       offset 0.000000 0.000000;
+       advance 0.750000;
+};
+glyph 209
+{
+       texcoords 0.208984 0.222656 0.230469 0.292969;
+       size 0.550000 0.900000;
+       offset 0.100000 0.000000;
+       advance 0.750000;
+};
+glyph 210
+{
+       texcoords 0.253906 0.222656 0.281250 0.296875;
+       size 0.700000 0.950000;
+       offset 0.050000 0.000000;
+       advance 0.800000;
+};
+glyph 211
+{
+       texcoords 0.302734 0.222656 0.330078 0.296875;
+       size 0.700000 0.950000;
+       offset 0.050000 0.000000;
+       advance 0.800000;
+};
+glyph 212
+{
+       texcoords 0.351562 0.222656 0.378906 0.296875;
+       size 0.700000 0.950000;
+       offset 0.050000 0.000000;
+       advance 0.800000;
+};
+glyph 213
+{
+       texcoords 0.400391 0.222656 0.427734 0.292969;
+       size 0.700000 0.900000;
+       offset 0.050000 0.000000;
+       advance 0.800000;
+};
+glyph 214
+{
+       texcoords 0.449219 0.222656 0.476562 0.296875;
+       size 0.700000 0.950000;
+       offset 0.050000 0.000000;
+       advance 0.800000;
+};
+glyph 215
+{
+       texcoords 0.501953 0.226562 0.523438 0.269531;
+       size 0.550000 0.550000;
+       offset 0.150000 0.050000;
+       advance 0.850000;
+};
+glyph 216
+{
+       texcoords 0.544922 0.218750 0.576172 0.285156;
+       size 0.800000 0.850000;
+       offset 0.000000 -0.050000;
+       advance 0.800000;
+};
+glyph 217
+{
+       texcoords 0.599609 0.222656 0.621094 0.296875;
+       size 0.550000 0.950000;
+       offset 0.100000 0.000000;
+       advance 0.750000;
+};
+glyph 218
+{
+       texcoords 0.648438 0.222656 0.669922 0.296875;
+       size 0.550000 0.950000;
+       offset 0.100000 0.000000;
+       advance 0.750000;
+};
+glyph 219
+{
+       texcoords 0.697266 0.222656 0.718750 0.296875;
+       size 0.550000 0.950000;
+       offset 0.100000 0.000000;
+       advance 0.750000;
+};
+glyph 220
+{
+       texcoords 0.746094 0.222656 0.767578 0.296875;
+       size 0.550000 0.950000;
+       offset 0.100000 0.000000;
+       advance 0.750000;
+};
+glyph 221
+{
+       texcoords 0.792969 0.222656 0.816406 0.296875;
+       size 0.600000 0.950000;
+       offset 0.000000 0.000000;
+       advance 0.600000;
+};
+glyph 222
+{
+       texcoords 0.845703 0.222656 0.863281 0.281250;
+       size 0.450000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.600000;
+};
+glyph 223
+{
+       texcoords 0.892578 0.222656 0.912109 0.281250;
+       size 0.500000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 224
+{
+       texcoords 0.943359 0.222656 0.960938 0.285156;
+       size 0.450000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 225
+{
+       texcoords 0.015625 0.125000 0.033203 0.187500;
+       size 0.450000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 226
+{
+       texcoords 0.064453 0.125000 0.082031 0.187500;
+       size 0.450000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 227
+{
+       texcoords 0.113281 0.125000 0.130859 0.187500;
+       size 0.450000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 228
+{
+       texcoords 0.162109 0.125000 0.179688 0.183594;
+       size 0.450000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 229
+{
+       texcoords 0.210938 0.125000 0.228516 0.191406;
+       size 0.450000 0.850000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 230
+{
+       texcoords 0.251953 0.125000 0.285156 0.167969;
+       size 0.850000 0.550000;
+       offset 0.050000 0.000000;
+       advance 0.950000;
+};
+glyph 231
+{
+       texcoords 0.308594 0.109375 0.326172 0.167969;
+       size 0.450000 0.750000;
+       offset 0.050000 -0.200000;
+       advance 0.550000;
+};
+glyph 232
+{
+       texcoords 0.355469 0.125000 0.375000 0.187500;
+       size 0.500000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 233
+{
+       texcoords 0.404297 0.125000 0.423828 0.187500;
+       size 0.500000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 234
+{
+       texcoords 0.453125 0.125000 0.472656 0.187500;
+       size 0.500000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 235
+{
+       texcoords 0.501953 0.125000 0.521484 0.183594;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 236
+{
+       texcoords 0.556641 0.125000 0.566406 0.187500;
+       size 0.250000 0.800000;
+       offset -0.050000 0.000000;
+       advance 0.300000;
+};
+glyph 237
+{
+       texcoords 0.605469 0.125000 0.615234 0.187500;
+       size 0.250000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.300000;
+};
+glyph 238
+{
+       texcoords 0.652344 0.125000 0.664062 0.187500;
+       size 0.300000 0.800000;
+       offset 0.000000 0.000000;
+       advance 0.300000;
+};
+glyph 239
+{
+       texcoords 0.701172 0.125000 0.712891 0.183594;
+       size 0.300000 0.750000;
+       offset 0.000000 0.000000;
+       advance 0.300000;
+};
+glyph 240
+{
+       texcoords 0.746094 0.125000 0.765625 0.183594;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 241
+{
+       texcoords 0.796875 0.125000 0.814453 0.187500;
+       size 0.450000 0.800000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 242
+{
+       texcoords 0.843750 0.125000 0.863281 0.187500;
+       size 0.500000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 243
+{
+       texcoords 0.892578 0.125000 0.912109 0.187500;
+       size 0.500000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 244
+{
+       texcoords 0.941406 0.125000 0.960938 0.187500;
+       size 0.500000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 245
+{
+       texcoords 0.013672 0.027344 0.033203 0.089844;
+       size 0.500000 0.800000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 246
+{
+       texcoords 0.062500 0.027344 0.082031 0.085938;
+       size 0.500000 0.750000;
+       offset 0.050000 0.000000;
+       advance 0.600000;
+};
+glyph 247
+{
+       texcoords 0.109375 0.031250 0.132812 0.070312;
+       size 0.600000 0.500000;
+       offset 0.100000 0.050000;
+       advance 0.850000;
+};
+glyph 248
+{
+       texcoords 0.158203 0.023438 0.181641 0.074219;
+       size 0.600000 0.650000;
+       offset 0.000000 -0.050000;
+       advance 0.600000;
+};
+glyph 249
+{
+       texcoords 0.210938 0.027344 0.228516 0.089844;
+       size 0.450000 0.800000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 250
+{
+       texcoords 0.259766 0.027344 0.277344 0.089844;
+       size 0.450000 0.800000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 251
+{
+       texcoords 0.308594 0.027344 0.326172 0.089844;
+       size 0.450000 0.800000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 252
+{
+       texcoords 0.357422 0.027344 0.375000 0.085938;
+       size 0.450000 0.750000;
+       offset 0.100000 0.000000;
+       advance 0.650000;
+};
+glyph 253
+{
+       texcoords 0.404297 0.011719 0.425781 0.089844;
+       size 0.550000 1.000000;
+       offset 0.000000 -0.200000;
+       advance 0.550000;
+};
+glyph 254
+{
+       texcoords 0.453125 0.011719 0.472656 0.085938;
+       size 0.500000 0.950000;
+       offset 0.100000 -0.200000;
+       advance 0.650000;
+};
+glyph 255
+{
+       texcoords 0.501953 0.011719 0.523438 0.085938;
+       size 0.550000 0.950000;
+       offset 0.000000 -0.200000;
+       advance 0.550000;
+};
diff --git a/dejavu-20.png b/dejavu-20.png
new file mode 100644 (file)
index 0000000..08251db
Binary files /dev/null and b/dejavu-20.png differ
diff --git a/digitalreadout-16.font b/digitalreadout-16.font
new file mode 100644 (file)
index 0000000..2828761
--- /dev/null
@@ -0,0 +1,669 @@
+texture "digitalreadout-16.png";
+default_size 16;
+ascent 0.812;
+descent 0.062;
+glyph 32
+{
+       texcoords 0.031250 0.898438 0.031250 0.898438;
+       size 0.000 0.000;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 33
+{
+       texcoords 0.085938 0.898438 0.101562 0.984375;
+       size 0.250 0.688;
+       offset 0.000 0.000;
+       advance 0.125;
+};
+glyph 34
+{
+       texcoords 0.148438 0.968750 0.160156 0.992188;
+       size 0.188 0.188;
+       offset 0.125 0.562;
+       advance 0.188;
+};
+glyph 35
+{
+       texcoords 0.199219 0.898438 0.234375 0.992188;
+       size 0.562 0.750;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 36
+{
+       texcoords 0.265625 0.882812 0.292969 1.000000;
+       size 0.438 0.938;
+       offset 0.062 -0.125;
+       advance 0.438;
+};
+glyph 37
+{
+       texcoords 0.324219 0.898438 0.359375 0.992188;
+       size 0.562 0.750;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 38
+{
+       texcoords 0.390625 0.898438 0.421875 0.984375;
+       size 0.500 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 39
+{
+       texcoords 0.464844 0.968750 0.472656 0.984375;
+       size 0.125 0.125;
+       offset 0.125 0.562;
+       advance 0.125;
+};
+glyph 40
+{
+       texcoords 0.519531 0.898438 0.542969 0.984375;
+       size 0.375 0.688;
+       offset 0.000 0.000;
+       advance 0.250;
+};
+glyph 41
+{
+       texcoords 0.582031 0.898438 0.605469 0.984375;
+       size 0.375 0.688;
+       offset 0.000 0.000;
+       advance 0.250;
+};
+glyph 42
+{
+       texcoords 0.640625 0.906250 0.667969 0.976562;
+       size 0.438 0.562;
+       offset 0.062 0.062;
+       advance 0.438;
+};
+glyph 43
+{
+       texcoords 0.707031 0.906250 0.726562 0.976562;
+       size 0.312 0.562;
+       offset 0.125 0.062;
+       advance 0.438;
+};
+glyph 44
+{
+       texcoords 0.777344 0.890625 0.785156 0.906250;
+       size 0.125 0.125;
+       offset 0.000 -0.062;
+       advance 0.125;
+};
+glyph 45
+{
+       texcoords 0.832031 0.937500 0.851562 0.945312;
+       size 0.312 0.062;
+       offset 0.125 0.312;
+       advance 0.438;
+};
+glyph 46
+{
+       texcoords 0.902344 0.898438 0.910156 0.914062;
+       size 0.125 0.125;
+       offset 0.000 0.000;
+       advance 0.125;
+};
+glyph 47
+{
+       texcoords 0.953125 0.906250 0.980469 0.976562;
+       size 0.438 0.562;
+       offset 0.062 0.062;
+       advance 0.438;
+};
+glyph 48
+{
+       texcoords 0.011719 0.773438 0.046875 0.859375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 49
+{
+       texcoords 0.085938 0.781250 0.101562 0.859375;
+       size 0.250 0.625;
+       offset 0.312 0.062;
+       advance 0.438;
+};
+glyph 50
+{
+       texcoords 0.136719 0.773438 0.171875 0.859375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 51
+{
+       texcoords 0.203125 0.773438 0.234375 0.859375;
+       size 0.500 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 52
+{
+       texcoords 0.265625 0.781250 0.296875 0.859375;
+       size 0.500 0.625;
+       offset 0.062 0.062;
+       advance 0.438;
+};
+glyph 53
+{
+       texcoords 0.328125 0.773438 0.355469 0.859375;
+       size 0.438 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 54
+{
+       texcoords 0.390625 0.773438 0.421875 0.859375;
+       size 0.500 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 55
+{
+       texcoords 0.457031 0.781250 0.480469 0.859375;
+       size 0.375 0.625;
+       offset 0.188 0.062;
+       advance 0.438;
+};
+glyph 56
+{
+       texcoords 0.511719 0.773438 0.546875 0.859375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 57
+{
+       texcoords 0.578125 0.773438 0.609375 0.859375;
+       size 0.500 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 58
+{
+       texcoords 0.632812 0.773438 0.675781 0.812500;
+       size 0.688 0.312;
+       offset -0.250 0.000;
+       advance 0.125;
+};
+glyph 59
+{
+       texcoords 0.695312 0.765625 0.738281 0.812500;
+       size 0.688 0.375;
+       offset -0.250 -0.062;
+       advance 0.125;
+};
+glyph 60
+{
+       texcoords 0.773438 0.781250 0.789062 0.851562;
+       size 0.250 0.562;
+       offset 0.250 0.062;
+       advance 0.438;
+};
+glyph 61
+{
+       texcoords 0.828125 0.804688 0.855469 0.828125;
+       size 0.438 0.188;
+       offset 0.062 0.250;
+       advance 0.438;
+};
+glyph 62
+{
+       texcoords 0.898438 0.781250 0.914062 0.851562;
+       size 0.250 0.562;
+       offset 0.250 0.062;
+       advance 0.438;
+};
+glyph 63
+{
+       texcoords 0.949219 0.773438 0.984375 0.859375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 64
+{
+       texcoords 0.011719 0.648438 0.046875 0.742188;
+       size 0.562 0.750;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 65
+{
+       texcoords 0.074219 0.656250 0.109375 0.734375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 66
+{
+       texcoords 0.140625 0.648438 0.171875 0.734375;
+       size 0.500 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 67
+{
+       texcoords 0.203125 0.648438 0.234375 0.734375;
+       size 0.500 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 68
+{
+       texcoords 0.265625 0.648438 0.296875 0.734375;
+       size 0.500 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 69
+{
+       texcoords 0.328125 0.648438 0.359375 0.734375;
+       size 0.500 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 70
+{
+       texcoords 0.390625 0.656250 0.421875 0.734375;
+       size 0.500 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 71
+{
+       texcoords 0.453125 0.648438 0.484375 0.734375;
+       size 0.500 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 72
+{
+       texcoords 0.511719 0.656250 0.546875 0.734375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 73
+{
+       texcoords 0.585938 0.656250 0.597656 0.726562;
+       size 0.188 0.562;
+       offset 0.188 0.062;
+       advance 0.438;
+};
+glyph 74
+{
+       texcoords 0.636719 0.648438 0.671875 0.734375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 75
+{
+       texcoords 0.703125 0.648438 0.734375 0.734375;
+       size 0.500 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 76
+{
+       texcoords 0.769531 0.648438 0.792969 0.734375;
+       size 0.375 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 77
+{
+       texcoords 0.824219 0.656250 0.859375 0.734375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 78
+{
+       texcoords 0.886719 0.656250 0.921875 0.734375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 79
+{
+       texcoords 0.949219 0.648438 0.984375 0.734375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 80
+{
+       texcoords 0.011719 0.531250 0.046875 0.609375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 81
+{
+       texcoords 0.074219 0.523438 0.109375 0.609375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 82
+{
+       texcoords 0.136719 0.531250 0.171875 0.609375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 83
+{
+       texcoords 0.203125 0.523438 0.230469 0.609375;
+       size 0.438 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 84
+{
+       texcoords 0.269531 0.531250 0.289062 0.609375;
+       size 0.312 0.625;
+       offset 0.188 0.062;
+       advance 0.438;
+};
+glyph 85
+{
+       texcoords 0.324219 0.523438 0.359375 0.609375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 86
+{
+       texcoords 0.390625 0.531250 0.417969 0.609375;
+       size 0.438 0.625;
+       offset 0.125 0.062;
+       advance 0.438;
+};
+glyph 87
+{
+       texcoords 0.449219 0.531250 0.484375 0.609375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 88
+{
+       texcoords 0.515625 0.531250 0.542969 0.601562;
+       size 0.438 0.562;
+       offset 0.062 0.062;
+       advance 0.438;
+};
+glyph 89
+{
+       texcoords 0.578125 0.531250 0.609375 0.609375;
+       size 0.500 0.625;
+       offset 0.062 0.062;
+       advance 0.438;
+};
+glyph 90
+{
+       texcoords 0.640625 0.523438 0.667969 0.609375;
+       size 0.438 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 91
+{
+       texcoords 0.707031 0.523438 0.730469 0.609375;
+       size 0.375 0.688;
+       offset 0.000 0.000;
+       advance 0.250;
+};
+glyph 92
+{
+       texcoords 0.769531 0.531250 0.789062 0.601562;
+       size 0.312 0.562;
+       offset 0.125 0.062;
+       advance 0.438;
+};
+glyph 93
+{
+       texcoords 0.832031 0.523438 0.855469 0.609375;
+       size 0.375 0.688;
+       offset 0.000 0.000;
+       advance 0.250;
+};
+glyph 94
+{
+       texcoords 0.894531 0.562500 0.914062 0.601562;
+       size 0.312 0.312;
+       offset 0.125 0.312;
+       advance 0.438;
+};
+glyph 95
+{
+       texcoords 0.949219 0.523438 0.984375 0.617188;
+       size 0.562 0.750;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 96
+{
+       texcoords 0.011719 0.398438 0.046875 0.492188;
+       size 0.562 0.750;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 97
+{
+       texcoords 0.074219 0.406250 0.109375 0.484375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 98
+{
+       texcoords 0.140625 0.398438 0.171875 0.484375;
+       size 0.500 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 99
+{
+       texcoords 0.203125 0.398438 0.234375 0.484375;
+       size 0.500 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 100
+{
+       texcoords 0.265625 0.398438 0.296875 0.484375;
+       size 0.500 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 101
+{
+       texcoords 0.328125 0.398438 0.359375 0.484375;
+       size 0.500 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 102
+{
+       texcoords 0.390625 0.406250 0.421875 0.484375;
+       size 0.500 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 103
+{
+       texcoords 0.453125 0.398438 0.484375 0.484375;
+       size 0.500 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 104
+{
+       texcoords 0.511719 0.406250 0.546875 0.484375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 105
+{
+       texcoords 0.585938 0.406250 0.597656 0.476562;
+       size 0.188 0.562;
+       offset 0.188 0.062;
+       advance 0.438;
+};
+glyph 106
+{
+       texcoords 0.636719 0.398438 0.671875 0.484375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 107
+{
+       texcoords 0.703125 0.398438 0.734375 0.484375;
+       size 0.500 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 108
+{
+       texcoords 0.769531 0.398438 0.792969 0.484375;
+       size 0.375 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 109
+{
+       texcoords 0.824219 0.406250 0.859375 0.484375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 110
+{
+       texcoords 0.886719 0.406250 0.921875 0.484375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 111
+{
+       texcoords 0.949219 0.398438 0.984375 0.484375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 112
+{
+       texcoords 0.011719 0.281250 0.046875 0.359375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 113
+{
+       texcoords 0.074219 0.273438 0.109375 0.359375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 114
+{
+       texcoords 0.136719 0.281250 0.171875 0.359375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 115
+{
+       texcoords 0.203125 0.273438 0.230469 0.359375;
+       size 0.438 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 116
+{
+       texcoords 0.269531 0.281250 0.289062 0.359375;
+       size 0.312 0.625;
+       offset 0.188 0.062;
+       advance 0.438;
+};
+glyph 117
+{
+       texcoords 0.324219 0.273438 0.359375 0.359375;
+       size 0.562 0.688;
+       offset 0.000 0.000;
+       advance 0.438;
+};
+glyph 118
+{
+       texcoords 0.390625 0.281250 0.417969 0.359375;
+       size 0.438 0.625;
+       offset 0.125 0.062;
+       advance 0.438;
+};
+glyph 119
+{
+       texcoords 0.449219 0.281250 0.484375 0.359375;
+       size 0.562 0.625;
+       offset 0.000 0.062;
+       advance 0.438;
+};
+glyph 120
+{
+       texcoords 0.515625 0.281250 0.542969 0.351562;
+       size 0.438 0.562;
+       offset 0.062 0.062;
+       advance 0.438;
+};
+glyph 121
+{
+       texcoords 0.578125 0.281250 0.609375 0.359375;
+       size 0.500 0.625;
+       offset 0.062 0.062;
+       advance 0.438;
+};
+glyph 122
+{
+       texcoords 0.640625 0.273438 0.667969 0.359375;
+       size 0.438 0.688;
+       offset 0.062 0.000;
+       advance 0.438;
+};
+glyph 123
+{
+       texcoords 0.707031 0.273438 0.730469 0.359375;
+       size 0.375 0.688;
+       offset 0.000 0.000;
+       advance 0.250;
+};
+glyph 124
+{
+       texcoords 0.773438 0.281250 0.789062 0.351562;
+       size 0.250 0.562;
+       offset 0.000 0.062;
+       advance 0.125;
+};
+glyph 125
+{
+       texcoords 0.832031 0.273438 0.855469 0.359375;
+       size 0.375 0.688;
+       offset 0.000 0.000;
+       advance 0.250;
+};
+glyph 126
+{
+       texcoords 0.886719 0.273438 0.921875 0.367188;
+       size 0.562 0.750;
+       offset 0.000 0.000;
+       advance 0.438;
+};
diff --git a/digitalreadout-16.png b/digitalreadout-16.png
new file mode 100644 (file)
index 0000000..e9f7684
Binary files /dev/null and b/digitalreadout-16.png differ
diff --git a/engineer.res b/engineer.res
new file mode 100644 (file)
index 0000000..191663f
--- /dev/null
@@ -0,0 +1,226 @@
+font "dejavu-12.font";
+font "digitalreadout-16.font";
+
+texture "gui.png";
+
+graphic "button"
+{
+       texture "gui";
+       slice 8 112 21 10;
+       border { top 2; right 3; bottom 4; left 2; };
+       shadow { top 0; right 1; bottom 2; left 0; };
+};
+
+graphic "buttondown"
+{
+       texture "gui";
+       slice 38 112 21 10;
+       border { top 2; right 3; bottom 4; left 2; };
+       shadow { top 0; right 1; bottom 2; left 0; };
+};
+
+graphic "greenbutton"
+{
+       texture "gui";
+       slice 8 97 21 10;
+       border { top 2; right 3; bottom 4; left 2; };
+       shadow { top 0; right 1; bottom 2; left 0; };
+};
+
+graphic "greenbuttondown"
+{
+       texture "gui";
+       slice 38 97 21 10;
+       border { top 2; right 3; bottom 4; left 2; };
+       shadow { top 0; right 1; bottom 2; left 0; };
+};
+
+graphic "redbutton"
+{
+       texture "gui";
+       slice 8 82 21 10;
+       border { top 2; right 3; bottom 4; left 2; };
+       shadow { top 0; right 1; bottom 2; left 0; };
+};
+
+graphic "redbuttondown"
+{
+       texture "gui";
+       slice 38 82 21 10;
+       border { top 2; right 3; bottom 4; left 2; };
+       shadow { top 0; right 1; bottom 2; left 0; };
+};
+
+graphic "panel"
+{
+       texture "gui";
+       slice 8 158 30 20;
+       border { top 2; right 2; bottom 2; left 2; };
+};
+
+graphic "greenlamp"
+{
+       texture "gui";
+       slice 87 222 12 12;
+};
+
+graphic "greenlamplit"
+{
+       texture "gui";
+       slice 87 236 12 12;
+};
+
+graphic "redlamp"
+{
+       texture "gui";
+       slice 101 222 12 12;
+};
+
+graphic "redlamplit"
+{
+       texture "gui";
+       slice 101 236 12 12;
+};
+
+graphic "digidisplay"
+{
+       texture "gui";
+       slice 14 226 48 24;
+       border { top 5; right 5; bottom 5; left 5; };
+};
+
+graphic "hsliderslot"
+{
+       texture "gui";
+       slice 8 188 60 10;
+       border { top 0; right 5; bottom 0; left 5; };
+};
+
+graphic "hslider"
+{
+       texture "gui";
+       slice 73 105 32 13;
+       shadow { top 0; right 2; bottom 3; left 0; };
+};
+
+style "button" ""
+{
+       font_color 0 0 0;
+
+       part "button"
+       {
+               graphic normal "button";
+               graphic active "buttondown";
+               align 0 -1;
+               fill false false;
+       };
+
+       part "text"
+       {
+               align 0 1;
+       };
+};
+
+style "button" "green"
+{
+       font_color 0 0 0;
+
+       part "button"
+       {
+               graphic normal "greenbutton";
+               graphic active "greenbuttondown";
+               align 0 -1;
+               fill false false;
+       };
+
+       part "text"
+       {
+               align 0 1;
+       };
+};
+
+style "button" "red"
+{
+       font_color 0 0 0;
+
+       part "button"
+       {
+               graphic normal "redbutton";
+               graphic active "redbuttondown";
+               align 0 -1;
+               fill false false;
+       };
+
+       part "text"
+       {
+               align 0 1;
+       };
+};
+
+style "panel" ""
+{
+       part "background"
+       {
+               graphic normal "panel";
+       };
+       part "children";
+};
+
+style "indicator" "";
+
+style "indicator" "green"
+{
+       part "lamp"
+       {
+               graphic normal "greenlamp";
+               graphic active "greenlamplit";
+               fill false false;
+       };
+};
+
+style "indicator" "red"
+{
+       part "lamp"
+       {
+               graphic normal "redlamp";
+               graphic active "redlamplit";
+               fill false false;
+       };
+};
+
+style "label" ""
+{
+       font_color 0 0 0;
+       part "text";
+};
+
+style "label" "digital"
+{
+       font "digitalreadout-16";
+       font_color 0.3 1 0.3;
+
+       part "background"
+       {
+               graphic normal "digidisplay";
+       };
+
+       part "text"
+       {
+               fill false false;
+               align 0 0;
+       };
+};
+
+style "hslider" ""
+{
+       part "background"
+       {
+               graphic normal "hsliderslot";
+               fill true false;
+       };
+
+       part "slider"
+       {
+               graphic normal "hslider";
+       };
+};
diff --git a/gui.blend b/gui.blend
new file mode 100644 (file)
index 0000000..f53efda
Binary files /dev/null and b/gui.blend differ
diff --git a/gui.png b/gui.png
new file mode 100644 (file)
index 0000000..3457c02
Binary files /dev/null and b/gui.png differ
diff --git a/source/3d/layout.cpp b/source/3d/layout.cpp
new file mode 100644 (file)
index 0000000..b90f553
--- /dev/null
@@ -0,0 +1,132 @@
+#include <algorithm>
+#include <fstream>
+#include <msp/gl/rendermode.h>
+#include <msp/gl/select.h>
+#include <msp/gl/texture.h>
+#include <msp/parser/parser.h>
+#include "layout.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace Marklin {
+
+Layout3D::Layout3D(Layout &l):
+       layout(l),
+       quality(4)
+{
+       layout.signal_track_added.connect(sigc::mem_fun(this, &Layout3D::track_added));
+       layout.signal_track_removed.connect(sigc::mem_fun(this, &Layout3D::track_removed));
+}
+
+Layout3D::~Layout3D()
+{
+       for(Track3DSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               delete *i;
+}
+
+void Layout3D::set_quality(unsigned q)
+{
+       quality=q;
+       for(Track3DSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               (*i)->set_quality(quality);
+}
+
+void Layout3D::render(bool endpoints)
+{
+       GL::Texture::unbind();
+       glEnable(GL_DEPTH_TEST);
+
+       for(Track3DSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               (*i)->render();
+
+       if(endpoints)
+       {
+               glDepthMask(false);
+               for(Track3DSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+                       (*i)->render_endpoints();
+               glDepthMask(true);
+       }
+}
+
+Track3D *Layout3D::get_track(const Track *t)
+{
+       for(Track3DSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               if(&(*i)->get_track()==t)
+                       return *i;
+
+       return 0;
+}
+
+Track3D *Layout3D::pick_track(float x, float y, float size)
+{
+       vector<GL::SelectRecord> select_buf;
+       GL::select_buffer(select_buf);
+       GL::render_mode(GL::SELECT);
+
+       glPushMatrix();
+       glLoadIdentity();
+
+       double clip[4];
+       clip[0]=1;
+       clip[1]=0;
+       clip[2]=x-size;
+       clip[3]=0;
+       glClipPlane(GL_CLIP_PLANE0, clip);
+       glEnable(GL_CLIP_PLANE0);
+
+       clip[0]=-1;
+       clip[2]=-(x+size);
+       glClipPlane(GL_CLIP_PLANE1, clip);
+       glEnable(GL_CLIP_PLANE1);
+
+       clip[0]=0;
+       clip[1]=1;
+       clip[2]=y-size;
+       glClipPlane(GL_CLIP_PLANE2, clip);
+       glEnable(GL_CLIP_PLANE2);
+
+       clip[1]=-1;
+       clip[2]=-(y+size);
+       glClipPlane(GL_CLIP_PLANE3, clip);
+       glEnable(GL_CLIP_PLANE3);
+
+       glPopMatrix();
+
+       render();
+
+       glDisable(GL_CLIP_PLANE0);
+       glDisable(GL_CLIP_PLANE1);
+       glDisable(GL_CLIP_PLANE2);
+       glDisable(GL_CLIP_PLANE3);
+
+       GL::render_mode(GL::RENDER);
+       Track3D *track=0;
+       unsigned track_depth=numeric_limits<unsigned>::max();
+       for(vector<GL::SelectRecord>::iterator i=select_buf.begin(); i!=select_buf.end(); ++i)
+               if(i->min_depth<track_depth)
+               {
+                       track=reinterpret_cast<Track3D *>(i->names.back());
+                       track_depth=i->min_depth;
+               }
+
+       return track;
+}
+
+void Layout3D::track_added(Track *t)
+{
+       tracks.push_back(new Track3D(*t, quality));
+}
+
+void Layout3D::track_removed(Track *t)
+{
+       for(Track3DSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               if(&(*i)->get_track()==t)
+               {
+                       delete *i;
+                       tracks.erase(i);
+                       return;
+               }
+}
+
+} // namespace Marklin
diff --git a/source/3d/layout.h b/source/3d/layout.h
new file mode 100644 (file)
index 0000000..09189ed
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef MARKLIN3D_LAYOUT_H_
+#define MARKLIN3D_LAYOUT_H_
+
+#include "libmarklin/layout.h"
+#include "track.h"
+
+namespace Marklin {
+
+class Layout3D
+{
+public:
+       Layout3D(Layout &);
+       ~Layout3D();
+
+       void set_quality(unsigned);
+       const Track3DSeq &get_tracks() const { return tracks; }
+       void  render(bool =false);
+       Track3D *get_track(const Track *);
+       Track3D *pick_track(float, float, float);
+private:
+       Layout     &layout;
+       Track3DSeq tracks;
+       unsigned   quality;
+
+       void track_added(Track *);
+       void track_removed(Track *);
+};
+
+} // namespace Marklin
+
+#endif
+
diff --git a/source/3d/misc.h b/source/3d/misc.h
new file mode 100644 (file)
index 0000000..8d77ee2
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef MARKLIN3D_MISC_H_
+#define MARKLIN3D_MISC_H_
+
+namespace Marklin {
+
+struct Color
+{
+       float r, g, b;
+
+       Color(float r_, float g_, float b_): r(r_), g(g_), b(b_) { }
+};
+
+} // namespace Marklin
+
+#endif
diff --git a/source/3d/track.cpp b/source/3d/track.cpp
new file mode 100644 (file)
index 0000000..e64f42e
--- /dev/null
@@ -0,0 +1,253 @@
+#include <cmath>
+#include <GL/gl.h>
+#include <msp/gl/misc.h>
+#include "track.h"
+
+using namespace std;
+using namespace Msp;
+
+#include <iostream>
+
+namespace Marklin {
+
+Track3D::Track3D(Track &t, unsigned q):
+       track(t),
+       color(1, 1, 1),
+       varray((GL::NORMAL3, GL::VERTEX3)),
+       quality(q)
+{
+       build_object();
+}
+
+void Track3D::set_quality(unsigned q)
+{
+       quality=q;
+       build_object();
+}
+
+void Track3D::get_bounds(float angle, Point &minp, Point &maxp) const
+{
+       const Point &pos=track.get_position();
+       float rot=track.get_rotation();
+
+       float c=cos(-angle);
+       float s=sin(-angle);
+
+       minp.x=maxp.x=c*pos.x-s*pos.y;
+       minp.y=maxp.y=s*pos.x+c*pos.y;
+
+       float c2=cos(rot-angle);
+       float s2=sin(rot-angle);
+
+       for(vector<Point>::const_iterator i=border.begin(); i!=border.end(); ++i)
+       {
+               float x=c*pos.x-s*pos.y + c2*i->x-s2*i->y;
+               float y=s*pos.x+c*pos.y + s2*i->x+c2*i->y;
+
+               minp.x=min(minp.x, x);
+               minp.y=min(minp.y, y);
+               maxp.x=max(maxp.x, x);
+               maxp.y=max(maxp.y, y);
+       }
+}
+
+void Track3D::render()
+{
+       prepare_render();
+
+       glPushName((unsigned)this);
+
+       varray.apply();
+       glColor4f(0.25*color.r, 0.25*color.g, 0.25*color.b, 1);
+       glDrawElements(GL_QUADS, base_seq.size(), GL_UNSIGNED_INT, &base_seq[0]);
+       if(quality>1)
+       {
+               glColor4f(0.85*color.r, 0.85*color.g, 0.85*color.b, 1);
+               glDrawElements(GL_QUADS, rail_seq.size(), GL_UNSIGNED_INT, &rail_seq[0]);
+       }
+
+       glPopName();
+       glPopMatrix();
+}
+
+void Track3D::render_endpoints()
+{
+       prepare_render();
+
+       const Point &pos=track.get_position();
+       const Track::EndpointSeq &endpoints=track.get_endpoints();
+       for(Track::EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+       {
+               GL::set(GL_CULL_FACE, i->link);
+               if(i->link)
+                       glColor4f(0.5, 0, 1, 0.5);
+               else
+                       glColor4f(1, 0, 0.5, 0.5);
+
+               float c=cos(i->rot);
+               float s=sin(i->rot);
+
+               glBegin(GL_QUADS);
+               glVertex3f(i->pos.x-s*0.025, i->pos.y+c*0.025, 0);
+               glVertex3f(i->pos.x+s*0.025, i->pos.y-c*0.025, 0);
+               glVertex3f(i->pos.x+s*0.025, i->pos.y-c*0.025, pos.z+i->pos.z+0.02);
+               glVertex3f(i->pos.x-s*0.025, i->pos.y+c*0.025, pos.z+i->pos.z+0.02);
+               glEnd();
+       }
+
+       glPopMatrix();
+}
+
+void Track3D::render_route(int route)
+{
+       prepare_render();
+
+       varray.apply();
+       if(route>=0 && static_cast<unsigned>(route)<route_seq.size())
+               glDrawElements(GL_QUADS, route_seq[route].size(), GL_UNSIGNED_INT, &route_seq[route][0]);
+       else
+       {
+               for(unsigned i=0; i<route_seq.size(); ++i)
+                       glDrawElements(GL_QUADS, route_seq[i].size(), GL_UNSIGNED_INT, &route_seq[i][0]);
+       }
+
+       glPopMatrix();
+}
+
+void Track3D::prepare_render()
+{
+       const Point &pos=track.get_position();
+       float rot=track.get_rotation();
+
+       glPushMatrix();
+       glTranslatef(pos.x, pos.y, pos.z);
+       glRotatef(rot*180/M_PI, 0, 0, 1);
+}
+
+void Track3D::build_object()
+{
+       varray.clear();
+       RefPtr<GL::VertexArrayBuilder> builder=varray.modify();
+
+       base_seq.clear();
+       rail_seq.clear();
+       route_seq.clear();
+       route_seq.resize(track.get_n_routes());
+
+       const Track::PartSeq &parts=track.get_parts();
+       unsigned index=0;
+       for(Track::PartSeq::const_iterator i=parts.begin(); i!=parts.end(); ++i)
+               build_part(*i, *builder, index);
+}
+
+void Track3D::build_part(const Track::Part &part, GL::VertexArrayBuilder &va_builder, unsigned &base_index)
+{
+       static vector<Point> profile;
+       if(profile.empty())
+       {
+               profile.push_back(Point(0, -0.02, 0));
+               profile.push_back(Point(0, -0.014, 0.008));
+               profile.push_back(Point(0, -0.014, 0.008));
+               profile.push_back(Point(0, 0.014, 0.008));
+               profile.push_back(Point(0, 0.014, 0.008));
+               profile.push_back(Point(0, 0.02, 0));
+               for(unsigned i=0; i<2; ++i)
+               {
+                       profile.push_back(Point(0, -0.009+i*0.017, 0.008));
+                       profile.push_back(Point(0, -0.009+i*0.017, 0.0103));
+                       profile.push_back(Point(0, -0.009+i*0.017, 0.0103));
+                       profile.push_back(Point(0, -0.008+i*0.017, 0.0103));
+                       profile.push_back(Point(0, -0.008+i*0.017, 0.0103));
+                       profile.push_back(Point(0, -0.008+i*0.017, 0.008));
+               }
+               profile.push_back(Point(0, -0.002, 0.012));
+               profile.push_back(Point(0, 0.002, 0.012));
+       }
+       static unsigned psize=profile.size();
+
+       const float &radius=part.radius;
+       const float &x=part.x;
+       const float &y=part.y;
+       const float &length=part.length;
+       const float &dir=part.dir;
+
+       unsigned nsegs;
+       if(radius)
+       {
+               nsegs=(unsigned)(part.length*(1<<quality))+1;
+               Point center(x-sin(dir)*radius, y+cos(dir)*radius, 0);
+               float r=fabs(radius);
+               float start=((radius<0)?M_PI:0)+dir;
+               float angle=(radius<0)?-length:length;
+               int inv=(radius<0)?-1:1;
+               for(unsigned i=0; i<=nsegs; ++i)
+               {
+                       float a=start+i*angle/nsegs;
+                       float c=cos(a);
+                       float s=sin(a);
+
+                       for(unsigned j=0; j<profile.size(); ++j)
+                       {
+                               unsigned k=j&~1;
+                               float dy=profile[k+1].y-profile[k].y;
+                               float dz=profile[k+1].z-profile[k].z;
+                               float d=sqrt(dy*dy+dz*dz);
+                               va_builder.normal(s*dz/d, -c*dz/d, dy/d);
+
+                               Point p(center.x+s*(r-profile[j].y*inv), center.y-c*(r-profile[j].y*inv), profile[j].z+i*track.get_slope()/nsegs);
+                               va_builder.vertex(p.x, p.y, p.z);
+                               if(profile[j].z==0)
+                                       border.push_back(p);
+                       }
+               }
+       }
+       else
+       {
+               nsegs=1;
+               float c=cos(dir);
+               float s=sin(dir);
+               for(unsigned i=0; i<2; ++i)
+               {
+                       for(unsigned j=0; j<profile.size(); ++j)
+                       {
+                               unsigned k=j&~1;
+                               float dy=profile[k+1].y-profile[k].y;
+                               float dz=profile[k+1].z-profile[k].z;
+                               float d=sqrt(dy*dy+dz*dz);
+                               va_builder.normal(s*dz/d, -c*dz/d, dy/d);
+
+                               float len=(part.dead_end && i==1 && j>=6) ? length/2 : length;
+                               Point p(x+c*len*i-s*profile[j].y, y+s*len*i+c*profile[j].y, profile[j].z+i*track.get_slope());
+                               va_builder.vertex(p.x, p.y, p.z);
+                               if(profile[j].z==0)
+                                       border.push_back(p);
+                       }
+               }
+       }
+
+       for(unsigned i=0; i<nsegs; ++i)
+       {
+               for(unsigned j=0; j<3; ++j)
+               {
+                       base_seq.push_back(base_index+i*psize+j*2);
+                       base_seq.push_back(base_index+(i+1)*psize+j*2);
+                       base_seq.push_back(base_index+(i+1)*psize+1+j*2);
+                       base_seq.push_back(base_index+i*psize+1+j*2);
+               }
+               for(unsigned j=3; j<9; ++j)
+               {
+                       rail_seq.push_back(base_index+i*psize+j*2);
+                       rail_seq.push_back(base_index+(i+1)*psize+j*2);
+                       rail_seq.push_back(base_index+(i+1)*psize+1+j*2);
+                       rail_seq.push_back(base_index+i*psize+1+j*2);
+               }
+               route_seq[part.route].push_back(base_index+i*psize+18);
+               route_seq[part.route].push_back(base_index+(i+1)*psize+18);
+               route_seq[part.route].push_back(base_index+(i+1)*psize+19);
+               route_seq[part.route].push_back(base_index+i*psize+19);
+       }
+
+       base_index+=(nsegs+1)*psize;
+}
+
+} // namespace Marklin
diff --git a/source/3d/track.h b/source/3d/track.h
new file mode 100644 (file)
index 0000000..f38593e
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef MARKLIN3D_TRACK_H_
+#define MARKLIN3D_TRACK_H_
+
+#include <list>
+#include <msp/gl/vertexarray.h>
+#include "libmarklin/track.h"
+#include "misc.h"
+
+namespace Marklin {
+
+class Track3D
+{
+public:
+       Track3D(Track &, unsigned);
+       void set_color(const Color &c)  { color=c; }
+       void set_quality(unsigned);
+       void get_bounds(float, Point &, Point &) const;
+       Track &get_track() const { return track; }
+       void render();
+       void render_endpoints();
+       void render_route(int);
+private:
+       Track        &track;
+       Color        color;
+       std::vector<Point> border;
+       Msp::GL::VertexArray varray;
+       std::vector<unsigned> base_seq;
+       std::vector<unsigned> rail_seq;
+       std::vector<std::vector<unsigned> > route_seq;
+       unsigned quality;
+
+       void prepare_render();
+       void build_object();
+       void build_part(const Track::Part &, Msp::GL::VertexArrayBuilder &, unsigned &);
+};
+typedef std::list<Track3D *> Track3DSeq;
+
+} // namespace Marklin
+
+#endif
diff --git a/source/designer/designer.cpp b/source/designer/designer.cpp
new file mode 100644 (file)
index 0000000..eb5a0ef
--- /dev/null
@@ -0,0 +1,667 @@
+#include <signal.h>
+#include <cmath>
+#include <iostream>
+#include <GL/gl.h>
+#include <SDL.h>
+#include <msp/gl/rendermode.h>
+#include <msp/gl/select.h>
+#include <msp/strings/codec.h>
+#include <msp/strings/lexicalcast.h>
+#include <msp/strings/utf8.h>
+#include <msp/strings/utils.h>
+#include <msp/time/units.h>
+#include <msp/time/utils.h>
+#include "designer.h"
+#include "input.h"
+#include "manipulator.h"
+#include "measure.h"
+#include "selection.h"
+
+using namespace std;
+using namespace Marklin;
+using namespace Msp;
+
+Designer::Designer(int argc, char **argv):
+       screen_w(1280),
+       screen_h(960),
+       input(0),
+       mode(SELECT),
+       cam_yaw(M_PI/2),
+       cam_pitch(-M_PI/4),
+       cam_pos(0, -0.5, 0.5),
+       shift(false),
+       move_x(0),
+       move_y(0),
+       zoom(0),
+       rotate(0),
+       pitch(0)
+{
+       cout<<"blah?\n";
+       catalogue.load("tracks.dat");
+       cout<<catalogue.get_tracks().size()<<'\n';
+
+       cat_layout=new Layout(catalogue);
+       cat_layout_3d=new Layout3D(*cat_layout);
+
+       const Catalogue::TrackMap &ctracks=catalogue.get_tracks();
+       unsigned n=0;
+       for(Catalogue::TrackMap::const_iterator i=ctracks.begin(); i!=ctracks.end(); ++i, ++n)
+       {
+               Track *track=i->second->copy();
+               track->set_position(Point((n%11)*0.1-0.5, 0.2-n/11*0.3, 0));
+               track->set_rotation(M_PI/2);
+               cat_layout->add_track(track);
+       }
+
+       manipulator=new Manipulator(*this);
+       manipulator->signal_status.connect(sigc::mem_fun(this, &Designer::manipulation_status));
+       manipulator->signal_done.connect(sigc::mem_fun(this, &Designer::manipulation_done));
+
+       layout=new Layout(catalogue);
+       layout_3d=new Layout3D(*layout);
+
+       if(argc>1)
+       {
+               layout->load(argv[1]);
+               const Track3DSeq &ltracks=layout_3d->get_tracks();
+               for(Track3DSeq::const_iterator i=ltracks.begin(); i!=ltracks.end(); ++i)
+               {
+                       if((*i)->get_track().get_sensor_id())
+                               (*i)->set_color(Color(1, 1, 0.5));
+                       else if((*i)->get_track().get_turnout_id())
+                               (*i)->set_color(Color(0.5, 1, 1));
+                       else if((*i)->get_track().get_flex())
+                               (*i)->set_color(Color(1, 0.5, 1));
+               }
+       }
+
+       selection=new Selection;
+       manipulator->set_selection(selection);
+
+       measure=new Measure(*this);
+       measure->signal_changed.connect(sigc::mem_fun(this, &Designer::measure_changed));
+       measure->signal_done.connect(sigc::mem_fun(this, &Designer::measure_done));
+}
+
+int Designer::main()
+{
+       setenv("__GL_SYNC_TO_VBLANK", "1", 0);
+       SDL_Init(SDL_INIT_VIDEO);
+       SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+       SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
+       SDL_SetVideoMode(screen_w, screen_h, 32, SDL_OPENGL);
+       SDL_EnableUNICODE(1);
+
+       glEnableClientState(GL_VERTEX_ARRAY);
+       glEnable(GL_DEPTH_TEST);
+       glEnable(GL_BLEND);
+       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+       glEnable(GL_CULL_FACE);
+
+       font=new GL::Font();
+       Parser::load(*font, "dejavu.font");
+
+       mode=SELECT;
+
+       Application::main();
+
+       delete font;
+       delete input;
+
+       SDL_Quit();
+
+       return exit_code;
+}
+
+void Designer::map_pointer_coords(int x, int y, float &gx, float &gy)
+{
+       float cos_pitch=cos(cam_pitch);
+       float sin_pitch=sin(cam_pitch);
+       float cos_yaw=cos(cam_yaw);
+       float sin_yaw=sin(cam_yaw);
+
+       float rx=sin_yaw*0.55228;
+       float ry=-cos_yaw*0.55228;
+
+       float ux=cos_yaw*-sin_pitch*0.41421;
+       float uy=sin_yaw*-sin_pitch*0.41421;
+       float uz=cos_pitch*0.41421;
+
+       float xf=(float)x*2/screen_w-1;
+       float yf=1-(float)y*2/screen_h;
+
+       float vx=cos_yaw*cos_pitch + xf*rx + yf*ux;
+       float vy=sin_yaw*cos_pitch + xf*ry + yf*uy;
+       float vz=sin_pitch + yf*uz;
+
+       gx=cam_pos.x-vx*cam_pos.z/vz;
+       gy=cam_pos.y-vy*cam_pos.z/vz;
+}
+
+void Designer::tick()
+{
+       SDL_Event event;
+       while(SDL_PollEvent(&event))
+       {
+               float gx,gy;
+               switch(event.type)
+               {
+               case SDL_MOUSEBUTTONDOWN:
+                       map_pointer_coords(event.button.x, event.button.y, gx, gy);
+                       button_press(event.button.x, screen_h-1-event.button.y, gx, gy, event.button.button);
+                       break;
+               case SDL_MOUSEMOTION:
+                       map_pointer_coords(event.motion.x, event.motion.y, gx, gy);
+                       pointer_motion(event.motion.x, screen_h-1-event.motion.y, gx, gy);
+                       break;
+               case SDL_KEYDOWN:
+                       key_press(event.key.keysym.sym, event.key.keysym.mod, event.key.keysym.unicode);
+                       break;
+               case SDL_KEYUP:
+                       key_release(event.key.keysym.sym, event.key.keysym.mod);
+                       break;
+               case SDL_QUIT:
+                       exit(0);
+                       break;
+               }
+       }
+
+       const Time::TimeStamp t=Time::now();
+       float dt=(t-last_tick)/Time::sec;
+       last_tick=t;
+
+       if(move_y)
+       {
+               cam_pos.x+=cos(cam_yaw)*dt*move_y;
+               cam_pos.y+=sin(cam_yaw)*dt*move_y;
+       }
+       if(move_x)
+       {
+               cam_pos.x+=sin(cam_yaw)*dt*move_x;
+               cam_pos.y+=-cos(cam_yaw)*dt*move_x;
+       }
+       if(zoom)
+       {
+               cam_pos.x+=cos(cam_yaw)*cos(cam_pitch)*dt*zoom;
+               cam_pos.y+=sin(cam_yaw)*cos(cam_pitch)*dt*zoom;
+               cam_pos.z+=sin(cam_pitch)*dt*zoom;
+       }
+       if(rotate)
+       {
+               float vx=cos(cam_yaw)*cos(cam_pitch);
+               float vy=sin(cam_yaw)*cos(cam_pitch);
+               float vz=sin(cam_pitch);
+
+               float gx=cam_pos.x-vx*cam_pos.z/vz;
+               float gy=cam_pos.y-vy*cam_pos.z/vz;
+               float d=sqrt(vx*vx+vy*vy)*cam_pos.z/vz;
+
+               cam_yaw+=M_PI*dt*rotate;
+               if(cam_yaw>M_PI*2)
+                       cam_yaw-=M_PI*2;
+               else if(cam_yaw<0)
+                       cam_yaw+=M_PI*2;
+
+               cam_pos.x=gx+cos(cam_yaw)*d;
+               cam_pos.y=gy+sin(cam_yaw)*d;
+       }
+       if(pitch)
+       {
+               cam_pitch+=M_PI/2*dt*pitch;
+               if(cam_pitch>M_PI/12)
+                       cam_pitch=M_PI/12;
+               else if(cam_pitch<-M_PI/2)
+                       cam_pitch=-M_PI/2;
+       }
+
+       if(tooltip_timeout && t>tooltip_timeout)
+       {
+               Track3D *t3d=0;
+
+               if(mode==CATALOGUE)
+                       t3d=pick_track(pointer_x, pointer_y);
+               else
+                       t3d=pick_track(pointer_x, pointer_y);
+
+               if(t3d)
+               {
+                       const Track &track=t3d->get_track();
+                       ostringstream ss;
+                       ss.precision(2);
+                       ss<<track.get_article_number()<<' '<<track.get_description();
+                       if(mode!=CATALOGUE)
+                               ss<<" (slope "<<track.get_slope()/track.get_length()*100<<"%)";
+                       if(track.get_turnout_id())
+                               ss<<" (turnout "<<track.get_turnout_id()<<')';
+                       else if(track.get_sensor_id())
+                               ss<<" (sensor "<<track.get_sensor_id()<<')';
+                       tooltip=decode<Utf8>(ss.str());
+
+                       move_tooltip(pointer_x, pointer_y);
+               }
+               else
+                       tooltip=L"";
+
+               tooltip_timeout=Time::TimeStamp();
+       }
+
+       render();
+
+       SDL_GL_SwapBuffers();
+}
+
+Designer::~Designer()
+{
+       delete manipulator;
+       delete selection;
+       delete layout;
+       delete layout_3d;
+       delete cat_layout;
+       delete cat_layout_3d;
+       delete measure;
+}
+
+/*** private ***/
+
+void Designer::key_press(unsigned key, unsigned mod, wchar_t ch)
+{
+       if(mode==INPUT)
+       {
+               input->key_press(key, mod, ch);
+               return;
+       }
+
+       if(key==SDLK_RSHIFT || key==SDLK_LSHIFT)
+               shift=true;
+
+       if(key==SDLK_n)
+               mode=CATALOGUE;
+       else if(key==SDLK_g)
+       {
+               manipulator->start_move();
+               mode=MANIPULATE;
+       }
+       else if(key==SDLK_r)
+       {
+               manipulator->start_rotate();
+               mode=MANIPULATE;
+       }
+       else if(key==SDLK_d)
+       {
+               manipulator->duplicate();
+               manipulator->start_move();
+               mode=MANIPULATE;
+       }
+       else if(key==SDLK_w)
+       {
+               input=new Input(*this, "Filename");
+               input->signal_cancel.connect(sigc::mem_fun(this, &Designer::input_dismiss));
+               input->signal_accept.connect(sigc::mem_fun(this, &Designer::save_accept));
+               mode=INPUT;
+       }
+       else if(key==SDLK_PLUS)
+               selection->select_more();
+       else if(key==SDLK_l && (mod&KMOD_SHIFT))
+       {
+               const TrackSeq &tracks=layout->get_tracks();
+               float len=0;
+               for(TrackSeq::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
+                       len+=(*i)->get_total_length();
+               cout<<"Total length: "<<len<<"m\n";
+       }
+       else if(key==SDLK_l)
+               selection->select_linked();
+       else if(key==SDLK_m)
+       {
+               measure->start();
+               mode=MEASURE;
+       }
+       else if(key==SDLK_z)
+       {
+               manipulator->start_elevate();
+               mode=MANIPULATE;
+       }
+       else if(key==SDLK_ESCAPE)
+       {
+               if(mode==MANIPULATE)
+                       manipulator->cancel();
+               else if(mode==CATALOGUE)
+                       mode=SELECT;
+               else
+                       selection->clear();
+       }
+       else if(key==SDLK_x)
+       {
+               Selection::TrackSet tracks=selection->get_tracks();
+               selection->clear();
+               for(Selection::TrackSet::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               {
+                       layout->remove_track(*i);
+                       delete *i;
+               }
+       }
+       else if(key==SDLK_f && (mod&KMOD_SHIFT))
+       {
+               const Selection::TrackSet &tracks=selection->get_tracks();
+               const TrackSeq &ltracks=layout->get_tracks();
+               for(Selection::TrackSet::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               {
+                       (*i)->set_flex(!(*i)->get_flex());
+                       (*i)->break_links();
+                       for(TrackSeq::const_iterator j=ltracks.begin(); j!=ltracks.end(); ++j)
+                               if(*j!=*i)
+                                       (*i)->snap_to(**j, true);
+
+                       Track3D *t3d=layout_3d->get_track(*i);
+                       if((*i)->get_flex())
+                               t3d->set_color(Color(1, 0.5, 1));
+                       else
+                               t3d->set_color(Color(1, 1, 1));
+               }
+       }
+       else if(key==SDLK_f)
+               manipulator->flatten();
+       else if(key==SDLK_e && (mod&KMOD_SHIFT))
+               manipulator->even_slope(true);
+       else if(key==SDLK_e)
+               manipulator->even_slope();
+       else if(key==SDLK_t)
+       {
+               Track *track=selection->get_track();
+               if(selection->size()==1 && track->get_n_routes()>1)
+               {
+                       ostringstream ss;
+                       ss<<track->get_turnout_id();
+                       input=new Input(*this, "Turnout ID", ss.str());
+                       input->signal_cancel.connect(sigc::mem_fun(this, &Designer::input_dismiss));
+                       input->signal_accept.connect(sigc::mem_fun(this, &Designer::turnout_id_accept));
+                       mode=INPUT;
+               }
+       }
+       else if(key==SDLK_s)
+       {
+               Track *track=selection->get_track();
+               if(selection->size()==1 && track->get_n_routes()==1)
+               {
+                       ostringstream ss;
+                       ss<<track->get_sensor_id();
+                       input=new Input(*this, "Sensor ID", ss.str());
+                       input->signal_cancel.connect(sigc::mem_fun(this, &Designer::input_dismiss));
+                       input->signal_accept.connect(sigc::mem_fun(this, &Designer::sensor_id_accept));
+                       mode=INPUT;
+               }
+       }
+       else if(key==SDLK_RIGHT)
+               rotate=-1;
+       else if(key==SDLK_LEFT)
+               rotate=1;
+       else if(key==SDLK_UP)
+               move_y=1;
+       else if(key==SDLK_DOWN)
+               move_y=-1;
+       else if(key==SDLK_INSERT)
+               zoom=-1;
+       else if(key==SDLK_PAGEUP)
+               zoom=1;
+       else if(key==SDLK_HOME)
+               pitch=1;
+       else if(key==SDLK_END)
+               pitch=-1;
+       else if(key==SDLK_DELETE)
+               move_x=-1;
+       else if(key==SDLK_PAGEDOWN)
+               move_x=1;
+}
+
+void Designer::key_release(unsigned key, unsigned)
+{
+       if(mode==INPUT)
+               return;
+
+       if(key==SDLK_RSHIFT || key==SDLK_LSHIFT)
+               shift=false;
+       else if(key==SDLK_RIGHT || key==SDLK_LEFT)
+               rotate=0;
+       else if(key==SDLK_UP || key==SDLK_DOWN)
+               move_y=0;
+       else if(key==SDLK_INSERT || key==SDLK_PAGEUP)
+               zoom=0;
+       else if(key==SDLK_HOME || key==SDLK_END)
+               pitch=0;
+       else if(key==SDLK_DELETE || key==SDLK_PAGEDOWN)
+               move_x=0;
+}
+
+void Designer::button_press(int x, int y, float gx, float gy, unsigned btn)
+{
+       if(mode==CATALOGUE)
+       {
+               if(btn==1)
+               {
+                       Track3D *ctrack=pick_track(x, y);
+                       if(ctrack)
+                       {
+                               Track *track=ctrack->get_track().copy();
+                               track->set_position(Point(gx, gy, 0));
+                               layout->add_track(track);
+
+                               selection->clear();
+                               selection->add_track(track);
+
+                               mode=SELECT;
+                       }
+               }
+               else
+                       mode=SELECT;
+       }
+       else if(mode==SELECT)
+       {
+               if(btn==1)
+               {
+                       Track3D *track=pick_track(x, y);
+                       if(track)
+                       {
+                               if(!shift)
+                                       selection->clear();
+                               selection->toggle_track(&track->get_track());
+                       }
+               }
+       }
+       else if(mode==MANIPULATE)
+               manipulator->button_press(x, y, gx, gy, btn);
+       else if(mode==MEASURE)
+               measure->button_press(x, y, gx, gy, btn);
+}
+
+void Designer::pointer_motion(int x, int y, float gx, float gy)
+{
+       if(mode==SELECT || mode==CATALOGUE)
+       {
+               pointer_x=x;
+               pointer_y=y;
+               tooltip_timeout=Time::now()+100*Time::msec;
+       }
+
+       if(mode!=INPUT)
+       {
+               manipulator->pointer_motion(x, y, gx, gy);
+               measure->pointer_motion(x, y, gx, gy);
+       }
+
+       if(mode==MEASURE || mode==MANIPULATE)
+               move_tooltip(x, y);
+}
+
+void Designer::project_3d()
+{
+       glMatrixMode(GL_PROJECTION);
+       glLoadIdentity();
+       glFrustum(-0.055228, 0.055228, -0.041421, 0.041421, 0.1, 10);
+       glMatrixMode(GL_MODELVIEW);
+}
+
+void Designer::apply_camera()
+{
+       glLoadIdentity();
+       if(mode==CATALOGUE)
+               glTranslatef(0, 0, -1);
+       else
+       {
+               glRotatef(-cam_pitch*180/M_PI-90, 1, 0, 0);
+               glRotatef(90-cam_yaw*180/M_PI, 0, 0, 1);
+               glTranslatef(-cam_pos.x, -cam_pos.y, -cam_pos.z);
+       }
+}
+
+void Designer::render()
+{
+       glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+       glEnable(GL_DEPTH_TEST);
+
+       project_3d();
+       apply_camera();
+       if(mode==CATALOGUE)
+               cat_layout_3d->render();
+       else
+       {
+               layout_3d->render(true);
+               manipulator->render();
+               if(mode==MEASURE)
+                       measure->render();
+       }
+
+       /*glBegin(GL_LINES);
+       glColor4f(1, 0, 0, 1);
+       glVertex3f(0, 0, 0);
+       glVertex3f(0.5, 0, 0);
+       glColor4f(0, 1, 0, 1);
+       glVertex3f(0, 0, 0);
+       glVertex3f(0, 0.5, 0);
+       glColor4f(0, 0, 1, 1);
+       glVertex3f(0, 0, 0);
+       glVertex3f(0, 0, 0.5);
+       glEnd();*/
+
+       glMatrixMode(GL_PROJECTION);
+       glLoadIdentity();
+       glOrtho(0, screen_w, 0, screen_h, 0, 1);
+       glMatrixMode(GL_MODELVIEW);
+       glLoadIdentity();
+
+       glDisable(GL_DEPTH_TEST);
+
+       if(!tooltip.empty())
+       {
+               glTranslatef(tooltip_x, tooltip_y, 0);
+               glScalef(20, 20, 20);
+               float width=font->get_string_width(tooltip);
+               glColor4f(0, 0, 0, 0.5);
+               glBegin(GL_QUADS);
+               glVertex2f(0, 0);
+               glVertex2f(width, 0);
+               glVertex2f(width, 1);
+               glVertex2f(0, 1);
+               glEnd();
+               glColor4f(1, 1, 1, 1);
+               font->draw_string(tooltip);
+       }
+
+       if(mode==INPUT)
+               input->render();
+}
+
+Track3D *Designer::pick_track(int x, int y)
+{
+       Layout3D *l=layout_3d;
+       if(mode==CATALOGUE)
+               l=cat_layout_3d;
+
+       float xx=((float)(x-(int)screen_w/2)/screen_h)*0.82843;
+       float yy=((float)y/screen_h-0.5)*0.82843;
+       float size=(float)4/screen_h*0.82843;
+
+       project_3d();
+       apply_camera();
+
+       return l->pick_track(xx, yy, size);
+}
+
+void Designer::manipulation_status(const string &status)
+{
+       tooltip=decode<Utf8>(status);
+}
+
+void Designer::manipulation_done(bool)
+{
+       mode=SELECT;
+}
+
+void Designer::measure_changed()
+{
+       float pard=measure->get_parallel_distance()*1000;
+       float perpd=measure->get_perpendicular_distance()*1000;
+       float d=sqrt(pard*pard+perpd*perpd);
+       float adiff=measure->get_angle_difference()*180/M_PI;
+       ostringstream ss;
+       ss.precision(3);
+       ss<<"Par "<<pard<<"mm - Perp "<<perpd<<"mm - Total "<<d<<"mm - Angle "<<adiff<<"°";
+       tooltip=decode<Utf8>(ss.str());
+}
+
+void Designer::measure_done()
+{
+       mode=SELECT;
+}
+
+void Designer::move_tooltip(int x, int y)
+{
+       int w=(int)(font->get_string_width(tooltip)*20);
+       tooltip_x=max(min((int)screen_w-w, x), 0);
+       tooltip_y=max(min((int)screen_h-20, y), 0);
+}
+
+void Designer::save_accept()
+{
+       layout->save(input->get_text());
+
+       input_dismiss();
+}
+
+void Designer::turnout_id_accept()
+{
+       Track *track=selection->get_track();
+       unsigned id=lexical_cast<unsigned>(input->get_text());
+       track->set_turnout_id(id);
+
+       Track3D *t3d=layout_3d->get_track(track);
+       if(id)
+               t3d->set_color(Color(0.5, 1, 1));
+       else
+               t3d->set_color(Color(1, 1, 1));
+
+       input_dismiss();
+}
+
+void Designer::sensor_id_accept()
+{
+       Track *track=selection->get_track();
+       unsigned id=lexical_cast<unsigned>(input->get_text());
+       track->set_sensor_id(id);
+
+       Track3D *t3d=layout_3d->get_track(track);
+       if(id)
+               t3d->set_color(Color(1, 1, 0.5));
+       else
+               t3d->set_color(Color(1, 1, 1));
+
+       input_dismiss();
+}
+
+void Designer::input_dismiss()
+{
+       delete input;
+       input=0;
+       mode=SELECT;
+}
+
+Application::RegApp<Designer> Designer::reg;
diff --git a/source/designer/designer.h b/source/designer/designer.h
new file mode 100644 (file)
index 0000000..ece41a5
--- /dev/null
@@ -0,0 +1,90 @@
+#ifndef DESIGNER_H_
+#define DESIGNER_H_
+
+#include <string>
+#include <msp/core/application.h>
+#include <msp/gl/font.h>
+#include <msp/time/timestamp.h>
+#include "libmarklin/catalogue.h"
+#include "libmarklin/layout.h"
+#include "3d/layout.h"
+#include "3d/track.h"
+
+class Input;
+class Manipulator;
+class Measure;
+class Selection;
+
+class Designer: public Msp::Application
+{
+public:
+       Designer(int, char **);
+       Marklin::Layout *get_layout() { return layout; }
+       Marklin::Layout3D *get_layout_3d() { return layout_3d; }
+       Msp::GL::Font   &get_font()   { return *font; }
+       int             main();
+       void            map_pointer_coords(int, int, float &, float &);
+       ~Designer();
+private:
+       enum Mode
+       {
+               SELECT,
+               CATALOGUE,
+               MANIPULATE,
+               MEASURE,
+               INPUT
+       };
+
+       unsigned       screen_w;
+       unsigned       screen_h;
+       Msp::GL::Font  *font;
+       Marklin::Catalogue catalogue;
+       Marklin::Layout    *layout;
+       Marklin::Layout3D  *layout_3d;
+       Marklin::Layout    *cat_layout;
+       Marklin::Layout3D  *cat_layout_3d;
+       Selection      *selection;
+       Manipulator    *manipulator;
+       Measure        *measure;
+       Input          *input;
+       Mode           mode;
+       float          cam_yaw;
+       float          cam_pitch;
+       Marklin::Point cam_pos;
+       bool           shift;
+       int            move_x;
+       int            move_y;
+       int            zoom;
+       int            rotate;
+       int            pitch;
+       int            pointer_x;
+       int            pointer_y;
+       int            tooltip_x;
+       int            tooltip_y;
+       std::wstring   tooltip;
+       Msp::Time::TimeStamp tooltip_timeout;
+       Msp::Time::TimeStamp last_tick;
+
+       void tick();
+       void key_press(unsigned, unsigned, wchar_t);
+       void key_release(unsigned, unsigned);
+       void button_press(int, int, float, float, unsigned);
+       void pointer_motion(int, int, float, float);
+       void project_3d();
+       void apply_camera();
+       void render();
+       Marklin::Track3D *pick_track(int, int);
+       void manipulation_status(const std::string &);
+       void manipulation_done(bool);
+       void measure_changed();
+       void measure_done();
+       void move_tooltip(int, int);
+       void save_accept();
+       void turnout_id_accept();
+       void sensor_id_accept();
+       void input_dismiss();
+
+       static Msp::Application::RegApp<Designer> reg;
+};
+
+#endif
diff --git a/source/designer/input.cpp b/source/designer/input.cpp
new file mode 100644 (file)
index 0000000..1b18646
--- /dev/null
@@ -0,0 +1,87 @@
+#include <SDL_keysym.h>
+#include <GL/gl.h>
+#include <msp/gl/texture.h>
+#include "designer.h"
+#include "input.h"
+
+using namespace std;
+using namespace Msp;
+
+Input::Input(Designer &d, const string &t, const string &e):
+       designer(d),
+       title(t),
+       text(e),
+       pos(text.size())
+{ }
+
+void Input::key_press(unsigned key, unsigned, wchar_t ch)
+{
+       if(key==SDLK_RETURN)
+               signal_accept.emit();
+       else if(key==SDLK_ESCAPE)
+               signal_cancel.emit();
+       else if(key==SDLK_BACKSPACE)
+       {
+               if(pos>0)
+               {
+                       text.erase(pos-1, 1);
+                       --pos;
+               }
+       }
+       else if(key==SDLK_DELETE)
+       {
+               if(pos<text.size())
+                       text.erase(pos, 1);
+       }
+       else if(key==SDLK_LEFT)
+       {
+               if(pos>0)
+                       --pos;
+       }
+       else if(key==SDLK_RIGHT)
+       {
+               if(pos<text.size())
+                       ++pos;
+       }
+       else if(ch>=0x20)
+       {
+               text.insert(pos, 1, ch);
+               ++pos;
+       }
+}
+
+void Input::render()
+{
+       glLoadIdentity();
+       glTranslatef(300, 450, 0);
+
+       GL::Texture::unbind();
+       glColor4f(0.7, 0.7, 0.7, 0.9);
+       glBegin(GL_QUADS);
+       glVertex2f(0, 0);
+       glVertex2f(680, 0);
+       glVertex2f(680, 60);
+       glVertex2f(0, 60);
+       glEnd();
+
+       glColor4f(0, 0, 0, 1);
+       glTranslatef(5, 35, 0);
+
+       glPushMatrix();
+       glScalef(20, 20, 20);
+       designer.get_font().draw_string(title);
+       glPopMatrix();
+
+       glTranslatef(0, -30, 0);
+       glPushMatrix();
+       glScalef(20, 20, 20);
+       designer.get_font().draw_string(text);
+
+       glTranslatef(designer.get_font().get_string_width(text.substr(0, pos)), 0, 0);
+       glDisable(GL_TEXTURE_2D);
+       glBegin(GL_LINES);
+       glVertex2f(0, 0);
+       glVertex2f(0, 1);
+       glEnd();
+       glPopMatrix();
+}
diff --git a/source/designer/input.h b/source/designer/input.h
new file mode 100644 (file)
index 0000000..276ce5f
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef INPUT_H_
+#define INPUT_H_
+
+#include <string>
+#include <sigc++/sigc++.h>
+
+class Designer;
+
+class Input
+{
+public:
+       sigc::signal<void> signal_accept;
+       sigc::signal<void> signal_cancel;
+
+       Input(Designer &, const std::string &, const std::string & ="");
+       const std::string &get_text() { return text; }
+       void key_press(unsigned, unsigned, wchar_t);
+       void render();
+private:
+       Designer    &designer;
+       std::string title;
+       std::string text;
+       unsigned    pos;
+};
+
+#endif
diff --git a/source/designer/manipulator.cpp b/source/designer/manipulator.cpp
new file mode 100644 (file)
index 0000000..cbeb982
--- /dev/null
@@ -0,0 +1,479 @@
+#include <algorithm>
+#include <cmath>
+#include <GL/gl.h>
+#include "3d/layout.h"
+#include "designer.h"
+#include "manipulator.h"
+#include "selection.h"
+
+using namespace std;
+using namespace Marklin;
+using namespace Msp;
+
+#include <iostream>
+
+Manipulator::Manipulator(Designer &d):
+       designer(d),
+       selection(0),
+       wrap_rot(0),
+       mode(NONE),
+       angle(0)
+{ }
+
+void Manipulator::set_selection(Selection *s)
+{
+       selection_changed_conn.disconnect();
+
+       selection=s;
+       if(selection)
+               selection_changed_conn=selection->signal_changed.connect(sigc::mem_fun(this, &Manipulator::selection_changed));
+
+       selection_changed();
+}
+
+void Manipulator::start_move()
+{
+       if(mode)
+               cancel();
+
+       move_origin=gpointer;
+
+       mode=MOVE;
+}
+
+void Manipulator::start_rotate()
+{
+       if(mode)
+               cancel();
+
+       rot_origin=atan2(gpointer.y-center.y, gpointer.x-center.x);
+
+       mode=ROTATE;
+}
+
+void Manipulator::start_elevate()
+{
+       if(mode)
+               cancel();
+
+       elev_origin=pointer_y;
+
+       mode=ELEVATE;
+}
+
+void Manipulator::duplicate()
+{
+       if(mode)
+               cancel();
+
+       TrackSeq new_tracks;
+       for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               Track *track=i->track->copy();
+               designer.get_layout()->add_track(track);
+               new_tracks.push_back(track);
+       }
+
+       selection->clear();
+       for(TrackSeq::iterator i=new_tracks.begin(); i!=new_tracks.end(); ++i)
+       {
+               selection->add_track(*i);
+               for(TrackSeq::iterator j=i; j!=new_tracks.end(); ++j)
+                       if(j!=i)
+                               (*i)->snap_to(**j, true);
+       }
+}
+
+void Manipulator::flatten()
+{
+       if(mode)
+               cancel();
+
+       if(tracks.empty()) return;
+
+       float z=0;
+       for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               z+=i->track->get_position().z+i->track->get_slope()/2;
+       z/=tracks.size();
+
+       for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               Point p=i->track->get_position();
+               i->track->set_position(Point(p.x, p.y, z));
+               i->track->set_slope(0);
+       }
+
+       for(TrackSeq::iterator i=neighbors.begin(); i!=neighbors.end(); ++i)
+               (*i)->check_slope();
+
+       update_wrap();
+}
+
+void Manipulator::even_slope(bool smooth)
+{
+       if(mode)
+               cancel();
+
+       if(neighbors.size()!=2)
+               return;
+
+       for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               if(i->track->get_endpoints().size()!=2)
+                       return;
+
+       TrackSeq tracks2;
+       for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               tracks2.push_back(i->track);
+
+       float total_len=0;
+
+       TrackOrderSeq order;
+       Track *cur=neighbors.front();
+       while(tracks2.size())
+       {
+               bool rev=false;
+               for(TrackSeq::iterator i=tracks2.begin(); i!=tracks2.end(); ++i)
+               {
+                       const Track::EndpointSeq &epl=(*i)->get_endpoints();
+                       if(epl.front().link==cur)
+                       {
+                               cur=*i;
+                               tracks2.erase(i);
+                               break;
+                       }
+                       else if(epl.back().link==cur)
+                       {
+                               cur=*i;
+                               rev=true;
+                               tracks2.erase(i);
+                               break;
+                       }
+               }
+               order.push_back(TrackOrder(cur, rev));
+               total_len+=cur->get_length();
+       }
+
+       const Track::Endpoint *ep=neighbors.front()->get_endpoint_by_link(order.front().track);
+       float start_z=neighbors.front()->get_position().z+ep->pos.z;
+       ep=neighbors.back()->get_endpoint_by_link(order.back().track);
+       float end_z=neighbors.back()->get_position().z+ep->pos.z;
+
+       if(smooth)
+       {
+               float dir=(end_z>start_z)?1:-1;
+               float cur_slope=0;
+               while((end_z-start_z)*dir/total_len>cur_slope+0.025 && order.size()>2)
+               {
+                       cur_slope+=0.025;
+
+                       float dz=order.front().track->get_length()*dir*cur_slope;
+                       set_slope(order.front(), start_z, dz);
+                       start_z+=dz;
+                       total_len-=order.front().track->get_length();
+                       order.erase(order.begin());
+
+                       dz=order.back().track->get_length()*dir*cur_slope;
+                       set_slope(order.back(), end_z-dz, dz);
+                       end_z-=dz;
+                       total_len-=order.back().track->get_length();
+                       order.erase(--order.end());
+               }
+       }
+
+       float cur_z=start_z;
+       for(TrackOrderSeq::iterator i=order.begin(); i!=order.end(); ++i)
+       {
+               float dz=i->track->get_length()*(end_z-start_z)/total_len;
+               set_slope(*i, cur_z, dz);
+               cur_z+=dz;
+       }
+
+       for(TrackSeq::iterator i=neighbors.begin(); i!=neighbors.end(); ++i)
+               (*i)->check_slope();
+
+       update_wrap();
+}
+
+void Manipulator::cancel()
+{
+       if(!mode)
+               return;
+       mode=NONE;
+
+       wrap_pos=center;
+       for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               i->track->set_position(Point(center.x+i->pos.x, center.y+i->pos.y, center.z+i->pos.z));
+               i->track->set_rotation(i->rot);
+       }
+
+       for(TrackSeq::iterator i=neighbors.begin(); i!=neighbors.end(); ++i)
+               (*i)->check_slope();
+
+       angle=0;
+       //snapped=0;
+
+       signal_done.emit(false);
+}
+
+void Manipulator::button_press(int, int, float, float, unsigned btn)
+{
+       if(btn==3)
+               cancel();
+       else if(mode)
+       {
+               mode=NONE;
+               update_wrap();
+               //snapped=0;
+
+               for(TrackSeq::iterator i=neighbors.begin(); i!=neighbors.end(); ++i)
+                       for(MTrackSeq::iterator j=tracks.begin(); j!=tracks.end(); ++j)
+                               j->track->break_link(**i);
+
+               const TrackSeq &ltracks=designer.get_layout()->get_tracks();
+               for(TrackSeq::const_iterator i=ltracks.begin(); i!=ltracks.end(); ++i)
+               {
+                       bool ok=true;
+                       for(MTrackSeq::iterator j=tracks.begin(); (j!=tracks.end() && ok); ++j)
+                               ok=(j->track!=*i);
+                       if(!ok) continue;
+
+                       for(MTrackSeq::iterator j=tracks.begin(); j!=tracks.end(); ++j)
+                               j->track->snap_to(**i, true);
+               }
+
+               update_neighbors();
+
+               signal_done.emit(true);
+       }
+}
+
+void Manipulator::pointer_motion(int, int y, float gx, float gy)
+{
+       pointer_y=y;
+       gpointer=Point(gx, gy, 0);
+
+       if(mode==MOVE)
+       {
+               Point delta(gpointer.x-move_origin.x, gpointer.y-move_origin.y, 0);
+
+               wrap_pos=Point(center.x+delta.x, center.y+delta.y, center.z);
+               for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               {
+                       i->track->set_position(Point(wrap_pos.x+i->pos.x, wrap_pos.y+i->pos.y, wrap_pos.z+i->pos.z));
+                       i->track->set_rotation(i->rot);
+               }
+
+               const TrackSeq &ltracks=designer.get_layout()->get_tracks();
+               MTrack *snapped=0;
+               for(TrackSeq::const_iterator i=ltracks.begin(); (i!=ltracks.end() && !snapped); ++i)
+               {
+                       bool ok=true;
+                       for(MTrackSeq::iterator j=tracks.begin(); (j!=tracks.end() && ok); ++j)
+                               ok=(j->track!=*i);
+                       if(!ok) continue;
+
+                       for(MTrackSeq::iterator j=tracks.begin(); (j!=tracks.end() && !snapped); ++j)
+                               if(j->track->snap_to(**i, false))
+                                       snapped=&*j;
+               }
+
+               if(snapped)
+               {
+                       float da=snapped->track->get_rotation()-snapped->rot;
+                       float c=cos(da);
+                       float s=sin(da);
+                       const Point &sp=snapped->track->get_position();
+                       for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+                       {
+                               if(&*i==snapped)
+                                       continue;
+
+                               Point dp(i->pos.x-snapped->pos.x, i->pos.y-snapped->pos.y, 0);
+                               i->track->set_position(Point(sp.x+c*dp.x-s*dp.y, sp.y+s*dp.x+c*dp.y, sp.z));
+                               i->track->set_rotation(i->rot+da);
+                       }
+               }
+       }
+       else if(mode==ROTATE)
+       {
+               float a=atan2(gpointer.y-center.y, gpointer.x-center.x);
+               angle+=a-rot_origin;
+               rot_origin=a;
+
+               wrap_rot=angle;
+               for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               {
+                       float c=cos(angle);
+                       float s=sin(angle);
+                       i->track->set_position(Point(center.x+c*i->pos.x-s*i->pos.y, center.y+s*i->pos.x+c*i->pos.y, center.z*i->pos.z));
+                       i->track->set_rotation(angle+i->rot);
+               }
+       }
+       else if(mode==ELEVATE)
+       {
+               float dz=(y-elev_origin)/1000.;
+
+               ostringstream ss;
+               ss.precision(3);
+               ss<<"Elevation: "<<dz*1000<<"mm ("<<(center.z+dz)*1000<<"mm)";
+               signal_status.emit(ss.str());
+
+               wrap_pos.z=center.z+dz;
+               for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+                       i->track->set_position(Point(center.x+i->pos.x, center.y+i->pos.y, center.z+i->pos.z+dz));
+
+               for(TrackSeq::iterator i=neighbors.begin(); i!=neighbors.end(); ++i)
+                       (*i)->check_slope();
+       }
+}
+
+void Manipulator::render()
+{
+       glPushMatrix();
+       glTranslatef(wrap_pos.x, wrap_pos.y, wrap_pos.z);
+       glRotatef(wrap_rot*180/M_PI, 0, 0, 1);
+
+       glLineWidth(2);
+       glColor4f(0, 1, 0, 0.5);
+       for(list<TrackWrap>::iterator i=wrap.begin(); i!=wrap.end(); ++i)
+       {
+               glPushMatrix();
+               glTranslatef(i->pos.x, i->pos.y, i->pos.z);
+               glRotatef(i->rot*180/M_PI, 0, 0, 1);
+
+               glBegin(GL_LINE_LOOP);
+               glVertex2f(-i->width/2, -i->height/2);
+               glVertex2f(i->width/2, -i->height/2);
+               glVertex2f(i->width/2, i->height/2);
+               glVertex2f(-i->width/2, i->height/2);
+               glEnd();
+
+               glPopMatrix();
+       }
+
+       glPopMatrix();
+}
+
+/*** private ***/
+
+void Manipulator::selection_changed()
+{
+       if(mode)
+               cancel();
+
+       tracks.clear();
+       if(selection)
+       {
+               const Selection::TrackSet &stracks=selection->get_tracks();
+               tracks.insert(tracks.end(), stracks.begin(), stracks.end());
+       }
+
+       update_neighbors();
+       update_wrap();
+}
+
+void Manipulator::update_wrap()
+{
+       wrap.clear();
+       float min_x=0,max_x=0;
+       float min_y=0,max_y=0;
+       for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               Track3D *t3d=designer.get_layout_3d()->get_track(i->track);
+
+               TrackWrap tw;
+               float min_area=100;
+               for(float a=0; a<M_PI; a+=0.01)
+               {
+                       Point minp,maxp;
+                       t3d->get_bounds(a, minp, maxp);
+                       float area=(maxp.x-minp.x)*(maxp.y-minp.y);
+                       if(area<min_area)
+                       {
+                               float c=cos(a);
+                               float s=sin(a);
+                               float x=(minp.x+maxp.x)/2;
+                               float y=(minp.y+maxp.y)/2;
+                               tw.pos=Point(c*x-s*y, s*x+c*y, (minp.z+maxp.z)/2);
+                               tw.rot=a;
+                               tw.width=maxp.x-minp.x+0.01;
+                               tw.height=maxp.y-minp.y+0.01;
+
+                               min_area=area;
+                       }
+               }
+
+               if(i==tracks.begin())
+               {
+                       min_x=max_x=tw.pos.x;
+                       min_y=max_y=tw.pos.y;
+               }
+               else
+               {
+                       min_x=min(min_x, tw.pos.x);
+                       max_x=max(max_x, tw.pos.x);
+                       min_y=min(min_y, tw.pos.y);
+                       max_y=max(max_y, tw.pos.y);
+               }
+               wrap.push_back(tw);
+       }
+
+       center=Point((min_x+max_x)/2, (min_y+max_y)/2, 0);
+       wrap_pos=center;
+       wrap_rot=0;
+       for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               const Point &tp=i->track->get_position();
+               i->pos=Point(tp.x-center.x, tp.y-center.y, tp.z);
+       }
+       for(list<TrackWrap>::iterator i=wrap.begin(); i!=wrap.end(); ++i)
+       {
+               i->pos.x-=center.x;
+               i->pos.y-=center.y;
+       }
+}
+
+void Manipulator::update_neighbors()
+{
+       neighbors.clear();
+       for(MTrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               const Track::EndpointSeq &epl=i->track->get_endpoints();
+               for(Track::EndpointSeq::const_iterator j=epl.begin(); j!=epl.end(); ++j)
+               {
+                       if(!j->link)
+                               continue;
+                       if(find(neighbors.begin(), neighbors.end(), j->link)!=neighbors.end())
+                               continue;
+
+                       bool ok=true;
+                       for(MTrackSeq::iterator k=tracks.begin(); (k!=tracks.end() && ok); ++k)
+                               ok=(k->track!=j->link);
+
+                       if(ok)
+                               neighbors.push_back(j->link);
+               }
+       }
+}
+
+void Manipulator::set_slope(TrackOrder &track, float z, float dz)
+{
+       const Point &p=track.track->get_position();
+       if(track.rev)
+       {
+               track.track->set_position(Point(p.x, p.y, z+dz));
+               track.track->set_slope(-dz);
+       }
+       else
+       {
+               track.track->set_position(Point(p.x, p.y, z));
+               track.track->set_slope(dz);
+       }
+}
+
+Manipulator::MTrack::MTrack(Track *t):
+       track(t),
+       pos(track->get_position()),
+       rot(track->get_rotation())
+{ }
diff --git a/source/designer/manipulator.h b/source/designer/manipulator.h
new file mode 100644 (file)
index 0000000..3a8882e
--- /dev/null
@@ -0,0 +1,89 @@
+#ifndef MANIPULATOR_H_
+#define MANIPULATOR_H_
+
+#include <sigc++/sigc++.h>
+#include "3d/track.h"
+
+class Designer;
+class Selection;
+
+class Manipulator
+{
+public:
+       sigc::signal<void, const std::string &> signal_status;
+       sigc::signal<void, bool> signal_done;
+
+       Manipulator(Designer &);
+       void set_selection(Selection *);
+       void start_move();
+       void start_rotate();
+       void start_elevate();
+       void duplicate();
+       void flatten();
+       void even_slope(bool =false);
+       void cancel();
+       void button_press(int, int, float, float, unsigned);
+       void pointer_motion(int, int, float, float);
+       void render();
+private:
+       enum Mode
+       {
+               NONE,
+               MOVE,
+               ROTATE,
+               ELEVATE
+       };
+
+       struct MTrack
+       {
+               Marklin::Track *track;
+               Marklin::Point pos;
+               float rot;
+
+               MTrack(Marklin::Track *);
+       };
+       typedef std::list<MTrack> MTrackSeq;
+
+       struct TrackOrder
+       {
+               Marklin::Track *track;
+               bool rev;
+
+               TrackOrder(Marklin::Track *t, bool r): track(t), rev(r) { }
+       };
+       typedef std::list<TrackOrder> TrackOrderSeq;
+
+       struct TrackWrap
+       {
+               Marklin::Point pos;
+               float rot;
+               float width;
+               float height;
+       };
+
+       Designer   &designer;
+       Selection  *selection;
+       MTrackSeq  tracks;
+       Marklin::Point center;
+
+       std::list<TrackWrap> wrap;
+       Marklin::Point wrap_pos;
+       float wrap_rot;
+
+       Marklin::Point gpointer;
+       int            pointer_y;
+       Mode           mode;
+       Marklin::Point move_origin;
+       float          angle;
+       float          rot_origin;
+       int            elev_origin;
+       Marklin::TrackSeq neighbors;
+       sigc::connection selection_changed_conn;
+
+       void selection_changed();
+       void update_wrap();
+       void update_neighbors();
+       void set_slope(TrackOrder &, float, float);
+};
+
+#endif
diff --git a/source/designer/measure.cpp b/source/designer/measure.cpp
new file mode 100644 (file)
index 0000000..6f1d7bc
--- /dev/null
@@ -0,0 +1,120 @@
+#include <cmath>
+#include <GL/gl.h>
+#include "designer.h"
+#include "3d/layout.h"
+#include "measure.h"
+
+using namespace Marklin;
+using namespace Msp;
+
+Measure::Measure(Designer &d):
+       designer(d),
+       state(NONE)
+{ }
+
+void Measure::start()
+{
+       state=STARTING;
+}
+
+void Measure::snap_to_tracks(Point &pt, float &dir)
+{
+       const TrackSeq &ltracks=designer.get_layout()->get_tracks();
+       for(TrackSeq::const_iterator i=ltracks.begin(); i!=ltracks.end(); ++i)
+               if((*i)->snap(pt, dir))
+                       return;
+}
+
+void Measure::button_press(int, int, float gx, float gy, unsigned btn)
+{
+       if(!state)
+               return;
+
+       if(btn==1)
+       {
+               spoint=Point(gx, gy, 0);
+               sdir=0;
+               snap_to_tracks(spoint, sdir);
+
+               state=ACTIVE;
+       }
+       else if(btn==3)
+       {
+               if(state==ACTIVE)
+                       state=STARTING;
+               else
+               {
+                       state=NONE;
+                       signal_done.emit();
+               }
+       }
+}
+
+void Measure::pointer_motion(int, int, float gx, float gy)
+{
+       if(!state)
+               return;
+
+       pointer=Point(gx, gy, 0);
+       float dir=sdir;
+       snap_to_tracks(pointer, dir);
+
+       if(state!=STARTING)
+       {
+               Point delta(pointer.x-spoint.x, pointer.y-spoint.y, 0);
+               float c=cos(sdir);
+               float s=sin(sdir);
+
+               par_dist=delta.x*c+delta.y*s;
+               perp_dist=delta.x*s-delta.y*c;
+
+               adiff=dir-sdir+M_PI;
+               while(adiff<-M_PI)
+                       adiff+=M_PI*2;
+               while(adiff>M_PI)
+                       adiff-=M_PI*2;
+
+               signal_changed.emit();
+       }
+}
+
+void Measure::render()
+{
+       glPushMatrix();
+       if(state==ACTIVE)
+               glTranslatef(spoint.x, spoint.y, spoint.z);
+       else if(state==STARTING)
+               glTranslatef(pointer.x, pointer.y, pointer.z);
+
+       glDisable(GL_CULL_FACE);
+       glColor4f(1, 1, 1, 1);
+       glBegin(GL_QUAD_STRIP);
+       for(unsigned i=0; i<=16; ++i)
+       {
+               float x=cos(i*M_PI/8)*0.005;
+               float y=sin(i*M_PI/8)*0.005;
+               glVertex3f(x, y, 0);
+               glVertex3f(x, y, 0.01);
+       }
+       glEnd();
+
+       if(state==ACTIVE)
+       {
+               float c=cos(sdir);
+               float s=sin(sdir);
+               glBegin(GL_QUAD_STRIP);
+               glVertex3f(0, 0, 0);
+               glVertex3f(0, 0, 0.01);
+               glVertex3f(c*par_dist, s*par_dist, 0);
+               glVertex3f(c*par_dist, s*par_dist, 0.01);
+               glVertex3f(pointer.x-spoint.x, pointer.y-spoint.y, 0);
+               glVertex3f(pointer.x-spoint.x, pointer.y-spoint.y, 0.01);
+               /*glVertex3f(s*perp_dist, -c*perp_dist, 0);
+               glVertex3f(s*perp_dist, -c*perp_dist, 0.01);*/
+               glVertex3f(0, 0, 0);
+               glVertex3f(0, 0, 0.01);
+               glEnd();
+       }
+
+       glPopMatrix();
+}
diff --git a/source/designer/measure.h b/source/designer/measure.h
new file mode 100644 (file)
index 0000000..d7c7b77
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef MEASURE_H_
+#define MEASURE_H_
+
+#include <sigc++/sigc++.h>
+
+class Designer;
+
+class Measure
+{
+public:
+       sigc::signal<void> signal_done;
+       sigc::signal<void> signal_changed;
+
+       Measure(Designer &);
+       float get_parallel_distance() const      { return par_dist; }
+       float get_perpendicular_distance() const { return perp_dist; }
+       float get_angle_difference() const       { return adiff; }
+       void start();
+       void button_press(int, int, float, float, unsigned);
+       void pointer_motion(int, int, float, float);
+       void render();
+private:
+       enum State
+       {
+               NONE,
+               STARTING,
+               ACTIVE
+       };
+
+       Designer &designer;
+       Marklin::Point pointer;
+       Marklin::Point spoint;
+       float sdir;
+       float par_dist;
+       float perp_dist;
+       float adiff;
+       State state;
+
+       void snap_to_tracks(Marklin::Point &, float &);
+};
+
+#endif
diff --git a/source/designer/selection.cpp b/source/designer/selection.cpp
new file mode 100644 (file)
index 0000000..a693c0a
--- /dev/null
@@ -0,0 +1,89 @@
+#include <algorithm>
+#include "selection.h"
+
+using namespace std;
+using namespace Marklin;
+using namespace Msp;
+
+Track *Selection::get_track() const
+{
+       if(tracks.empty())
+               return 0;
+       else
+               return *tracks.begin();
+}
+
+void Selection::clear()
+{
+       tracks.clear();
+       signal_changed.emit();
+}
+
+void Selection::add_track(Track *t)
+{
+       if(tracks.insert(t).second)
+               signal_changed.emit();
+}
+
+void Selection::remove_track(Track *t)
+{
+       if(tracks.erase(t))
+               signal_changed.emit();
+}
+
+void Selection::toggle_track(Track *t)
+{
+       if(!tracks.erase(t))
+               tracks.insert(t);
+
+       signal_changed.emit();
+}
+
+void Selection::select_more()
+{
+       TrackSet new_tracks;
+       for(TrackSet::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               const Track::EndpointSeq &epl=(*i)->get_endpoints();
+               for(Track::EndpointSeq::const_iterator j=epl.begin(); j!=epl.end(); ++j)
+                       if(j->link)
+                               new_tracks.insert(j->link);
+       }
+
+       bool changed=false;
+       for(TrackSet::iterator i=new_tracks.begin(); i!=new_tracks.end(); ++i)
+               if(tracks.insert(*i).second)
+                       changed=true;
+
+       if(changed)
+               signal_changed.emit();
+}
+
+void Selection::select_linked()
+{
+       bool changed=false;
+       TrackSeq queue(tracks.begin(), tracks.end());
+       while(!queue.empty())
+       {
+               Track *track=queue.front();
+               queue.erase(queue.begin());
+
+               const Track::EndpointSeq &epl=track->get_endpoints();
+               for(Track::EndpointSeq::const_iterator j=epl.begin(); j!=epl.end(); ++j)
+                       if(j->link && tracks.insert(j->link).second)
+                       {
+                               queue.push_back(j->link);
+                               changed=true;
+                       }
+       }
+       for(TrackSet::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               const Track::EndpointSeq &epl=(*i)->get_endpoints();
+               for(Track::EndpointSeq::const_iterator j=epl.begin(); j!=epl.end(); ++j)
+                       if(j->link && tracks.insert(j->link).second)
+                               changed=true;
+       }
+
+       if(changed)
+               signal_changed.emit();
+}
diff --git a/source/designer/selection.h b/source/designer/selection.h
new file mode 100644 (file)
index 0000000..36e4435
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef SELECTION_H_
+#define SELECTION_H_
+
+#include <set>
+#include <sigc++/sigc++.h>
+#include "3d/track.h"
+
+class Selection
+{
+public:
+       typedef std::set<Marklin::Track *> TrackSet;
+
+       sigc::signal<void> signal_changed;
+
+       const TrackSet &get_tracks() const { return tracks; }
+       Marklin::Track *get_track() const;
+       unsigned size() const { return tracks.size(); }
+       void clear();
+       void add_track(Marklin::Track *);
+       void remove_track(Marklin::Track *);
+       void toggle_track(Marklin::Track *);
+       void select_more();
+       void select_linked();
+private:
+       TrackSet tracks;
+};
+
+#endif
diff --git a/source/engineer/engineer.cpp b/source/engineer/engineer.cpp
new file mode 100644 (file)
index 0000000..573ef6f
--- /dev/null
@@ -0,0 +1,485 @@
+#include <cmath>
+#include <limits>
+#include <SDL.h>
+#include <GL/gl.h>
+#include <msp/core/error.h>
+#include <msp/core/getopt.h>
+#include <msp/strings/lexicalcast.h>
+#include <msp/strings/regex.h>
+#include "engineer.h"
+#include "mainpanel.h"
+#include "trainpanel.h"
+
+using namespace std;
+using namespace Marklin;
+using namespace Msp;
+
+#include <iostream>
+
+Engineer::Engineer(int argc, char **argv):
+       screen_w(1280),
+       screen_h(960),
+       fullscreen(false),
+       layout(catalogue),
+       layout_3d(layout),
+       no_lighting(false),
+       placing_train(0)
+{
+       string res;
+       bool   debug=false;
+       string device="/dev/ttyS0";
+       unsigned quality=4;
+
+       GetOpt getopt;
+       getopt.add_option('r', "resolution",  res,         GetOpt::REQUIRED_ARG);
+       getopt.add_option('f', "fullscreen",  fullscreen,  GetOpt::NO_ARG);
+       getopt.add_option('g', "debug",       debug,       GetOpt::NO_ARG);
+       getopt.add_option('d', "device",      device,      GetOpt::REQUIRED_ARG);
+       getopt.add_option('q', "quality",     quality,     GetOpt::REQUIRED_ARG);
+       getopt.add_option(     "no-lighting", no_lighting, GetOpt::NO_ARG);
+       getopt(argc, argv);
+
+       if(!res.empty())
+       {
+               if(RegMatch m=Regex("([1-9][0-9]*)x([1-9][0-9]*)").match(res))
+               {
+                       screen_w=lexical_cast<unsigned>(m[1].str);
+                       screen_h=lexical_cast<unsigned>(m[2].str);
+               }
+               else
+                       throw UsageError("Invalid resolution");
+       }
+
+       if(device!="none")
+               control.open(device);
+
+       control.set_debug(debug);
+
+       layout_3d.set_quality(quality);
+
+       catalogue.load("tracks.dat");
+
+       const list<string> &args=getopt.get_args();
+       if(args.empty())
+               throw UsageError("No layout given");
+       layout.load(args.front());
+
+       trfc_mgr=new TrafficManager(control, layout);
+
+       view_all();
+
+       /*const TrackSeq &tracks=layout.get_tracks();
+
+       for(TrackSeq::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               if(unsigned trnt_id=(*i)->get_turnout_id())
+               {
+                       Turnout *trnt=new Turnout(control, trnt_id);
+                       trnt->signal_route_changed.connect(sigc::mem_fun(this, &Engineer::turnout_route_changed));
+               }
+               if(unsigned sens_id=(*i)->get_sensor_id())
+               {
+                       Sensor *sens=new Sensor(control, sens_id);
+                       sens->signal_state_changed.connect(sigc::bind(sigc::mem_fun(this, &Engineer::sensor_state_changed), sens_id));
+               }
+       }*/
+}
+
+Engineer::~Engineer()
+{
+}
+
+void Engineer::add_train(unsigned addr)
+{
+       if(control.get_locomotive(addr))
+               return;
+
+       Locomotive *loco=new Locomotive(control, addr);
+       Train *train=new Train(*trfc_mgr, *loco);
+
+       TrainPanel *tpanel=new TrainPanel(*this, ui_res, *train);
+       int y=main_panel->get_geometry().y;
+       for(TrainPanelSeq::iterator i=train_panels.begin(); i!=train_panels.end(); ++i)
+               y-=(*i)->get_geometry().h;
+       tpanel->set_position(0, y-tpanel->get_geometry().h);
+       train_panels.push_back(tpanel);
+
+       placing_train=train;
+}
+
+int Engineer::main()
+{
+       SDL_Init(SDL_INIT_VIDEO);
+       SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+       SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
+       SDL_Surface *screen=SDL_SetVideoMode(screen_w, screen_h, 32, SDL_OPENGL|(fullscreen?SDL_FULLSCREEN:0));
+       if(!screen)
+       {
+               SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
+               screen=SDL_SetVideoMode(screen_w, screen_h, 32, SDL_OPENGL|(fullscreen?SDL_FULLSCREEN:0));
+       }
+       if(!screen)
+       {
+               SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 0);
+               screen=SDL_SetVideoMode(screen_w, screen_h, 32, SDL_OPENGL|(fullscreen?SDL_FULLSCREEN:0));
+       }
+       if(!screen)
+               throw Exception("Couldn't create window");
+
+       glEnableClientState(GL_VERTEX_ARRAY);
+       glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
+       glEnable(GL_COLOR_MATERIAL);
+       glDepthFunc(GL_LEQUAL);
+       glEnable(GL_BLEND);
+       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+       font=new GL::Font();
+       if(screen_w>=1024)
+       {
+               font_size=20;
+               Parser::load(*font, "dejavu-20.font");
+       }
+       else
+       {
+               font_size=12;
+               Parser::load(*font, "dejavu-12.font");
+       }
+
+       Parser::load(ui_res, "engineer.res");
+       main_panel=new MainPanel(*this, ui_res);
+       main_panel->set_position(0, screen_h-main_panel->get_geometry().h);
+
+       Application::main();
+
+       delete font;
+       delete main_panel;
+
+       SDL_Quit();
+
+       return exit_code;
+}
+
+void Engineer::tick()
+{
+       //cout<<"tick\n";
+
+       SDL_Event event;
+       while(SDL_PollEvent(&event))
+       {
+               switch(event.type)
+               {
+               case SDL_MOUSEBUTTONDOWN:
+                       button_press(event.button.x, screen_h-1-event.button.y, event.button.button);
+                       break;
+               case SDL_MOUSEBUTTONUP:
+                       button_release(event.button.x, screen_h-1-event.button.y, event.button.button);
+                       break;
+               case SDL_MOUSEMOTION:
+                       pointer_motion(event.motion.x, screen_h-1-event.motion.y);
+                       break;
+               case SDL_KEYDOWN:
+                       key_press(event.key.keysym.sym, event.key.keysym.mod);
+                       break;
+               case SDL_QUIT:
+                       exit(0);
+                       break;
+               }
+       }
+
+       control.tick();
+
+       glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+
+       project_3d();
+       glLoadIdentity();
+       glRotatef(-cam_rot*180/M_PI, 0, 0, 1);
+       glTranslatef(-cam_pos.x, -cam_pos.y, -cam_pos.z);
+
+       if(!no_lighting)
+       {
+               glEnable(GL_LIGHTING);
+               glEnable(GL_LIGHT0);
+               float params[4];
+               params[0]=0;
+               params[1]=-0.2;
+               params[2]=1;
+               params[3]=0;
+               glLightfv(GL_LIGHT0, GL_POSITION, params);
+       }
+
+       //glEnable(GL_DEPTH_TEST);
+       glEnable(GL_MULTISAMPLE);
+
+       layout_3d.render();
+
+       glDisable(GL_LIGHTING);
+       glColor4f(1, 1, 1, 1);
+       const Track3DSeq &ltracks=layout_3d.get_tracks();
+       for(Track3DSeq::const_iterator i=ltracks.begin(); i!=ltracks.end(); ++i)
+       {
+               Track &track=(*i)->get_track();
+               if(track.get_turnout_id())
+               {
+                       Turnout *trnt=control.get_turnout(track.get_turnout_id());
+                       if(trnt)
+                               (*i)->render_route(trnt->get_route());
+                       else
+                               (*i)->render_route(-1);
+               }
+               else
+                       (*i)->render_route(-1);
+       }
+
+       glMatrixMode(GL_PROJECTION);
+       glLoadIdentity();
+       glOrtho(0, screen_w, 0, screen_h, 0, 1);
+       glMatrixMode(GL_MODELVIEW);
+       glLoadIdentity();
+
+       glDisable(GL_DEPTH_TEST);
+       glDisable(GL_LIGHTING);
+       glDisable(GL_MULTISAMPLE);
+
+       main_panel->render();
+       for(TrainPanelSeq::iterator i=train_panels.begin(); i!=train_panels.end(); ++i)
+               (*i)->render();
+
+       glLoadIdentity();
+       glTranslatef(340, 10, 0);
+       glScalef(20, 20, 20);
+       glColor4f(1, 1, 1, 1);
+       font->draw_string(status_text);
+
+       SDL_GL_SwapBuffers();
+}
+
+void Engineer::key_press(unsigned, unsigned)
+{
+}
+
+void Engineer::button_press(int x, int y, unsigned btn)
+{
+       if(main_panel->get_geometry().is_inside(x, y))
+       {
+               main_panel->button_press(x, y, btn);
+               return;
+       }
+       for(TrainPanelSeq::iterator i=train_panels.begin(); i!=train_panels.end(); ++i)
+               if((*i)->get_geometry().is_inside(x, y))
+               {
+                       (*i)->button_press(x, y, btn);
+                       return;
+               }
+
+       Track3D *track=pick_track(x, y);
+       if(track)
+       {
+               if(placing_train)
+               {
+                       Section *sect=trfc_man->get_section_by_track(track->get_track());
+               }
+               else
+               {
+                       Turnout *turnout=control.get_turnout(track->get_track().get_turnout_id());
+                       if(turnout)
+                               turnout->set_route(1-turnout->get_route());
+               }
+       }
+}
+
+void Engineer::button_release(int x, int y, unsigned btn)
+{
+       if(main_panel->get_geometry().is_inside(x, y))
+       {
+               main_panel->button_release(x, y, btn);
+               return;
+       }
+       for(TrainPanelSeq::iterator i=train_panels.begin(); i!=train_panels.end(); ++i)
+               if((*i)->get_geometry().is_inside(x, y))
+               {
+                       (*i)->button_release(x, y, btn);
+                       return;
+               }
+}
+
+void Engineer::pointer_motion(int x, int y)
+{
+       if(main_panel->get_geometry().is_inside(x, y))
+       {
+               main_panel->pointer_motion(x, y);
+               return;
+       }
+       for(TrainPanelSeq::iterator i=train_panels.begin(); i!=train_panels.end(); ++i)
+               if((*i)->get_geometry().is_inside(x, y))
+               {
+                       (*i)->pointer_motion(x, y);
+                       return;
+               }
+
+       Track3D *track=pick_track(x, y);
+       if(track && track->get_track().get_turnout_id())
+       {
+               ostringstream ss;
+               ss<<"Turnout "<<track->get_track().get_turnout_id();
+               status_text=ss.str();
+       }
+       else
+               status_text="";
+}
+
+void Engineer::view_all()
+{
+       const Track3DSeq &tracks=layout_3d.get_tracks();
+
+       cam_rot=0;
+       float best_height=-1;
+       float mid_x=0;
+       float mid_y=0;
+       for(float angle=0; angle<M_PI; angle+=0.01)
+       {
+               float min_x=0;
+               float max_x=0;
+               float min_y=0;
+               float max_y=0;
+               for(Track3DSeq::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               {
+                       Point minp,maxp;
+                       (*i)->get_bounds(angle, minp, maxp);
+                       min_x=min(min_x, minp.x);
+                       max_x=max(max_x, maxp.x);
+                       min_y=min(min_y, minp.y);
+                       max_y=max(max_y, maxp.y);
+               }
+
+               float width=max_x-min_x;
+               float height=max_y-min_y;
+               height=max(height, width);
+
+               if(height<best_height || best_height<0)
+               {
+                       cam_rot=angle;
+                       best_height=height;
+                       mid_x=(min_x+max_x)/2;
+                       mid_y=(min_y+max_y)/2;
+               }
+       }
+
+       float c=cos(cam_rot);
+       float s=sin(cam_rot);
+       cam_pos.x=c*mid_x-s*mid_y;
+       cam_pos.y=s*mid_x+c*mid_y;
+       cam_pos.z=max(best_height*1.05/0.82843, 0.15);
+}
+
+void Engineer::turnout_route_changed(unsigned)
+{
+}
+
+void Engineer::sensor_state_changed(bool state, unsigned addr)
+{
+       const Track3DSeq &ltracks=layout_3d.get_tracks();
+       for(Track3DSeq::const_iterator i=ltracks.begin(); i!=ltracks.end(); ++i)
+               if((*i)->get_track().get_sensor_id()==addr)
+               {
+                       if(state)
+                               (*i)->set_color(Color(1, 0, 0));
+                       else
+                               (*i)->set_color(Color(1, 1, 1));
+               }
+}
+
+void Engineer::project_3d()
+{
+       glMatrixMode(GL_PROJECTION);
+       glLoadIdentity();
+       //glFrustum(-0.055228, 0.055228, -0.041421, 0.041421, 0.1, 10);
+       glFrustum(-0.069036, 0.041421, -0.041421, 0.041421, 0.1, 10);
+       glMatrixMode(GL_MODELVIEW);
+}
+
+Track3D *Engineer::pick_track(int x, int y)
+{
+       float xx=((float)(x-(int)screen_w*5/8)/screen_h)*0.82843;
+       //float xx=((float)(x-(int)screen_w/2)/screen_h)*0.82843;
+       float yy=((float)y/screen_h-0.5)*0.82843;
+       float size=(float)4/screen_h*0.82843;
+
+       project_3d();
+       glLoadIdentity();
+       glRotatef(-cam_rot*180/M_PI, 0, 0, 1);
+       glTranslatef(-cam_pos.x, -cam_pos.y, -cam_pos.z);
+
+       return layout_3d.pick_track(xx, yy, size);
+
+       /*unsigned select_buf[1024];
+       glSelectBuffer(1024, select_buf);
+       glRenderMode(GL_SELECT);
+
+       //XXX Hardcoded values
+       float xn=((float)(x-800)/960)*0.082843;
+       float yn=((float)(y-480)/960)*0.082843;
+       float size=(float)4/960*0.082843;
+
+       project_3d();
+       glLoadIdentity();
+
+       double clip[4];
+       clip[0]=0.1;
+       clip[1]=0;
+       clip[2]=xn-size;
+       clip[3]=0;
+       glClipPlane(GL_CLIP_PLANE0, clip);
+       glEnable(GL_CLIP_PLANE0);
+
+       clip[0]=-0.1;
+       clip[2]=-(xn+size);
+       glClipPlane(GL_CLIP_PLANE1, clip);
+       glEnable(GL_CLIP_PLANE1);
+
+       clip[0]=0;
+       clip[1]=0.1;
+       clip[2]=yn-size;
+       glClipPlane(GL_CLIP_PLANE2, clip);
+       glEnable(GL_CLIP_PLANE2);
+
+       clip[1]=-0.1;
+       clip[2]=-(yn+size);
+       glClipPlane(GL_CLIP_PLANE3, clip);
+       glEnable(GL_CLIP_PLANE3);
+
+       glRotatef(-cam_rot*180/M_PI, 0, 0, 1);
+       glTranslatef(-cam_pos.x, -cam_pos.y, -cam_pos.z);
+
+       layout_3d.render();
+
+       glDisable(GL_CLIP_PLANE0);
+       glDisable(GL_CLIP_PLANE1);
+       glDisable(GL_CLIP_PLANE2);
+       glDisable(GL_CLIP_PLANE3);
+
+       unsigned n_records=glRenderMode(GL_RENDER);
+       if(n_records)
+       {
+               Track *track=0;
+               unsigned i=0;
+               unsigned track_depth=numeric_limits<unsigned>::max();
+               for(unsigned j=0; j<n_records; ++j)
+               {
+                       unsigned ns_size=select_buf[i++];
+                       unsigned min_depth=select_buf[i++];
+                       ++i;  // Skip max_depth
+                       if(min_depth<track_depth)
+                       {
+                               track=(Track *)select_buf[i+ns_size-1];
+                               track_depth=min_depth;
+                       }
+                       i+=ns_size;
+               }
+
+               return track;
+       }
+
+       return 0;*/
+}
+
+Application::RegApp<Engineer> Engineer::reg;
diff --git a/source/engineer/engineer.h b/source/engineer/engineer.h
new file mode 100644 (file)
index 0000000..112e52f
--- /dev/null
@@ -0,0 +1,65 @@
+#ifndef ENGINEER_H_
+#define ENGINEER_H_
+
+#include <msp/core/application.h>
+#include <msp/gl/font.h>
+#include <msp/gltk/resources.h>
+#include "libmarklin/catalogue.h"
+#include "libmarklin/control.h"
+#include "libmarklin/trafficmanager.h"
+#include "3d/layout.h"
+
+class MainPanel;
+class TrainPanel;
+
+class Engineer: public Msp::Application
+{
+public:
+       Engineer(int argc, char **argv);
+       ~Engineer();
+
+       Marklin::Control &get_control()    { return control; }
+       unsigned get_screen_width() const  { return screen_w; }
+       unsigned get_screen_height() const { return screen_h; }
+       unsigned get_font_size() const     { return font_size; }
+       Msp::GL::Font &get_font()          { return *font; }
+       void     add_train(unsigned);
+       int      main();
+       void     quit() { exit(0); }
+private:
+       typedef std::list<TrainPanel *> TrainPanelSeq;
+
+       unsigned           screen_w;
+       unsigned           screen_h;
+       unsigned           font_size;
+       bool               fullscreen;
+       Msp::GL::Font      *font;
+       Marklin::Catalogue catalogue;
+       Marklin::Layout    layout;
+       Marklin::Layout3D  layout_3d;
+       Marklin::Control   control;
+       Marklin::Point     cam_pos;
+       float              cam_rot;
+       Msp::GLtk::Resources ui_res;
+       MainPanel          *main_panel;
+       TrainPanelSeq      train_panels;
+       std::string        status_text;
+       bool               no_lighting;
+       Marklin::TrafficManager *trfc_mgr;
+       Train              *placing_train;
+
+       void tick();
+       void key_press(unsigned, unsigned);
+       void button_press(int, int, unsigned);
+       void button_release(int, int, unsigned);
+       void pointer_motion(int, int);
+       void view_all();
+       void turnout_route_changed(unsigned);
+       void sensor_state_changed(bool, unsigned);
+       void project_3d();
+       Marklin::Track3D *pick_track(int, int);
+
+       static Msp::Application::RegApp<Engineer> reg;
+};
+
+#endif
diff --git a/source/engineer/guicomponent.h b/source/engineer/guicomponent.h
new file mode 100644 (file)
index 0000000..f752936
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef GUICOMPONENT_H_
+#define GUICOMPONENT_H_
+
+class GuiComponent
+{
+public:
+       enum HAlign
+       {
+               LEFT,
+               CENTER,
+               RIGHT
+       };
+
+       enum VAlign
+       {
+               TOP,
+               MIDDLE,
+               BOTTOM
+       };
+
+       struct Part
+       {
+               enum Type
+               {
+                       IMAGE,
+                       LABEL,
+                       TEXT
+               };
+
+               std::string variation;
+               std::string state;
+               Type type;
+               std::string data;
+               HAlign align_x;
+               VAlign align_y;
+               int offset_x;
+               int offset_y;
+       };
+       typedef std::list<Part> PartSeq;
+
+private:
+};
+
+#endif
diff --git a/source/engineer/guiresources.h b/source/engineer/guiresources.h
new file mode 100644 (file)
index 0000000..86d29ce
--- /dev/null
@@ -0,0 +1,4 @@
+#ifndef GUIRESOURCES_H_
+#define GUIRESOURCES_H_
+
+#endif
diff --git a/source/engineer/mainpanel.cpp b/source/engineer/mainpanel.cpp
new file mode 100644 (file)
index 0000000..7e68c03
--- /dev/null
@@ -0,0 +1,70 @@
+#include <msp/gltk/button.h>
+#include "engineer.h"
+#include "mainpanel.h"
+
+using namespace Msp;
+
+MainPanel::MainPanel(Engineer &e, GLtk::Resources &r):
+       Panel(r),
+       engineer(e)
+{
+       set_size(200, 100);
+
+       GLtk::Button *btn;
+
+       add(*(btn=new GLtk::Button(res, "Off")));
+       btn->set_geometry(GLtk::Geometry(10, 50, 40, 25));
+       btn->set_style("red");
+       btn->signal_clicked.connect(sigc::mem_fun(this, &MainPanel::power_off));
+
+       add(*(ind_off=new GLtk::Indicator(res)));
+       ind_off->set_geometry(GLtk::Geometry(10, 78, 40, 12));
+       ind_off->set_style("red");
+
+       add(*(btn=new GLtk::Button(res, "On")));
+       btn->set_geometry(GLtk::Geometry(50, 50, 40, 25));
+       btn->set_style("green");
+       btn->signal_clicked.connect(sigc::mem_fun(this, &MainPanel::power_on));
+
+       add(*(ind_on=new GLtk::Indicator(res)));
+       ind_on->set_geometry(GLtk::Geometry(50, 78, 40, 12));
+       ind_on->set_style("green");
+
+       add(*(btn=new GLtk::Button(res, "Quit")));
+       btn->set_geometry(GLtk::Geometry(150, 50, 40, 25));
+       btn->set_style("red");
+       btn->signal_clicked.connect(sigc::mem_fun(this, &MainPanel::quit));
+
+       add(*(btn=new GLtk::Button(res, "+Loc")));
+       btn->set_geometry(GLtk::Geometry(10, 10, 40, 25));
+       btn->signal_clicked.connect(sigc::mem_fun(this, &MainPanel::new_loc));
+
+       if(engineer.get_control().get_power())
+               ind_on->set_active(true);
+       else
+               ind_off->set_active(true);
+}
+
+void MainPanel::power_on()
+{
+       engineer.get_control().set_power(true);
+       ind_on->set_active(true);
+       ind_off->set_active(false);
+}
+
+void MainPanel::power_off()
+{
+       engineer.get_control().set_power(false);
+       ind_on->set_active(false);
+       ind_off->set_active(true);
+}
+
+void MainPanel::new_loc()
+{
+       engineer.add_train(0);
+}
+
+void MainPanel::quit()
+{
+       engineer.quit();
+}
diff --git a/source/engineer/mainpanel.h b/source/engineer/mainpanel.h
new file mode 100644 (file)
index 0000000..3d9a2b7
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef MAINPANEL_H_
+#define MAINPANEL_H_
+
+#include <msp/gltk/indicator.h>
+#include <msp/gltk/panel.h>
+
+class Engineer;
+
+class MainPanel: public Msp::GLtk::Panel
+{
+public:
+       MainPanel(Engineer &, Msp::GLtk::Resources &);
+private:
+       Engineer &engineer;
+       Msp::GLtk::Indicator *ind_on;
+       Msp::GLtk::Indicator *ind_off;
+
+       void power_on();
+       void power_off();
+       void new_loc();
+       void quit();
+};
+
+#endif
diff --git a/source/engineer/trainpanel.cpp b/source/engineer/trainpanel.cpp
new file mode 100644 (file)
index 0000000..d9a8c77
--- /dev/null
@@ -0,0 +1,36 @@
+#include <msp/gltk/button.h>
+#include "trainpanel.h"
+
+using namespace Msp;
+using namespace Marklin;
+
+TrainPanel::TrainPanel(Engineer &e, const GLtk::Resources &r, Train &t):
+       Panel(r),
+       engineer(e),
+       train(t)
+{
+       set_size(200, 100);
+
+       add(*(lbl_name=new GLtk::Label(res, "Train 1")));
+       lbl_name->set_style("digital");
+       lbl_name->set_geometry(GLtk::Geometry(10, geom.h-34, 140, 24));
+
+       GLtk::Button *btn;
+
+       add(*(btn=new GLtk::Button(res, "Name")));
+       btn->set_geometry(GLtk::Geometry(150, geom.h-34, 40, 24));
+
+       add(*(sld_speed=new GLtk::HSlider(res)));
+       sld_speed->set_geometry(GLtk::Geometry(10, geom.h-49, 180, 10));
+       sld_speed->set_range(0, 14);
+       sld_speed->set_step(1);
+       sld_speed->signal_value_changed.connect(sigc::mem_fun(this, &TrainPanel::speed_slider_changed));
+
+       add(*(btn=new GLtk::Button(res, "Place")));
+       btn->set_geometry(GLtk::Geometry(150, geom.h-75, 40, 24));
+}
+
+void TrainPanel::speed_slider_changed(double v)
+{
+       train.set_speed(static_cast<unsigned>(v));
+}
diff --git a/source/engineer/trainpanel.h b/source/engineer/trainpanel.h
new file mode 100644 (file)
index 0000000..79b9d49
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef TRAINPANEL_H_
+#define TRAINPANEL_H_
+
+#include <msp/gltk/hslider.h>
+#include <msp/gltk/label.h>
+#include <msp/gltk/panel.h>
+#include "libmarklin/train.h"
+
+class Engineer;
+
+class TrainPanel: public Msp::GLtk::Panel
+{
+public:
+       TrainPanel(Engineer &, const Msp::GLtk::Resources &, Marklin::Train &);
+private:
+       Engineer &engineer;
+       Marklin::Train &train;
+       Msp::GLtk::Label *lbl_name;
+       Msp::GLtk::HSlider *sld_speed;
+       Marklin::Locomotive *loco;
+
+       void speed_slider_changed(double);
+};
+
+#endif
diff --git a/source/libmarklin/catalogue.cpp b/source/libmarklin/catalogue.cpp
new file mode 100644 (file)
index 0000000..3c8e279
--- /dev/null
@@ -0,0 +1,63 @@
+#include <fstream>
+#include <msp/parser/parser.h>
+#include "catalogue.h"
+#include "track.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace Marklin {
+
+Track *Catalogue::get_track(unsigned art_nr)
+{
+       TrackMap::const_iterator i=tracks.find(art_nr);
+
+       if(i!=tracks.end())
+               return i->second;
+
+       return 0;
+}
+
+void Catalogue::load(const string &fn)
+{
+       ifstream in(fn.c_str());
+       if(!in)
+               throw Exception("File not found");
+
+       Parser::Parser parser(in, fn);
+       Loader loader(*this);
+       loader.load(parser);
+}
+
+Catalogue::~Catalogue()
+{
+       for(TrackMap::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               delete i->second;
+}
+
+Catalogue::Loader::Loader(Catalogue &c):
+       cat(c)
+{
+       add("track", &Loader::track);
+}
+
+void Catalogue::Loader::track(unsigned art_no)
+{
+       TrackMap::iterator i=cat.tracks.find(art_no);
+       if(i!=cat.tracks.end())
+               throw Exception("Duplicate track number");
+
+       Track *trk=new Track(art_no);
+       try
+       {
+               load_sub(*trk);
+       }
+       catch(const Msp::Exception &)
+       {
+               delete trk;
+               throw;
+       }
+       cat.tracks.insert(TrackMap::value_type(trk->get_article_number(), trk));
+}
+
+} // namespace Marklin
diff --git a/source/libmarklin/catalogue.h b/source/libmarklin/catalogue.h
new file mode 100644 (file)
index 0000000..1a328bb
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef LIBMARKLIN_CATALOGUE_H_
+#define LIBMARKLIN_CATALOGUE_H_
+
+#include <map>
+#include <msp/parser/loader.h>
+
+namespace Marklin {
+
+class Track;
+
+class Catalogue
+{
+public:
+       class Loader: public Msp::Parser::Loader
+       {
+       public:
+               Loader(Catalogue &);
+       private:
+               Catalogue &cat;
+
+               void track(unsigned);
+       };
+       typedef std::map<unsigned, Track *> TrackMap;
+
+       Track *get_track(unsigned);
+       const TrackMap &get_tracks() const { return tracks; }
+       void  load(const std::string &);
+       ~Catalogue();
+private:
+       TrackMap tracks;
+};
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/command.h b/source/libmarklin/command.h
new file mode 100644 (file)
index 0000000..2106d23
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef COMMAND_H_
+#define COMMAND_H_
+
+#include <string>
+#include <sigc++/sigc++.h>
+#include "constants.h"
+
+namespace Marklin {
+
+class Command
+{
+public:
+       sigc::signal<void, Error, const std::string &> signal_done;
+
+       Command(const std::string &c): cmd(c), sent(false) { }
+       void              set_sent(bool s)     { sent=s; }
+       const std::string &get_command() const { return cmd; }
+       bool              get_sent() const     { return sent; }
+private:
+       std::string cmd;
+       bool        sent;
+};
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/constants.h b/source/libmarklin/constants.h
new file mode 100644 (file)
index 0000000..2db75c2
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef ERROR_H_
+#define ERROR_H_
+
+namespace Marklin {
+
+enum Error
+{
+       ERR_NO_ERROR=0,
+       ERR_SYS_ERROR,
+       ERR_BAD_PARAM,
+       ERR_POWER_OFF=0x6,
+       ERR_NO_LOK_SPACE=0x8,  // No space in lok command buffer
+       ERR_NO_TURNOUT_SPACE,  // No space in turnout command buffer
+       ERR_NO_DATA,           // "no Lok status available (Lok is not in a slot)"
+       ERR_NO_SLOT,           // "there is no slot available"
+       ERR_BAD_LOK_ADDR,
+       ERR_LOK_BUSY,
+       ERR_BAD_TURNOUT_ADDR,
+       ERR_BAD_SO_VALUE,
+       ERR_NO_I2C_SPACE,
+       ERR_LOW_TURNOUT_SPACE=0x40,
+       ERR_LOK_HALTED,
+       ERR_LOK_POWER_OFF,
+       ERR_UNKNOWN_ERROR=0xFF
+};
+
+enum Cmd
+{
+       CMD_LOK=0x80,
+       CMD_LOK_STATUS=0x84,
+       CMD_LOK_CONFIG=0x85,
+       CMD_FUNC=0x88,
+       CMD_FUNC_STATUS=0x8C,
+       CMD_TURNOUT=0x90,
+       CMD_TURNOUT_FREE=0x93,
+       CMD_TURNOUT_STATUS=0x94,
+       CMD_TURNOUT_GROUP_STATUS=0x95,
+       CMD_SENSOR_STATUS=0x98,
+       CMD_SENSOR_REPORT=0x99,
+       CMD_SENSOR_PARAM_SET=0x9D,
+       CMD_POWER_OFF=0xA6,
+       CMD_POWER_ON=0xA7,
+       CMD_NOP=0xC4,
+       CMD_EVENT=0xC8,
+       CMD_EVENT_LOK=0xC9,
+       CMD_EVENT_TURNOUT=0xCA,
+       CMD_EVENT_SENSOR=0xCB
+};
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/control.cpp b/source/libmarklin/control.cpp
new file mode 100644 (file)
index 0000000..d371777
--- /dev/null
@@ -0,0 +1,329 @@
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/poll.h>
+#include <iostream>
+#include <msp/core/error.h>
+#include <msp/time/units.h>
+#include <msp/time/utils.h>
+#include "command.h"
+#include "control.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace Marklin {
+
+Control::Control():
+       serial_fd(-1),
+       p50_enabled(false),
+       power(true),
+       poll_sensors(false),
+       debug(false)
+{ }
+
+void Control::set_power(bool p)
+{
+       power=p;
+       if(power)
+               command(string(1, CMD_POWER_ON));
+       else
+               command(string(1, CMD_POWER_OFF));
+}
+
+Turnout *Control::get_turnout(unsigned id) const
+{
+       TurnoutMap::const_iterator i=turnouts.find(id);
+       if(i!=turnouts.end())
+               return i->second;
+       
+       return 0;
+}
+
+Locomotive *Control::get_locomotive(unsigned id) const
+{
+       for(LocomotiveSeq::const_iterator i=locomotives.begin(); i!=locomotives.end(); ++i)
+               if((*i)->get_address()==id)
+                       return *i;
+       return 0;
+}
+
+Sensor *Control::get_sensor(unsigned id) const
+{
+       SensorMap::const_iterator i=sensors.find(id);
+       if(i!=sensors.end())
+               return i->second;
+       
+       return 0;
+}
+
+void Control::open(const string &dev)
+{
+       serial_fd=::open(dev.c_str(), O_RDWR);
+       if(serial_fd<0)
+               throw Exception("Couldn't open serial port\n");
+
+       static unsigned baud[]=
+       {
+                2400, B2400,
+                4800, B4800,
+                9600, B9600,
+               19200, B19200,
+               0
+       };
+
+       termios attr;
+       tcgetattr(serial_fd, &attr);
+       cfmakeraw(&attr);
+       attr.c_cflag|=CSTOPB;
+
+       bool ok=false;
+       for(unsigned i=0; baud[i]; i+=2)
+       {
+               cfsetospeed(&attr, baud[i+1]);
+               tcsetattr(serial_fd, TCSADRAIN, &attr);
+
+               write(serial_fd, "\xC4", 1);
+
+               pollfd pfd={serial_fd, POLLIN, 0};
+               if(poll(&pfd, 1, 500)>0)
+               {
+                       cout<<"IB detected at "<<baud[i]<<" bits/s, P50 is ";
+                       char buf[2];
+                       if(read(serial_fd, buf, 2)==2)
+                       {
+                               p50_enabled=true;
+                               cout<<"enabled";
+                       }
+                       else
+                       {
+                               p50_enabled=false;
+                               cout<<"disabled";
+                       }
+                       cout<<'\n';
+                       ok=true;
+                       break;
+               }
+       }
+       if(!ok)
+               throw Exception("IB not detected");
+}
+
+Command &Control::command(const string &cmd)
+{
+       queue.push_back(Command(cmd));
+       return queue.back();
+}
+
+void Control::add_turnout(Turnout *t)
+{
+       if(turnouts.count(t->get_address())==0)
+               turnouts.insert(TurnoutMap::value_type(t->get_address(), t));
+}
+
+void Control::add_locomotive(Locomotive *l)
+{
+       if(find(locomotives.begin(), locomotives.end(), l)==locomotives.end())
+               locomotives.push_back(l);
+}
+
+void Control::add_sensor(Sensor *s)
+{
+       if(sensors.count(s->get_address())==0)
+       {
+               sensors.insert(SensorMap::value_type(s->get_address(), s));
+               poll_sensors=true;
+       }
+}
+
+void Control::tick()
+{
+       const Time::TimeStamp t=Time::now();
+
+       if(t>next_event_query)
+       {
+               next_event_query=t+300*Time::msec;
+               command(string(1, CMD_EVENT)).signal_done.connect(sigc::mem_fun(this, &Control::event_query_done));
+       }
+
+       if(poll_sensors)
+       {
+               unsigned max_addr=(--sensors.end())->second->get_address();
+               string cmd(3, 0);
+               cmd[0]=CMD_SENSOR_PARAM_SET;
+               cmd[1]=0;
+               cmd[2]=(max_addr+7)/8;
+               command(cmd);
+               command(string(1, CMD_SENSOR_REPORT));
+               poll_sensors=false;
+       }
+
+       if(queue.size() && queue.front().get_sent())
+       {
+               pollfd pfd={serial_fd, POLLIN, 0};
+               if(poll(&pfd, 1, 0)>0)
+               {
+                       string resp=read_reply((Cmd)(unsigned char)queue.front().get_command()[0]);
+                       if(debug)
+                       {
+                               printf("read:  ");
+                               for(unsigned i=0; i<resp.size(); ++i)
+                                       printf("%02X ", (unsigned char)resp[i]);
+                               printf("(%d bytes)\n", resp.size());
+                       }
+
+                       queue.front().signal_done.emit((Error)resp[0], resp.substr(1));
+                       queue.erase(queue.begin());
+               }
+               else
+                       return;
+       }
+
+       if(queue.size())
+       {
+               string cmd=queue.front().get_command();
+
+               if(p50_enabled)
+               {
+                       if(cmd[0]&0x80)
+                               cmd="X"+cmd;
+                       else
+                               cmd="x"+cmd;
+               }
+
+               if(debug)
+               {
+                       printf("write: ");
+                       for(unsigned i=0; i<cmd.size(); ++i)
+                               printf("%02X ",(unsigned char)cmd[i]);
+                       printf("(%d bytes)\n",cmd.size());
+               }
+
+               write(serial_fd, cmd.data(), cmd.size());
+               queue.front().set_sent(true);
+       }
+}
+
+Control::~Control()
+{
+       close(serial_fd);
+}
+
+/*** private ***/
+
+void Control::read_all(int fd, char *buf, int size)
+{
+       int pos=0;
+       while(pos<size)
+       {
+               int len=read(fd, buf+pos, size-pos);
+               pos+=len;
+       }
+}
+
+string Control::read_reply(Cmd cmd)
+{
+       string result;
+       if(cmd==CMD_EVENT)
+       {
+               result+=ERR_NO_ERROR;
+               for(unsigned i=0; i<3; ++i)
+               {
+                       char c;
+                       read(serial_fd, &c, 1);
+                       result+=c;
+                       if(!(c&0x80)) break;
+               }
+       }
+       else if(cmd==CMD_EVENT_LOK)
+       {
+               result+=ERR_NO_ERROR;
+               char c[5];
+               read(serial_fd, c+4, 1);
+               result+=c[4];
+               while(c[4]&0x80)
+               {
+                       read_all(serial_fd, c, 5);
+                       result.append(c, 5);
+               }
+       }
+       else if(cmd==CMD_EVENT_TURNOUT)
+       {
+               result+=ERR_NO_ERROR;
+               char c[511];
+               read(serial_fd, c, 1);
+               read_all(serial_fd, c+1, c[0]*2);
+               result.append(c, c[0]*2+1);
+       }
+       else if(cmd==CMD_EVENT_SENSOR)
+       {
+               result+=ERR_NO_ERROR;
+               char c[3];
+               read(serial_fd, c+2, 1);
+               result+=c[2];
+               while(c[2])
+               {
+                       read_all(serial_fd, c, 3);
+                       result.append(c, 3);
+               }
+       }
+       else
+       {
+               unsigned expected_bytes=1;
+               if(cmd==CMD_FUNC_STATUS || cmd==CMD_TURNOUT_STATUS)
+                       expected_bytes=2;
+               if(cmd==CMD_SENSOR_STATUS || cmd==CMD_TURNOUT_GROUP_STATUS)
+                       expected_bytes=3;
+               if(cmd==CMD_LOK_STATUS)
+                       expected_bytes=4;
+               if(cmd==CMD_LOK_CONFIG)
+                       expected_bytes=5;
+               char c[5];
+               read_all(serial_fd, c, 1);
+               result+=c[0];
+               if(!c[0])
+               {
+                       read_all(serial_fd, c+1, expected_bytes-1);
+                       result.append(c+1, expected_bytes-1);
+               }
+       }
+
+       return result;
+}
+
+void Control::event_query_done(Error, const string &resp)
+{
+       if(resp[0]&0x20)
+               command(string(1, CMD_EVENT_TURNOUT)).signal_done.connect(sigc::mem_fun(this, &Control::turnout_event_done));
+       if(resp[0]&0x04)
+               command(string(1, CMD_EVENT_SENSOR)).signal_done.connect(sigc::mem_fun(this, &Control::sensor_event_done));
+}
+
+void Control::turnout_event_done(Error, const string &resp)
+{
+       unsigned count=resp[0];
+       for(unsigned i=0; i<count; ++i)
+       {
+               unsigned addr=(unsigned char)resp[i*2+1]+((resp[i*2+2]&7)<<8);
+               bool status=!(resp[i*2+2]&0x80);
+               cout<<"Turnout "<<addr<<", status "<<status<<'\n';
+               signal_turnout_event.emit(addr, status);
+       }
+}
+
+void Control::sensor_event_done(Error, const string &resp)
+{
+       for(unsigned i=0; resp[i]; i+=3)
+       {
+               unsigned module=(unsigned char)resp[i];
+               cout<<"S88 module "<<module<<", status ";
+               for(unsigned j=0; j<16; ++j)
+               {
+                       bool status=(resp[i+1+j/8]>>(7-j%8))&1;
+                       cout<<status;
+                       signal_sensor_event.emit(module*16+j-15, status);
+               }
+               cout<<'\n';
+       }
+}
+
+} // namespace Marklin
diff --git a/source/libmarklin/control.h b/source/libmarklin/control.h
new file mode 100644 (file)
index 0000000..62d64f9
--- /dev/null
@@ -0,0 +1,59 @@
+#ifndef LIBMARKLIN_CONTROL_H_
+#define LIBMARKLIN_CONTROL_H_
+
+#include <list>
+#include <string>
+#include <msp/time/timestamp.h>
+#include "constants.h"
+#include "sensor.h"
+#include "locomotive.h"
+#include "turnout.h"
+
+namespace Marklin {
+
+class Command;
+
+class Control
+{
+public:
+       sigc::signal<void, unsigned, bool> signal_turnout_event;
+       sigc::signal<void, unsigned, bool> signal_sensor_event;
+
+       Control();
+       void       set_power(bool);
+       void       set_debug(bool d) { debug=d; }
+       bool       get_power() const { return power; }
+       const TurnoutMap &get_turnouts() const { return turnouts; }
+       Turnout    *get_turnout(unsigned) const;
+       Locomotive *get_locomotive(unsigned) const;
+       Sensor     *get_sensor(unsigned) const;
+       unsigned   get_queue_length() const { return queue.size(); }
+       void       open(const std::string &);
+       Command    &command(const std::string &);
+       void       add_turnout(Turnout *);
+       void       add_locomotive(Locomotive *);
+       void       add_sensor(Sensor *);
+       void       tick();
+       ~Control();
+private:
+       int  serial_fd;
+       bool p50_enabled;
+       bool power;
+       std::list<Command> queue;
+       TurnoutMap turnouts;
+       LocomotiveSeq locomotives;
+       SensorMap sensors;
+       Msp::Time::TimeStamp next_event_query;
+       bool poll_sensors;
+       bool debug;
+
+       void read_all(int, char *, int);
+       std::string read_reply(Cmd);
+       void event_query_done(Error, const std::string &);
+       void turnout_event_done(Error, const std::string &);
+       void sensor_event_done(Error, const std::string &);
+};
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/geometry.h b/source/libmarklin/geometry.h
new file mode 100644 (file)
index 0000000..9dee86e
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef LIBMARKLIN_GEOMETRY_H_
+#define LIBMARKLIN_GEOMETRY_H_
+
+#include <vector>
+
+namespace Marklin {
+
+struct Point
+{
+       float x,y,z;
+
+       Point(): x(0), y(0), z(0) { }
+       Point(float x_, float y_, float z_): x(x_), y(y_), z(z_) { }
+};
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/layout.cpp b/source/libmarklin/layout.cpp
new file mode 100644 (file)
index 0000000..1fe06c7
--- /dev/null
@@ -0,0 +1,134 @@
+#include <algorithm>
+#include <fstream>
+#include <msp/parser/parser.h>
+#include "catalogue.h"
+#include "layout.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace Marklin {
+
+Layout::Layout(Catalogue &c):
+       catalogue(c)
+{ }
+
+void Layout::add_track(Track *t)
+{
+       if(find(tracks.begin(), tracks.end(), t)==tracks.end())
+       {
+               tracks.push_back(t);
+               signal_track_added.emit(t);
+       }
+}
+
+void Layout::remove_track(Track *t)
+{
+       TrackSeq::iterator i=remove_if(tracks.begin(), tracks.end(), bind1st(equal_to<Track *>(), t));
+       if(i!=tracks.end())
+       {
+               tracks.erase(i, tracks.end());
+               signal_track_removed.emit(t);
+       }
+}
+
+void Layout::check_links()
+{
+       for(TrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               (*i)->break_links();
+
+       for(TrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               for(TrackSeq::iterator j=i; j!=tracks.end(); ++j)
+                       if(j!=i)
+                               (*i)->snap_to(**j, true);
+}
+
+void Layout::load(const string &fn)
+{
+       ifstream in(fn.c_str());
+       if(!in)
+               throw Exception("Couldn't open file");
+
+       filename=fn;
+       Parser::Parser parser(in, fn);
+       Loader loader(*this);
+       loader.load(parser);
+
+       check_links();
+
+       for(TrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               (*i)->check_slope();
+}
+
+int Layout::save(const string &fn)
+{
+       ofstream out(fn.c_str());
+       if(!out) return -1;
+
+       filename=fn;
+
+       if(base.size())
+               out<<"base \""<<base<<"\";\n";
+       for(TrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               out<<"track "<<(*i)->get_article_number()<<"\n{\n";
+               const Point &p=(*i)->get_position();
+               out<<"\tposition "<<p.x<<' '<<p.y<<' '<<p.z<<";\n";
+               out<<"\trotation "<<(*i)->get_rotation()<<";\n";
+               out<<"\tslope "<<(*i)->get_slope()<<";\n";
+
+               unsigned id=(*i)->get_turnout_id();
+               if(id)
+                       out<<"\tturnout_id "<<id<<";\n";
+
+               id=(*i)->get_sensor_id();
+               if(id)
+                       out<<"\tsensor_id "<<id<<";\n";
+
+               if((*i)->get_flex())
+                       out<<"\tflex true;\n";
+
+               out<<"};\n";
+       }
+
+       return 0;
+}
+
+Layout::~Layout()
+{
+       for(TrackSeq::iterator i=tracks.begin(); i!=tracks.end(); ++i)
+               delete *i;
+}
+
+/*******************
+** Layout::Loader
+*/
+
+Layout::Loader::Loader(Layout &l):
+       layout(l)
+{
+       add("base",  &Layout::base);
+       add("track", &Loader::track);
+}
+
+void Layout::Loader::track(unsigned art_nr)
+{
+       Track *tmpl=layout.catalogue.get_track(art_nr);
+       if(!tmpl)
+               throw Exception("Unknown track");
+
+       Track *trk=tmpl->copy();
+       try
+       {
+               load_sub(*trk);
+       }
+       catch(Msp::Exception &e)
+       {
+               delete trk;
+               throw;
+       }
+       layout.tracks.push_back(trk);
+       layout.signal_track_added.emit(trk);
+}
+
+} // namespace Marklin
diff --git a/source/libmarklin/layout.h b/source/libmarklin/layout.h
new file mode 100644 (file)
index 0000000..c292af6
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef LIBMARKLIN_LAYOUT_H_
+#define LIBMARKLIN_LAYOUT_H_
+
+#include <sigc++/sigc++.h>
+#include <msp/parser/loader.h>
+#include "route.h"
+#include "track.h"
+
+namespace Marklin {
+
+class Catalogue;
+
+class Layout
+{
+public:
+       class Loader: public Msp::Parser::Loader
+       {
+       public:
+               Loader(Layout &);
+               Layout &get_object() { return layout; }
+       private:
+               Layout &layout;
+
+               void track(unsigned);
+       };
+
+       sigc::signal<void, Track *> signal_track_added;
+       sigc::signal<void, Track *> signal_track_removed;
+
+       Layout(Catalogue &);
+       const TrackSeq &get_tracks() const { return tracks; }
+       void  add_track(Track *);
+       void  remove_track(Track *);
+       void  check_links();
+       void  load(const std::string &);
+       int   save(const std::string &);
+       ~Layout();
+private:
+       Catalogue   &catalogue;
+       std::string filename;
+       std::string base;
+       TrackSeq    tracks;
+       RouteSeq    routes;
+};
+
+} // namespace Marklin
+
+#endif
+
diff --git a/source/libmarklin/locomotive.cpp b/source/libmarklin/locomotive.cpp
new file mode 100644 (file)
index 0000000..ecacea1
--- /dev/null
@@ -0,0 +1,116 @@
+#include <msp/time/timer.h>
+#include <msp/time/units.h>
+#include "command.h"
+#include "constants.h"
+#include "control.h"
+#include "locomotive.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace Marklin {
+
+Locomotive::Locomotive(Control &c, unsigned a):
+       control(c),
+       addr(a),
+       speed(0),
+       reverse(false),
+       funcs(0)
+{
+       control.add_locomotive(this);
+
+       refresh_status();
+}
+
+void Locomotive::set_speed(unsigned spd)
+{
+       speed=min(spd, 14U);
+
+       send_command(false);
+}
+
+void Locomotive::set_reverse(bool rev)
+{
+       if(rev==reverse)
+               return;
+
+       if(speed)
+       {
+               (new Time::Timer((500+speed*150)*Time::msec))->signal_timeout.connect(sigc::mem_fun(this, &Locomotive::reverse_timeout));
+               set_speed(0);
+       }
+       else
+       {
+               reverse=rev;
+               send_command(false);
+       }
+}
+
+void Locomotive::set_function(unsigned func, bool state)
+{
+       if(state)
+               funcs|=1<<func;
+       else
+               funcs&=~(1<<func);
+
+       send_command(true);
+}
+
+void Locomotive::refresh_status()
+{
+       char cmd[3];
+       cmd[0]=CMD_LOK_STATUS;
+       cmd[1]=addr&0xFF;
+       cmd[2]=(addr>>8)&0xFF;
+       control.command(string(cmd,3)).signal_done.connect(sigc::mem_fun(this,&Locomotive::status_reply));
+}
+
+void Locomotive::send_command(bool setf)
+{
+       char cmd[16];
+       cmd[0]=CMD_LOK;
+       cmd[1]=addr&0xFF;
+       cmd[2]=(addr>>8)&0xFF;
+
+       if(speed==0)
+               cmd[3]=0;
+       else if(speed==1)
+               cmd[3]=2;
+       else
+               cmd[3]=(speed*19-18)/2;
+       
+       cmd[4]=(reverse?0:0x20) | ((funcs&1)?0x10:0);
+
+       if(setf)
+       {
+               cmd[4]|=0x80;
+               for(unsigned i=0; i<4; ++i)
+                       if((funcs>>i)&2)
+                               cmd[4]|=(1<<i);
+       }
+       control.command(string(cmd,5));
+}
+
+void Locomotive::status_reply(Error err, const string &reply)
+{
+       if(err==ERR_NO_ERROR)
+       {
+               if((unsigned char)reply[0]<=1)
+                       speed=0;
+               else
+                       speed=(unsigned char)reply[0]*2/19+1;
+               reverse=(reply[1]&0x20)?false:true;
+               funcs=(reply[1]&0xF)<<1;
+               if(reply[1]&0x10)
+                       funcs|=1;
+       }
+}
+
+bool Locomotive::reverse_timeout()
+{
+       reverse=!reverse;
+       send_command(true);
+       return false;
+}
+
+} // namespace Marklin
diff --git a/source/libmarklin/locomotive.h b/source/libmarklin/locomotive.h
new file mode 100644 (file)
index 0000000..68e1330
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef LIBMARKLIN_LOCOMOTIVE_H_
+#define LIBMARKLIN_LOCOMOTIVE_H_
+
+#include <list>
+#include <string>
+#include "constants.h"
+
+namespace Marklin {
+
+class Control;
+
+class Locomotive
+{
+public:
+       Locomotive(Control &, unsigned);
+       void     set_speed(unsigned);
+       void     set_reverse(bool);
+       void     set_function(unsigned, bool);
+       unsigned get_address() const   { return addr; }
+       unsigned get_speed() const     { return speed; }
+       bool     get_reverse() const   { return reverse; }
+       bool     get_function(unsigned f) const { return (funcs>>f)&1; }
+       void     refresh_status();
+private:
+       Control  &control;
+       unsigned addr;
+       unsigned speed;
+       bool     reverse;
+       unsigned funcs;
+
+       void     send_command(bool);
+       void     status_reply(Error, const std::string &);
+       bool     reverse_timeout();
+};
+typedef std::list<Locomotive *> LocomotiveSeq;
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/route.cpp b/source/libmarklin/route.cpp
new file mode 100644 (file)
index 0000000..9894398
--- /dev/null
@@ -0,0 +1,7 @@
+#include "route.h"
+
+namespace Marklin {
+
+
+
+} // namespace Marklin
diff --git a/source/libmarklin/route.h b/source/libmarklin/route.h
new file mode 100644 (file)
index 0000000..f5b7602
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef LIBMARKLIN_ROUTE_H_
+#define LIBMARKLIN_ROUTE_H_
+
+#include <list>
+#include <string>
+#include "track.h"
+
+namespace Marklin {
+
+class Route
+{
+public:
+       typedef std::map<unsigned, unsigned> TurnoutMap;
+
+       Route();
+       const TurnoutMap &get_turnouts() const { return turnouts; }
+       void add_track(Track *);
+       ~Route();
+private:
+       std::string name;
+       TrackSeq    tracks;
+       TurnoutMap  turnouts;
+};
+typedef std::list<Route *> RouteSeq;
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/section.cpp b/source/libmarklin/section.cpp
new file mode 100644 (file)
index 0000000..17a0ce4
--- /dev/null
@@ -0,0 +1,183 @@
+#include "control.h"
+#include "section.h"
+#include "trafficmanager.h"
+#include "turnout.h"
+
+using namespace Msp;
+
+#include <iostream>
+using namespace std;
+
+namespace Marklin {
+
+Section::Section(TrafficManager &tm, Track *start):
+       trfc_mgr(tm),
+       id(next_id++),
+       sensor_id(0),
+       train(0)
+{
+       tracks.insert(start);
+
+       if(start->get_sensor_id())
+       {
+               sensor_id=start->get_sensor_id();
+               const Track::EndpointSeq &eps=start->get_endpoints();
+               for(Track::EndpointSeq::const_iterator i=eps.begin(); i!=eps.end(); ++i)
+                       endpoints.push_back(Endpoint(start, &*i));
+       }
+       else
+       {
+               TrackSeq queue;
+               queue.push_back(start);
+
+               while(!queue.empty())
+               {
+                       Track *track=queue.front();
+                       queue.erase(queue.begin());
+
+                       const Track::EndpointSeq &eps=track->get_endpoints();
+                       for(Track::EndpointSeq::const_iterator i=eps.begin(); i!=eps.end(); ++i)
+                               if(i->link && tracks.count(i->link)==0)
+                               {
+                                       if(!i->link->get_sensor_id())
+                                       {
+                                               queue.push_back(i->link);
+                                               tracks.insert(i->link);
+                                       }
+                                       else
+                                               endpoints.push_back(Endpoint(track, &*i));
+                               }
+               }
+       }
+
+       unsigned n=0;
+       for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i, ++n)
+       {
+               unsigned route=1<<n;
+               i->routes|=route;
+               set<Track *> visited;
+               find_routes(i->track, i->track_ep, route, visited);
+       }
+}
+
+const Section::Endpoint *Section::get_endpoint_by_link(const Section *other) const
+{
+       for(EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+               if(i->link==other)
+                       return &*i;
+
+       return 0;
+}
+
+const Section::Endpoint *Section::traverse(const Endpoint *ep) const
+{
+       Track *track=ep->track;
+       const Track::Endpoint *track_ep=ep->track_ep;
+
+       while(1)
+       {
+               unsigned cur_route=0;
+               unsigned tid=track->get_turnout_id();
+               if(tid)
+               {
+                       Turnout *turnout=trfc_mgr.get_control().get_turnout(tid);
+                       if(turnout)
+                               cur_route=turnout->get_route();
+               }
+
+               const Track::Endpoint *other_ep=track->traverse(track_ep, cur_route);
+               if(!other_ep)
+                       return 0;
+
+               for(EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+                       if(i->track==track && i->track_ep==other_ep)
+                               return &*i;
+
+               track_ep=track->get_endpoint_by_link(other_ep->link);
+               track=other_ep->link;
+
+               if(tracks.count(track)==0)
+                       return 0;
+       }
+}
+
+void Section::check_link(Section &other)
+{
+       for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+       {
+               if(i->link)
+                       continue;
+
+               for(EndpointSeq::iterator j=other.endpoints.begin(); j!=other.endpoints.end(); ++j)
+                       if(j->track==i->track_ep->link && j->track_ep->link==i->track && !j->link)
+                       {
+                               i->link=&other;
+                               j->link=this;
+                       }
+       }
+}
+
+bool Section::reserve(const Train *t)
+{
+       if(!t || !train)
+       {
+               train=t;
+               return true;
+       }
+       else
+               return false;
+}
+
+void Section::print_debug()
+{
+       cout<<"Section "<<id;
+       if((*tracks.begin())->get_sensor_id())
+               cout<<", sensor="<<(*tracks.begin())->get_sensor_id();
+       cout<<'\n';
+       for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+       {
+               cout<<"  Endpoint, link=";
+               if(i->link)
+                       cout<<i->link->id;
+               else
+                       cout<<"none";
+               cout<<", routes="<<i->routes<<'\n';
+       }
+}
+
+void Section::find_routes(Track *track, const Track::Endpoint *track_ep, unsigned route, std::set<Marklin::Track *> &visited)
+{
+       visited.insert(track);
+
+       const Track::EndpointSeq &eps=track->get_endpoints();
+       for(Track::EndpointSeq::const_iterator i=eps.begin(); i!=eps.end(); ++i)
+       {
+               if(&*i==track_ep) continue;
+               if(!i->link) continue;
+               if(!(i->routes&track_ep->routes)) continue;
+               if(visited.count(i->link)) continue;
+
+               if(tracks.count(i->link))
+                       find_routes(i->link, i->link->get_endpoint_by_link(track), route, visited);
+               else
+               {
+                       for(EndpointSeq::iterator j=endpoints.begin(); j!=endpoints.end(); ++j)
+                               if(j->track==track && j->track_ep==&*i)
+                                       j->routes|=route;
+               }
+       }
+
+       visited.erase(--visited.end());
+}
+
+unsigned Section::next_id=1;
+
+
+Section::Endpoint::Endpoint(Track *t, const Track::Endpoint *e):
+       track(t),
+       track_ep(e),
+       link(0),
+       routes(0)
+{ }
+
+} // namespace Marklin
diff --git a/source/libmarklin/section.h b/source/libmarklin/section.h
new file mode 100644 (file)
index 0000000..679466f
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef MARKLIN_3D_SECTION_H_
+#define MARKLIN_3D_SECTION_H_
+
+#include <list>
+#include <set>
+#include "track.h"
+
+namespace Marklin {
+
+class Train;
+class TrafficManager;
+
+class Section
+{
+public:
+       struct Endpoint
+       {
+               Track *track;
+               const Track::Endpoint *track_ep;
+               Section  *link;
+               unsigned routes;
+
+               Endpoint(Track *, const Track::Endpoint *);
+       };
+       typedef std::list<Endpoint> EndpointSeq;
+
+       Section(TrafficManager &, Track *);
+       unsigned get_sensor_id() const { return sensor_id; }
+       const TrackSet &get_tracks() const { return tracks; }
+       const Endpoint *get_endpoint_by_link(const Section *) const;
+       const Endpoint *traverse(const Endpoint *) const;
+       void check_link(Section &);
+       bool reserve(const Train *);
+       void print_debug();
+private:
+       TrafficManager &trfc_mgr;
+       unsigned       id;
+       unsigned       sensor_id;
+       TrackSet       tracks;
+       EndpointSeq    endpoints;
+       const Train    *train;
+
+       void find_routes(Track *, const Track::Endpoint *, unsigned, TrackSet &);
+
+       static unsigned next_id;
+};
+typedef std::list<Section *> SectionSeq;
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/sensor.cpp b/source/libmarklin/sensor.cpp
new file mode 100644 (file)
index 0000000..f6913ba
--- /dev/null
@@ -0,0 +1,24 @@
+#include "control.h"
+#include "sensor.h"
+
+namespace Marklin {
+
+Sensor::Sensor(Control &c, unsigned a):
+       control(c),
+       addr(a),
+       state(false)
+{
+       control.add_sensor(this);
+       control.signal_sensor_event.connect(sigc::mem_fun(this, &Sensor::sensor_event));
+}
+
+void Sensor::sensor_event(unsigned a, bool s)
+{
+       if(a==addr && s!=state)
+       {
+               state=s;
+               signal_state_changed.emit(state);
+       }
+}
+
+} // namespace Marklin
diff --git a/source/libmarklin/sensor.h b/source/libmarklin/sensor.h
new file mode 100644 (file)
index 0000000..e5e1b43
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef LIBMARKLIN_SENSOR_H_
+#define LIBMARKLIN_SENSOR_H_
+
+#include <list>
+#include <map>
+#include <sigc++/sigc++.h>
+
+namespace Marklin {
+
+class Control;
+
+class Sensor
+{
+public:
+       sigc::signal<void, bool> signal_state_changed;
+
+       Sensor(Control &, unsigned);
+       unsigned get_address() const { return addr; }
+       bool     get_state() const   { return state; }
+private:
+       Control  &control;
+       unsigned addr;
+       bool     state;
+
+       void sensor_event(unsigned, bool);
+};
+typedef std::list<Sensor *> SensorSeq;
+typedef std::map<unsigned, Sensor *> SensorMap;
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/track.cpp b/source/libmarklin/track.cpp
new file mode 100644 (file)
index 0000000..aaf1b75
--- /dev/null
@@ -0,0 +1,374 @@
+#include <cmath>
+#include "track.h"
+
+using namespace std;
+using namespace Msp;
+
+#include <iostream>
+
+namespace Marklin {
+
+Track::Track(unsigned a):
+       art_nr(a),
+       rot(0),
+       slope(0),
+       flex(false),
+       turnout_id(0),
+       sensor_id(0)
+{ }
+
+Track::~Track()
+{
+       for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+               if(i->link)
+               {
+                       Track *trk=i->link;
+                       i->link=0;
+                       trk->break_link(*this);
+               }
+}
+
+void Track::set_position(const Point &p)
+{
+       pos=p;
+}
+
+void Track::set_rotation(float r)
+{
+       rot=r;
+       while(rot<0)
+               rot+=M_PI*2;
+       while(rot>M_PI*2)
+               rot-=M_PI*2;
+}
+
+void Track::set_slope(float s)
+{
+       if(endpoints.size()!=2) return;
+
+       slope=s;
+       endpoints.back().pos.z=slope;
+}
+
+const Track::Endpoint *Track::get_endpoint_by_link(Track *other) const
+{
+       for(EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+               if(i->link==other)
+                       return &*i;
+
+       return 0;
+}
+
+float Track::get_length() const
+{
+       float len=parts.front().length;
+       if(parts.front().radius)
+               len*=parts.front().radius;
+       return len;
+}
+
+float Track::get_total_length() const
+{
+       float len=0;
+       for(PartSeq::const_iterator i=parts.begin(); i!=parts.end(); ++i)
+       {
+               float l=i->length;
+               if(i->radius)
+                       l*=i->radius;
+               len+=l;
+       }
+       return len;
+}
+
+unsigned Track::get_n_routes() const
+{
+       unsigned n=1;
+       for(PartSeq::const_iterator i=parts.begin(); i!=parts.end(); ++i)
+               if(i->route>=n)
+                       n=i->route+1;
+       return n;
+}
+
+bool Track::snap_to(Track &other, bool link)
+{
+       float limit=(link && !flex) ? 1e-6 : 1e-4;
+       for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+       {
+               float x=pos.x+i->pos.x*cos(rot)-i->pos.y*sin(rot);
+               float y=pos.y+i->pos.y*cos(rot)+i->pos.x*sin(rot);
+               for(EndpointSeq::iterator j=other.endpoints.begin(); j!=other.endpoints.end(); ++j)
+               {
+                       if(j->link)
+                               continue;
+
+                       float x2=other.pos.x+j->pos.x*cos(other.rot)-j->pos.y*sin(other.rot);
+                       float y2=other.pos.y+j->pos.y*cos(other.rot)+j->pos.x*sin(other.rot);
+                       float dx=x2-x;
+                       float dy=y2-y;
+                       if(dx*dx+dy*dy<limit)
+                       {
+                               set_rotation(other.rot+j->rot-i->rot+M_PI);
+                               set_position(Point(x2-(i->pos.x*cos(rot)-i->pos.y*sin(rot)), y2-(i->pos.y*cos(rot)+i->pos.x*sin(rot)), other.pos.z+j->pos.z-i->pos.z));
+                               if(link)
+                               {
+                                       if(i->link)
+                                               break_link(*i->link);
+                                       i->link=&other;
+                                       j->link=this;
+                               }
+                               return true;
+                       }
+               }
+       }
+
+       return false;
+}
+
+bool Track::snap(Point &pt, float &d) const
+{
+       for(EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+       {
+               float x=pos.x+i->pos.x*cos(rot)-i->pos.y*sin(rot);
+               float y=pos.y+i->pos.y*cos(rot)+i->pos.x*sin(rot);
+               float dx=pt.x-x;
+               float dy=pt.y-y;
+               if(dx*dx+dy*dy<1e-4)
+               {
+                       pt.x=x;
+                       pt.y=y;
+                       d=rot+i->rot;
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+void Track::break_link(Track &trk)
+{
+       for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+               if(i->link==&trk)
+               {
+                       i->link=0;
+                       trk.break_link(*this);
+                       return;
+               }
+}
+
+void Track::break_links()
+{
+       for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+               if(i->link)
+               {
+                       Track *trk=i->link;
+                       i->link=0;
+                       trk->break_link(*this);
+               }
+}
+
+void Track::check_slope()
+{
+       if(endpoints.size()!=2)
+               return;
+
+       Track *link1=endpoints.front().link;
+       Track *link2=endpoints.back().link;
+       if(link1 && link2)
+       {
+               const Endpoint *ep1=link1->get_endpoint_by_link(this);
+               const Endpoint *ep2=link2->get_endpoint_by_link(this);
+               pos.z=link1->pos.z+ep1->pos.z;
+               slope=(link2->pos.z+ep2->pos.z)-pos.z;
+       }
+       else
+       {
+               slope=0;
+               if(link1)
+               {
+                       const Endpoint *ep=link1->get_endpoint_by_link(this);
+                       pos.z=link1->pos.z+ep->pos.z;
+               }
+               else if(link2)
+               {
+                       const Endpoint *ep=link2->get_endpoint_by_link(this);
+                       pos.z=link2->pos.z+ep->pos.z;
+               }
+       }
+
+       endpoints.back().pos.z=slope;
+}
+
+const Track::Endpoint *Track::traverse(const Endpoint *ep, unsigned route) const
+{
+       if(ep->routes&(1<<route))
+       {
+               // Find the other endpoint for this route
+               for(EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+                       if((i->routes&(1<<route)) && &*i!=ep)
+                               return &*i;
+       }
+       else
+       {
+               // Find an endpoint that's connected to this one and has the requested route
+               for(EndpointSeq::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+                       if((i->routes&(1<<route)) && (i->routes&ep->routes))
+                               return &*i;
+       }
+
+       return 0;
+}
+
+Track *Track::copy() const
+{
+       Track *trk=new Track(*this);
+       for(EndpointSeq::iterator i=trk->endpoints.begin(); i!=trk->endpoints.end(); ++i)
+               i->link=0;
+       trk->turnout_id=0;
+       trk->sensor_id=0;
+
+       return trk;
+}
+
+/*** private ***/
+
+void Track::collect_endpoints()
+{
+       endpoints.clear();
+
+       for(PartSeq::iterator i=parts.begin(); i!=parts.end(); ++i)
+               i->collect_endpoints(endpoints);
+       endpoints.back().pos.z=slope;
+
+       for(EndpointSeq::iterator i=endpoints.begin(); i!=endpoints.end();)
+       {
+               bool rm=false;
+               for(EndpointSeq::iterator j=i; j!=endpoints.end();)
+               {
+                       if(j==i)
+                       {
+                               ++j;
+                               continue;
+                       }
+                       float dx=i->pos.x-j->pos.x;
+                       float dy=i->pos.y-j->pos.y;
+                       if(dx*dx+dy*dy<0.0001)
+                       {
+                               float da=i->rot-j->rot;
+                               if(da<-M_PI)
+                                       da+=M_PI*2;
+                               if(da>M_PI)
+                                       da-=M_PI*2;
+                               if(da<-3 || da>3)
+                                       rm=true;
+                               i->routes|=j->routes;
+                               j=endpoints.erase(j);
+                       }
+                       else
+                               ++j;
+               }
+               if(rm)
+                       i=endpoints.erase(i);
+               else
+                       ++i;
+       }
+}
+
+/*******************
+** Track::Loader
+*/
+
+Track::Loader::Loader(Track &t):
+       track(t)
+{
+       add("description", &Track::description);
+       add("part",        &Loader::part);
+       add("position",    &Loader::position);
+       add("rotation",    &Track::rot);
+       add("slope",       &Track::slope);
+       add("turnout_id",  &Track::turnout_id);
+       add("sensor_id",   &Track::sensor_id);
+       add("flex",        &Track::flex);
+}
+
+Track::Loader::~Loader()
+{
+       track.collect_endpoints();
+}
+
+void Track::Loader::part()
+{
+       Part p;
+       load_sub(p);
+       track.parts.push_back(p);
+}
+
+void Track::Loader::position(float x, float y, float z)
+{
+       track.pos=Point(x, y, z);
+}
+
+/*******************
+** Track::Part
+*/
+
+Track::Part::Part():
+       x(0),
+       y(0),
+       dir(0),
+       length(0),
+       radius(0),
+       route(0),
+       dead_end(false)
+{ }
+
+void Track::Part::collect_endpoints(EndpointSeq &epl)
+{
+       epl.push_back(Endpoint(Point(x, y, 0), dir+M_PI, 1<<route));
+       if(dead_end)
+               return;
+       else if(radius)
+       {
+               float a=(radius<0)?-length:length;
+               float c=cos(a);
+               float s=sin(a);
+               float rx=radius*sin(dir);
+               float ry=-radius*cos(dir);
+               epl.push_back(Endpoint(Point(x+c*rx-s*ry-rx, y+c*ry+s*rx-ry, 0), dir+a, 1<<route));
+       }
+       else
+               epl.push_back(Endpoint(Point(x+cos(dir)*length, y+sin(dir)*length, 0), dir, 1<<route));
+}
+
+Track::Part::Loader::Loader(Part &p):
+       part(p)
+{
+       add("start",    &Loader::start);
+       add("length",   &Part::length);
+       add("radius",   &Part::radius);
+       add("route",    &Part::route);
+       add("dead_end", &Part::dead_end);
+}
+
+Track::Part::Loader::~Loader()
+{
+       if(part.radius)
+       {
+               part.length*=M_PI/180;
+               part.radius/=1000;
+       }
+       else
+               part.length/=1000;
+
+       part.x/=1000;
+       part.y/=1000;
+       part.dir*=M_PI/180;
+}
+
+void Track::Part::Loader::start(float x, float y, float d)
+{
+       part.x=x;
+       part.y=y;
+       part.dir=d;
+}
+
+} // namespace Marklin
diff --git a/source/libmarklin/track.h b/source/libmarklin/track.h
new file mode 100644 (file)
index 0000000..9345f03
--- /dev/null
@@ -0,0 +1,121 @@
+#ifndef LIBMARKLIN_TRACK_H_
+#define LIBMARKLIN_TRACK_H_
+
+#include <list>
+#include <set>
+#include <msp/parser/loader.h>
+#include "geometry.h"
+
+namespace Marklin {
+
+class Track
+{
+public:
+       class Loader: public Msp::Parser::Loader
+       {
+       public:
+               Loader(Track &);
+               Track &get_object() { return track; }
+               ~Loader();
+       private:
+               Track &track;
+
+               void part();
+               void position(float, float, float);
+       };
+
+       struct Endpoint
+       {
+               Point  pos;
+               float  rot;
+               Track *link;
+               unsigned routes;
+
+               Endpoint(const Point &p, float r, unsigned o): pos(p), rot(r), link(0), routes(o) { }
+       };
+       typedef std::list<Endpoint> EndpointSeq;
+
+       struct Part
+       {
+               class Loader: public Msp::Parser::Loader
+               {
+               public:
+                       Loader(Part &);
+                       Part &get_object() { return part; }
+                       ~Loader();
+               private:
+                       Part &part;
+
+                       void start(float, float, float);
+               };
+
+               float    x,y;
+               float    dir;
+               float    length;
+               float    radius;
+               unsigned route;
+               bool     dead_end;
+
+               Part();
+               void collect_endpoints(EndpointSeq &);
+       };
+       typedef std::list<Part> PartSeq;
+
+       Track(unsigned);
+       ~Track();
+       void               set_position(const Point &);
+       void               set_rotation(float);
+       void               set_slope(float);
+       void               set_flex(bool f)           { flex=f; }
+       void               set_turnout_id(unsigned i) { turnout_id=i; }
+       void               set_sensor_id(unsigned i)  { sensor_id=i; }
+       const Point        &get_position() const      { return pos; }
+       float              get_rotation() const       { return rot; }
+       unsigned           get_article_number() const { return art_nr; }
+       const PartSeq      &get_parts() const         { return parts; }
+       const EndpointSeq  &get_endpoints() const     { return endpoints; }
+       const Endpoint     *get_endpoint_by_link(Track *) const;
+       const std::string  &get_description() const   { return description; }
+       float              get_slope() const          { return slope; }
+       bool               get_flex() const           { return flex; }
+       float              get_length() const;
+       float              get_total_length() const;
+       unsigned           get_turnout_id() const     { return turnout_id; }
+       unsigned           get_sensor_id() const      { return sensor_id; }
+       unsigned           get_n_routes() const;
+       bool               snap_to(Track &, bool);
+       bool               snap(Point &, float &) const;
+       void               break_link(Track &);
+       void               break_links();
+       void               check_slope();
+       const Endpoint     *traverse(const Endpoint *, unsigned) const;
+
+       /**
+       Creates a copy of the track.  The new track will be almost identical, but
+       won't have any links to other tracks, nor a turnout or sensor id.
+       */
+       Track *copy() const;
+private:
+       unsigned     art_nr;
+       std::string  description;
+       PartSeq      parts;
+       EndpointSeq  endpoints;
+       Point        pos;
+       float        rot;
+       float        slope;
+       bool         flex;
+       unsigned     turnout_id;
+       unsigned     sensor_id;
+
+       // Direct copying not allowed due to links.  See the copy() function.
+       //Track(const Track &);
+       Track &operator=(const Track &);
+
+       void  collect_endpoints();
+};
+typedef std::list<Track *> TrackSeq;
+typedef std::set<Track *> TrackSet;
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/trafficmanager.cpp b/source/libmarklin/trafficmanager.cpp
new file mode 100644 (file)
index 0000000..7a47f14
--- /dev/null
@@ -0,0 +1,71 @@
+#include "control.h"
+#include "layout.h"
+#include "trafficmanager.h"
+#include "turnout.h"
+
+using namespace std;
+
+namespace Marklin {
+
+TrafficManager::TrafficManager(Control &c, Layout &l):
+       control(c),
+       layout(l)
+{
+       const TrackSeq &tracks=layout.get_tracks();
+
+       TrackSet used_tracks;
+       for(TrackSeq::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
+       {
+               if(unsigned tid=(*i)->get_turnout_id())
+               {
+                       Turnout *t=control.get_turnout(tid);
+                       if(!t)
+                               t=new Turnout(control, tid);
+                       //t->signal_route_changed.connect(sigc::mem_fun(*i, &Track::set_current_route));
+               }
+               if(unsigned sid=(*i)->get_sensor_id())
+               {
+                       Sensor *s=control.get_sensor(sid);
+                       if(!s)
+                               s=new Sensor(control, sid);
+               }
+               if(used_tracks.count(*i)==0)
+               {
+                       Section *sect=new Section(*this, *i);
+                       sections.push_back(sect);
+                       used_tracks.insert(sect->get_tracks().begin(), sect->get_tracks().end());
+               }
+       }
+
+       for(SectionSeq::iterator i=sections.begin(); i!=sections.end(); ++i)
+       {
+               for(SectionSeq::iterator j=i; j!=sections.end(); ++j)
+                       if(j!=i)
+                               (*i)->check_link(**j);
+               (*i)->print_debug();
+       }
+}
+
+Section *TrafficManager::get_section_by_track(const Track *t) const
+{
+       for(SectionSeq::const_iterator i=sections.begin(); i!=sections.end(); ++i)
+       {
+               const TrackSet &tracks=(*i)->get_tracks();
+               if(tracks.count(const_cast<Track *>(t)))
+                       return *i;
+       }
+
+       return 0;
+}
+
+void TrafficManager::add_train(Train *t)
+{
+       if(find(trains.begin(), trains.end(), t)==trains.end())
+               trains.push_back(t);
+}
+
+void TrafficManager::turnout_route_changed(unsigned, Turnout *)
+{
+}
+
+} // namespace Marklin
diff --git a/source/libmarklin/trafficmanager.h b/source/libmarklin/trafficmanager.h
new file mode 100644 (file)
index 0000000..11ecc95
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef LIBMARKLIN_TRAFFICMANAGER_H_
+#define LIBMARKLIN_TRAFFICMANAGER_H_
+
+#include "section.h"
+#include "train.h"
+
+namespace Marklin {
+
+class Control;
+class Layout;
+class Turnout;
+
+class TrafficManager
+{
+public:
+       TrafficManager(Control &, Layout &);
+       Control &get_control() const { return control; }
+       Section *get_section_by_track(const Track *) const;
+       void add_train(Train *);
+private:
+       Control &control;
+       Layout &layout;
+       SectionSeq sections;
+       TrainSeq trains;
+
+       void turnout_route_changed(unsigned, Turnout *);
+};
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/train.cpp b/source/libmarklin/train.cpp
new file mode 100644 (file)
index 0000000..73b2823
--- /dev/null
@@ -0,0 +1,121 @@
+#include "control.h"
+#include "trafficmanager.h"
+#include "train.h"
+
+namespace Marklin {
+
+Train::Train(TrafficManager &tm, Locomotive &l):
+       trfc_mgr(tm),
+       loco(l),
+       target_speed(0)
+{
+       trfc_mgr.add_train(this);
+       trfc_mgr.get_control().signal_sensor_event.connect(sigc::mem_fun(this, &Train::sensor_event));
+}
+
+void Train::set_speed(unsigned speed)
+{
+       target_speed=speed;
+       if(rsv_sections.empty() && !reserve_more())
+               return;
+       loco.set_speed(speed);
+}
+
+void Train::place(Section *sect, const Section::Endpoint *entry)
+{
+       for(SectRefSeq::iterator i=rsv_sections.begin(); i!=rsv_sections.end();)
+       {
+               i->section->reserve(0);
+               i=rsv_sections.erase(i);
+       }
+
+       for(SectRefSeq::iterator i=cur_sections.begin(); i!=cur_sections.end();)
+       {
+               i->section->reserve(0);
+               i=cur_sections.erase(i);
+       }
+
+       if(!sect->reserve(this))
+               return;
+
+       cur_sections.push_back(SectionRef(sect, entry));
+}
+
+bool Train::free_section(Section *sect)
+{
+       for(SectRefSeq::iterator i=rsv_sections.begin(); i!=rsv_sections.end(); ++i)
+               if(i->section==sect)
+               {
+                       while(i!=rsv_sections.end())
+                       {
+                               i->section->reserve(0);
+                               i=rsv_sections.erase(i);
+                       }
+                       return true;
+               }
+
+       return false;
+}
+
+void Train::sensor_event(unsigned addr, bool state)
+{
+       if(state)
+       {
+               SectRefSeq::iterator i;
+               for(i=rsv_sections.begin(); i!=rsv_sections.end(); ++i)
+                       if(i->section->get_sensor_id() && i->section->get_sensor_id()!=addr)
+                               break;
+               cur_sections.splice(cur_sections.begin(), rsv_sections, rsv_sections.begin(), i);
+
+               reserve_more();
+               if(rsv_sections.empty())
+                       loco.set_speed(0);
+       }
+       else
+       {
+               SectRefSeq::iterator i;
+               for(i=cur_sections.begin(); i!=cur_sections.end(); ++i)
+                       if(i->section->get_sensor_id()==addr)
+                               break;
+               if(i!=cur_sections.end())
+               {
+                       ++i;
+                       for(SectRefSeq::iterator j=cur_sections.begin(); j!=i; ++j)
+                               j->section->reserve(0);
+                       cur_sections.erase(cur_sections.begin(), i);
+               }
+
+               reserve_more();
+       }
+}
+
+bool Train::reserve_more()
+{
+       SectionRef *last=0;
+       if(!rsv_sections.empty())
+               last=&rsv_sections.back();
+       else if(!cur_sections.empty())
+               last=&cur_sections.back();
+       if(!last)
+               return false;
+
+       bool result=false;
+       unsigned size=rsv_sections.size();
+       while(size<5)
+       {
+               const Section::Endpoint *exit=last->section->traverse(last->entry);
+               if(exit->link->reserve(this))
+               {
+                       rsv_sections.push_back(SectionRef(exit->link, exit->link->get_endpoint_by_link(last->section)));
+                       last=&rsv_sections.back();
+                       ++size;
+                       result=true;
+               }
+               else
+                       break;
+       }
+
+       return result;
+}
+
+} // namespace Marklin
diff --git a/source/libmarklin/train.h b/source/libmarklin/train.h
new file mode 100644 (file)
index 0000000..363fb55
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef LIBMARKLIN_TRAIN_H_
+#define LIBMARKLIN_TRAIN_H_
+
+#include <sigc++/trackable.h>
+#include "section.h"
+
+namespace Marklin {
+
+class Locomotive;
+class TrafficManager;
+
+class Train: public sigc::trackable
+{
+public:
+       Train(TrafficManager &, Locomotive &);
+       const std::string &get_name() const { return name; }
+       void set_speed(unsigned);
+       void place(Section *, const Section::Endpoint *);
+       bool free_section(Section *);
+       void tick();
+private:
+       struct SectionRef
+       {
+               Section *section;
+               const Section::Endpoint *entry;
+
+               SectionRef(Section *s, const Section::Endpoint *e): section(s), entry(e) { }
+       };
+       typedef std::list<SectionRef> SectRefSeq;
+
+       TrafficManager &trfc_mgr;
+       std::string name;
+       Locomotive &loco;
+       SectRefSeq cur_sections;
+       SectRefSeq rsv_sections;
+       unsigned target_speed;
+
+       void sensor_event(unsigned, bool);
+       bool reserve_more();
+};
+typedef std::list<Train *> TrainSeq;
+
+} // namespace Marklin
+
+#endif
diff --git a/source/libmarklin/turnout.cpp b/source/libmarklin/turnout.cpp
new file mode 100644 (file)
index 0000000..eca9ae2
--- /dev/null
@@ -0,0 +1,77 @@
+#include <iostream>
+#include <msp/time/timer.h>
+#include <msp/time/units.h>
+#include "command.h"
+#include "control.h"
+#include "turnout.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace Marklin {
+
+Turnout::Turnout(Control &c, unsigned a):
+       control(c),
+       addr(a),
+       route(0)
+{
+       control.add_turnout(this);
+
+       control.signal_turnout_event.connect(sigc::mem_fun(this, &Turnout::turnout_event));
+
+       char cmd[3];
+       cmd[0]=CMD_TURNOUT_STATUS;
+       cmd[1]=addr&0xFF;
+       cmd[2]=(addr>>8)&0xFF;
+       control.command(string(cmd,3)).signal_done.connect(sigc::mem_fun(this, &Turnout::status_reply));
+}
+
+void Turnout::set_route(unsigned r)
+{
+       route=r;
+
+       command(true);
+       (new Time::Timer(200*Time::msec))->signal_timeout.connect(sigc::mem_fun(this, &Turnout::switch_timeout));
+
+       signal_route_changed.emit(route);
+}
+
+void Turnout::command(bool on)
+{
+       char cmd[3];
+       cmd[0]=CMD_TURNOUT;
+       cmd[1]=addr&0xFF;
+       cmd[2]=((addr>>8)&0x7);
+       if(on)
+               cmd[2]|=0x40;
+       if(route==0)
+               cmd[2]|=0x80;
+       control.command(string(cmd,3));
+}
+
+void Turnout::status_reply(Error err, const string &reply)
+{
+       if(err==ERR_NO_ERROR)
+       {
+               route=(reply[0]&4)?0:1;
+               signal_route_changed.emit(route);
+       }
+}
+
+bool Turnout::switch_timeout()
+{
+       command(false);
+
+       return false;
+}
+
+void Turnout::turnout_event(unsigned a, bool r)
+{
+       if(a==addr && r!=route)
+       {
+               route=r;
+               signal_route_changed.emit(route);
+       }
+}
+
+} // namespace Marklin
diff --git a/source/libmarklin/turnout.h b/source/libmarklin/turnout.h
new file mode 100644 (file)
index 0000000..edf1c03
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef LIBMARKLIN_TURNOUT_H_
+#define LIBMARKLIN_TURNOUT_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <sigc++/sigc++.h>
+#include "constants.h"
+
+namespace Marklin {
+
+class Control;
+
+class Turnout
+{
+public:
+       sigc::signal<void, unsigned> signal_route_changed;
+
+       Turnout(Control &, unsigned);
+       void     set_route(unsigned);
+       unsigned get_address() const { return addr; }
+       unsigned get_route() const   { return route; }
+private:
+       Control  &control;
+       unsigned addr;
+       unsigned route;
+
+       void command(bool);
+       void status_reply(Error, const std::string &);
+       bool switch_timeout();
+       void turnout_event(unsigned, bool);
+};
+typedef std::list<Turnout *> TurnoutSeq;
+typedef std::map<unsigned, Turnout *> TurnoutMap;
+
+} // namespace Marklin
+
+#endif
diff --git a/source/shoppinglist/main.cpp b/source/shoppinglist/main.cpp
new file mode 100644 (file)
index 0000000..a87e2f8
--- /dev/null
@@ -0,0 +1,138 @@
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <msp/core/getopt.h>
+#include <msp/parser/loader.h>
+#include <msp/parser/parser.h>
+
+using namespace std;
+using namespace Msp;
+
+class ShoppingList
+{
+public:
+       ShoppingList(int, char **);
+       void print(ostream &);
+private:
+       class InventoryLoader: public Parser::Loader
+       {
+       public:
+               InventoryLoader(ShoppingList &);
+       private:
+               ShoppingList &sl;
+
+               void track(unsigned, unsigned);
+       };
+
+       class LayoutLoader: public Parser::Loader
+       {
+       public:
+               LayoutLoader(ShoppingList &);
+       private:
+               ShoppingList &sl;
+
+               void track(unsigned);
+       };
+
+       typedef map<unsigned, unsigned> TrackMap;
+
+       TrackMap inventory;
+       TrackMap layout;
+
+       void load_inventory(const string &);
+       void load_layout(const string &);
+};
+
+int main(int argc, char **argv)
+{
+       ShoppingList sl(argc, argv);
+       sl.print(cout);
+       return 0;
+}
+
+ShoppingList::ShoppingList(int argc, char **argv)
+{
+       string inv_fn="inventory";
+       GetOpt getopt;
+       getopt.add_option('i', "inventory", inv_fn, GetOpt::REQUIRED_ARG);
+       getopt(argc,argv);
+
+       load_inventory(inv_fn);
+       load_layout(getopt.get_args().front());
+}
+
+void ShoppingList::load_inventory(const string &fn)
+{
+       ifstream in(fn.c_str());
+       Parser::Parser parser(in, fn);
+       InventoryLoader il(*this);
+       il.load(parser);
+}
+
+void ShoppingList::load_layout(const string &fn)
+{
+       ifstream in(fn.c_str());
+       Parser::Parser parser(in, fn);
+       LayoutLoader ll(*this);
+       ll.load(parser);
+}
+
+void ShoppingList::print(ostream &out)
+{
+       out<<"// Need to get:\n";
+       for(TrackMap::iterator i=layout.begin(); i!=layout.end(); ++i)
+       {
+               TrackMap::iterator j=inventory.find(i->first);
+               if(j!=inventory.end())
+               {
+                       if(j->second<i->second)
+                               out<<"track "<<i->first<<' '<<i->second-j->second<<";\n";
+               }
+               else
+                       out<<"track "<<i->first<<' '<<i->second<<";\n";
+       }
+
+       out<<"// Pre-existing:\n";
+       for(TrackMap::iterator i=layout.begin(); i!=layout.end(); ++i)
+       {
+               TrackMap::iterator j=inventory.find(i->first);
+               if(j!=inventory.end())
+                       out<<"track "<<i->first<<' '<<min(i->second,j->second)<<";\n";
+       }
+
+       out<<"// Unused:\n";
+       for(TrackMap::iterator i=inventory.begin(); i!=inventory.end(); ++i)
+       {
+               TrackMap::iterator j=layout.find(i->first);
+               if(j!=layout.end())
+               {
+                       if(j->second<i->second)
+                               out<<"track "<<i->first<<' '<<i->second-j->second<<";\n";
+               }
+               else
+                       out<<"track "<<i->first<<' '<<i->second<<";\n";
+       }
+}
+
+ShoppingList::InventoryLoader::InventoryLoader(ShoppingList &s):
+       sl(s)
+{
+       add("track", &InventoryLoader::track);
+}
+
+void ShoppingList::InventoryLoader::track(unsigned part, unsigned count)
+{
+       sl.inventory[part]+=count;
+}
+
+ShoppingList::LayoutLoader::LayoutLoader(ShoppingList &s):
+       sl(s)
+{
+       add("track", &LayoutLoader::track);
+       add("base");
+}
+
+void ShoppingList::LayoutLoader::track(unsigned part)
+{
+       ++sl.layout[part];
+}
diff --git a/tracks.dat b/tracks.dat
new file mode 100644 (file)
index 0000000..75d36d9
--- /dev/null
@@ -0,0 +1,425 @@
+// Straight
+
+track 24064
+{
+       description "Straight, 64.3mm (R3-R4-R5)";
+       part
+       {
+               length 64.3;
+       };
+};
+
+track 24071
+{
+       description "Straight, 70.3mm (for slim turnouts)";
+       part
+       {
+               length 70.8;
+       };
+};
+
+track 24077
+{
+       description "Straight, 77.5mm (R1-R2-R3)";
+       part
+       {
+               length 77.5;
+       };
+};
+
+track 24094
+{
+       description "Straight, 94.2mm";
+       part
+       {
+               length 94.2;
+       };
+};
+
+track 24172
+{
+       description "Straight, 171.7mm";
+       part
+       {
+               length 171.7;
+       };
+};
+
+track 24188
+{
+       description "Straight, 188.3mm (turnout length)";
+       part
+       {
+               length 188.3;
+       };
+};
+
+track 24229
+{
+       description "Straight, 229.3mm";
+       part
+       {
+               length 229.3;
+       };
+};
+
+track 24236
+{
+       description "Straight, 236.1mm (slim turnout length)";
+       part
+       {
+               length 236.1;
+       };
+};
+
+// Curves, R1
+
+track 24107
+{
+       description "Curve, R1 = 360mm, 7.5°";
+       part
+       {
+               length 7.5;
+               radius 360;
+       };
+};
+
+track 24115
+{
+       description "Curve, R1 = 360mm, 15°";
+       part
+       {
+               length 15;
+               radius 360;
+       };
+};
+
+track 24130
+{
+       description "Curve, R1 = 360mm, 30°";
+       part
+       {
+               length 30;
+               radius 360;
+       };
+};
+
+// Curves, R2
+
+track 24206
+{
+       description "Curve, R2 = 437.5mm, 5.7° (turnout to 30°)";
+       part
+       {
+               length 5.7;
+               radius 437.5;
+       };
+};
+
+track 24207
+{
+       description "Curve, R2 = 437.5mm, 7.5°";
+       part
+       {
+               length 7.5;
+               radius 437.5;
+       };
+};
+
+track 24215
+{
+       description "Curve, R2 = 437.5mm, 15°";
+       part
+       {
+               length 15;
+               radius 437.5;
+       };
+};
+
+track 24224
+{
+       description "Curve, R2 = 437.5mm, 24.3° (turnout)";
+       part
+       {
+               length 24.3;
+               radius 437.5;
+       };
+};
+
+track 24230
+{
+       description "Curve, R2 = 437.5mm, 30°";
+       part
+       {
+               length 30;
+               radius 437.5;
+       };
+};
+
+// Curves, R3
+
+track 24330
+{
+       description "Curve, R3 = 515mm, 30°";
+       part
+       {
+               length 30;
+               radius 515;
+       };
+};
+
+// Curves, R4
+
+track 24430
+{
+       description "Curve, R4 = 579.3mm, 30°";
+       part
+       {
+               length 30;
+               radius 579.3;
+       };
+};
+
+// Curves, R5
+
+track 24530
+{
+       description "Curve, R5 = 643.6mm, 30°";
+       part
+       {
+               length 30;
+               radius 643.6;
+       };
+};
+
+// Curves, slim turnout
+
+track 24912
+{
+       description "Curve, R = 1114.6mm, 12.1° (slim turnout)";
+       part
+       {
+               length 12.1;
+               radius 1114.6;
+       };
+};
+
+// Curved turnouts, R1
+
+track 24671
+{
+       description "Curved turnout, left";
+       part
+       {
+               length 77.5;
+               route 0;
+       };
+       part
+       {
+               start 77.5 0 0;
+               length 30;
+               radius 360;
+               route 0;
+       };
+       part
+       {
+               length 30;
+               radius 360;
+               route 1;
+       };
+};
+
+track 24672
+{
+       description "Curved turnout, right";
+       part
+       {
+               length 77.5;
+               route 0;
+       };
+       part
+       {
+               start 77.5 0 0;
+               length 30;
+               radius -360;
+               route 0;
+       };
+       part
+       {
+               length 30;
+               radius -360;
+               route 1;
+       };
+};
+
+// Turnouts, R2
+
+track 24611
+{
+       description "Turnout, left";
+       part
+       {
+               length 188.3;
+               route 0;
+       };
+       part
+       {
+               length 24.3;
+               radius 437.5;
+               route 1;
+       };
+};
+
+track 24612
+{
+       description "Turnout, right";
+       part
+       {
+               length 188.3;
+               route 0;
+       };
+       part
+       {
+               length 24.3;
+               radius -437.5;
+               route 1;
+       };
+};
+
+track 24630
+{
+       description "Turnout, 3-way";
+       part
+       {
+               length 188.3;
+               route 0;
+       };
+       part
+       {
+               length 24.3;
+               radius 437.5;
+               route 1;
+       };
+       part
+       {
+               length 24.3;
+               radius -437.5;
+               route 2;
+       };
+};
+
+track 24624
+{
+       description "Turnout, double crossing";
+       part
+       {
+               length 188.3;
+       };
+       part
+       {
+               length 24.3;
+               radius -437.5;
+       };
+       part
+       {
+               start 8.34 38.74 -24.3;
+               length 188.3;
+       };
+       part
+       {
+               start 8.34 38.74 -24.3;
+               length 24.3;
+               radius 437.5;
+       };
+};
+
+// Slim turnouts
+
+track 24711
+{
+       description "Slim turnout, left";
+       part
+       {
+               length 236.1;
+               route 0;
+       };
+       part
+       {
+               length 12.1;
+               radius 1114.6;
+               route 1;
+       };
+};
+
+track 24712
+{
+       description "Slim turnout, right";
+       part
+       {
+               length 236.1;
+               route 0;
+       };
+       part
+       {
+               length 12.1;
+               radius -1114.6;
+               route 1;
+       };
+};
+
+// Crossings
+
+track 24640
+{
+       description "Crossing, 24.3°";
+       part
+       {
+               length 188.3;
+       };
+       part
+       {
+               start 8.34 38.74 -24.3;
+               length 188.3;
+       };
+};
+
+track 24649
+{
+       description "Crossing, 48.6°";
+       part
+       {
+               length 103.3;
+       };
+       part
+       {
+               start 17.49 38.74 -48.6;
+               length 103.3;
+       };
+};
+
+track 24740
+{
+       description "Slim crossing, 12.1°";
+       part
+       {
+               length 236.1;
+       };
+       part
+       {
+               start 2.62 24.75 -12.1;
+               length 236.1;
+       };
+};
+
+// Specials
+
+track 24977
+{
+       description "Bumper";
+       part
+       {
+               length 94.2;
+               dead_end true;
+       };
+};
+