]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tgdb/apt_signs.cxx
Taxiway signs: support all variants from the apt.dat 850 spec and
[simgear.git] / simgear / scene / tgdb / apt_signs.cxx
1 // apt_signs.cxx -- build airport signs on the fly
2 //
3 // Written by Curtis Olson, started July 2001.
4 //
5 // Copyright (C) 2001  Curtis L. Olson  - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include <simgear_config.h>
25 #endif
26
27 #include <vector>
28 #include <boost/foreach.hpp>
29
30 #include <osg/Geode>
31 #include <osg/Geometry>
32 #include <osg/Group>
33 #include <osg/MatrixTransform>
34 #include <osg/StateSet>
35
36 #include <simgear/debug/logstream.hxx>
37 #include <simgear/math/sg_types.hxx>
38 #include <simgear/scene/material/Effect.hxx>
39 #include <simgear/scene/material/EffectGeode.hxx>
40 #include <simgear/scene/material/mat.hxx>
41 #include <simgear/scene/material/matlib.hxx>
42
43 #include "apt_signs.hxx"
44
45 #define SIGN "OBJECT_SIGN: "
46
47 using std::vector;
48 using namespace simgear;
49
50 // for temporary storage of sign elements
51 struct element_info {
52     element_info(SGMaterial *m, SGMaterialGlyph *g, double h, double c)
53         : material(m), glyph(g), height(h), coverwidth(c)
54     {
55         scale = h * m->get_xsize()
56                 / (m->get_ysize() < 0.001 ? 1.0 : m->get_ysize());
57         abswidth = c == 0 ? g->get_width() * scale : c;
58     }
59     SGMaterial *material;
60     SGMaterialGlyph *glyph;
61     double height;
62     double scale;
63     double abswidth;
64     double coverwidth;
65 };
66
67 typedef std::vector<element_info*> ElementVec;
68
69 // Standard panel height sizes. The first value is unused to
70 // make sure the size value equals 1:1 the value from the apt.dat file:
71 const double HT[6] = {0.1, 0.460, 0.610, 0.760, 1.220, 0.760};
72
73 const double grounddist = 0.2;     // hard-code sign distance from surface for now
74 const double thick = 0.1;    // half the thickness of the 3D sign
75
76
77 // translation table for "command" to "glyph name"
78 struct pair {
79     const char *keyword;
80     const char *glyph_name;
81 } cmds[] = {
82     {"@u",       "^u"},
83     {"@d",       "^d"},
84     {"@l",       "^l"},
85     {"@lu",      "^lu"},
86     {"@ld",      "^ld"},
87     {"@r",       "^r"},
88     {"@ru",      "^ru"},
89     {"@rd",      "^rd"},
90     {"r1",       "^I1"},
91     {"r2",       "^I2"},
92     {"r3",       "^I3"},
93     {0, 0},
94 };
95
96 struct GlyphGeometry
97 {
98   osg::DrawArrays* quads;
99   osg::Vec2Array* uvs;
100   osg::Vec3Array* vertices;
101   osg::Vec3Array* normals;
102   
103   void addGlyph(SGMaterialGlyph* glyph, double x, double y, double width, double height, const osg::Matrix& xform)
104   {
105     
106     vertices->push_back(xform.preMult(osg::Vec3(thick, x, y)));
107     vertices->push_back(xform.preMult(osg::Vec3(thick, x + width, y)));
108     vertices->push_back(xform.preMult(osg::Vec3(thick, x + width, y + height)));
109     vertices->push_back(xform.preMult(osg::Vec3(thick, x, y + height)));
110     
111     // texture coordinates
112     double xoffset = glyph->get_left();
113     double texWidth = glyph->get_width();
114     
115     uvs->push_back(osg::Vec2(xoffset,         0));
116     uvs->push_back(osg::Vec2(xoffset + texWidth, 0));
117     uvs->push_back(osg::Vec2(xoffset + texWidth, 1));
118     uvs->push_back(osg::Vec2(xoffset,         1));
119
120     // normals
121     for (int i=0; i<4; ++i)
122       normals->push_back(xform.preMult(osg::Vec3(0, -1, 0)));
123     
124     quads->setCount(vertices->size());
125   }
126   
127   void addSignCase(double caseWidth, double caseHeight, const osg::Matrix& xform)
128   {
129     int last = vertices->size();
130     double texsize = caseWidth / caseHeight;
131
132     //left
133     vertices->push_back(osg::Vec3(-thick, -caseWidth,  grounddist));
134     vertices->push_back(osg::Vec3(thick, -caseWidth,  grounddist));
135     vertices->push_back(osg::Vec3(thick, -caseWidth,  grounddist + caseHeight));
136     vertices->push_back(osg::Vec3(-thick, -caseWidth,  grounddist + caseHeight));
137
138     uvs->push_back(osg::Vec2(1,    1));
139     uvs->push_back(osg::Vec2(0.75, 1));
140     uvs->push_back(osg::Vec2(0.75, 0));
141     uvs->push_back(osg::Vec2(1,    0));
142     
143     for (int i=0; i<4; ++i)
144       normals->push_back(osg::Vec3(-1, 0.0, 0));
145
146     //top
147     vertices->push_back(osg::Vec3(-thick, -caseWidth,  grounddist + caseHeight));
148     vertices->push_back(osg::Vec3(thick,  -caseWidth,  grounddist + caseHeight));
149     vertices->push_back(osg::Vec3(thick,  caseWidth, grounddist + caseHeight));
150     vertices->push_back(osg::Vec3(-thick, caseWidth, grounddist + caseHeight));
151     
152     uvs->push_back(osg::Vec2(1,    texsize));
153     uvs->push_back(osg::Vec2(0.75, texsize));
154     uvs->push_back(osg::Vec2(0.75, 0));
155     uvs->push_back(osg::Vec2(1,    0));
156
157     for (int i=0; i<4; ++i)
158       normals->push_back(osg::Vec3(0, 0, 1));
159
160     //right
161     vertices->push_back(osg::Vec3(-thick, caseWidth, grounddist + caseHeight));
162     vertices->push_back(osg::Vec3(thick,  caseWidth, grounddist + caseHeight));
163     vertices->push_back(osg::Vec3(thick,  caseWidth, grounddist));
164     vertices->push_back(osg::Vec3(-thick, caseWidth, grounddist));
165
166     uvs->push_back(osg::Vec2(1,    1));
167     uvs->push_back(osg::Vec2(0.75, 1));
168     uvs->push_back(osg::Vec2(0.75, 0));
169     uvs->push_back(osg::Vec2(1,    0));
170
171     for (int i=0; i<4; ++i)
172       normals->push_back(osg::Vec3(1, 0.0, 0));
173
174
175   // transform all the newly added vertices and normals by the matrix
176     for (unsigned int i=last; i<vertices->size(); ++i) {
177       (*vertices)[i]= xform.preMult((*vertices)[i]);
178       (*normals)[i] = xform.preMult((*normals)[i]);
179     }
180     
181     quads->setCount(vertices->size());
182   }
183 };
184
185 typedef std::map<Effect*, GlyphGeometry*> EffectGeometryMap;
186
187 GlyphGeometry* makeGeometry(Effect* eff, osg::Group* group)
188 {
189   GlyphGeometry* gg = new GlyphGeometry;
190   
191   EffectGeode* geode = new EffectGeode;
192   geode->setEffect(eff);
193  
194   gg->vertices = new osg::Vec3Array;
195   gg->normals = new osg::Vec3Array;
196   gg->uvs = new osg::Vec2Array;
197   
198   osg::Vec4Array* cl = new osg::Vec4Array;
199   cl->push_back(osg::Vec4(1, 1, 1, 1));
200   
201   osg::Geometry* geometry = new osg::Geometry;
202   geometry->setVertexArray(gg->vertices);
203   geometry->setNormalArray(gg->normals);
204   geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
205   geometry->setColorArray(cl);
206   geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
207   geometry->setTexCoordArray(0, gg->uvs);
208   
209   gg->quads = new osg::DrawArrays(GL_QUADS, 0, gg->vertices->size());
210   geometry->addPrimitiveSet(gg->quads);
211   geode->addDrawable(geometry);
212   group->addChild(geode);
213   return gg;
214 }
215
216 // see $FG_ROOT/Docs/README.scenery
217
218 namespace simgear
219 {
220
221 class AirportSignBuilder::AirportSignBuilderPrivate
222 {
223 public:
224     SGMaterialLib* materials;
225     EffectGeometryMap geometries;
226     osg::MatrixTransform* signsGroup;
227     GlyphGeometry* signCaseGeometry;
228     
229     GlyphGeometry* getGeometry(Effect* eff)
230     {
231         EffectGeometryMap::iterator it = geometries.find(eff);
232         if (it != geometries.end()) {
233             return it->second;
234         }
235         
236         GlyphGeometry* gg = makeGeometry(eff, signsGroup);
237         geometries[eff] = gg;
238         return gg;
239     }
240     
241     void makeFace(const ElementVec& elements, double hpos, const osg::Matrix& xform)
242     {
243         BOOST_FOREACH(element_info* element, elements) {
244             GlyphGeometry* gg = getGeometry(element->material->get_effect());
245             gg->addGlyph(element->glyph, hpos, grounddist, element->abswidth, element->height, xform);
246             hpos += element->abswidth;
247             delete element;
248         }
249     }
250     
251 };
252
253 AirportSignBuilder::AirportSignBuilder(SGMaterialLib* mats, const SGGeod& center) :
254     d(new AirportSignBuilderPrivate)
255 {
256     d->signsGroup = new osg::MatrixTransform;
257     d->signsGroup->setMatrix(makeZUpFrame(center));
258     
259     assert(mats);
260     d->materials = mats;
261     d->signCaseGeometry = d->getGeometry(d->materials->find("signcase")->get_effect());
262 }
263
264 osg::Node* AirportSignBuilder::getSignsGroup()
265 {
266     if (0 == d->signsGroup->getNumChildren())
267         return 0;
268     return d->signsGroup;
269 }
270
271 AirportSignBuilder::~AirportSignBuilder()
272 {
273     EffectGeometryMap::iterator it;
274     for (it = d->geometries.begin(); it != d->geometries.end(); ++it) {
275         delete it->second;
276     }
277 }
278
279 void AirportSignBuilder::addSign(const SGGeod& pos, double heading, const std::string& content, int size)
280 {
281     double sign_height = 1.0;  // meter
282     string newmat = "BlackSign";
283     ElementVec elements1, elements2;
284     element_info *close1 = 0;
285     element_info *close2 = 0;
286     double total_width1 = 0.0;
287     double total_width2 = 0.0;
288     bool cmd = false;
289     bool isBackside = false;
290     char oldtype = 0, newtype = 0;
291     SGMaterial *material = 0;
292
293     if (size < -1 || size > 5){
294         SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "Found illegal sign size value of '" << size << "' for " << content << ".");
295         size = -1;
296     }
297
298     // Part I: parse & measure
299     for (const char *s = content.data(); *s; s++) {
300         string name;
301         string value;
302
303         if (*s == '{') {
304             if (cmd)
305                 SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "Illegal taxiway sign syntax. Unexpected '{' in '" << content << "'.");
306             cmd = true;
307             continue;
308
309         } else if (*s == '}') {
310             if (!cmd)
311                 SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "Illegal taxiway sign syntax. Unexpected '}' in '" << content << "'.");
312             cmd = false;
313             continue;
314
315         } else if (!cmd) {
316             name = *s;
317
318         } else {
319             if (*s == ',')
320                 continue;
321
322             for (; *s; s++) {
323                 name += *s;
324                 if (s[1] == ',' || s[1] == '}' )
325                     break;
326             }
327
328             if (!*s) {
329                 SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "unclosed { in sign contents");
330             } else if (s[1] == '=') {
331                 for (s += 2; *s; s++) {
332                     value += *s;
333                     if (s[1] == ',' || s[1] == '}')
334                         break;
335                 }
336                 if (!*s)
337                     SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "unclosed { in sign contents");
338             }
339
340             if (name == "no-entry") {
341                 sign_height = HT[size < 0 ? 3 : size];
342                 newmat = "RedSign";
343                 newtype = 'R';
344             }
345
346             else if (name == "critical") {
347                 sign_height = HT[size < 0 ? 3 : size];
348                 newmat = "SpecialSign";
349                 newtype = 'S';
350             }
351
352             else if (name == "safety") {
353                 sign_height = HT[size < 0 ? 3 : size];
354                 newmat = "SpecialSign";
355                 newtype = 'S';
356             }
357
358             else if (name == "hazard") {
359                 sign_height = HT[size < 0 ? 3 : size];
360                 newmat = "SpecialSign";
361                 newtype = 'S';
362             }
363
364             if (name == "@@") {
365                 isBackside = true;
366                 continue;
367             }
368
369             if (name.size() == 2 || name.size() == 3) {
370                 string n = name;
371                 if (n.size() == 3 && n[2] >= '1' && n[2] <= '5') {
372                     size = n[2];
373                     n = n.substr(0, 2);
374                 }
375                 if (n == "@Y") {
376                     if (size < 3) {
377                         sign_height = HT[size < 0 ? 3 : size];
378                         newmat = "YellowSign";
379                         newtype = 'Y';
380                         continue;
381                     }
382
383                 } else if (n == "@R") {
384                     if (size < 3) {
385                         sign_height = HT[size < 0 ? 3 : size];
386                         newmat = "RedSign";
387                         newtype = 'R';
388                         continue;
389                     }
390
391                 } else if (n == "@L") {
392                     if (size < 3) {
393                         sign_height = HT[size < 0 ? 3 : size];
394                         newmat = "FramedSign";
395                         newtype = 'L';
396                         continue;
397                     }
398
399                 } else if (n == "@B") {
400                     if (size < 0 || size == 4 || size == 5) {
401                         sign_height = HT[size < 0 ? 4 : size];
402                         newmat = "BlackSign";
403                         newtype = 'B';
404                         continue;
405                     }
406                 }
407             }
408
409             for (int i = 0; cmds[i].keyword; i++) {
410                 if (name == cmds[i].keyword) {
411                     name = cmds[i].glyph_name;
412                     break;
413                 }
414             }
415
416             if (name[0] == '@') {
417                 SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "ignoring unknown command `" << name << '\'');
418                 continue;
419             }
420         }
421
422         if (newmat.size()) {
423             material = d->materials->find(newmat);
424             newmat.clear();
425         }
426
427         SGMaterialGlyph *glyph = material->get_glyph(name);
428         if (!glyph) {
429             SG_LOG( SG_TERRAIN, SG_ALERT, SIGN "unsupported glyph '" << *s << '\'');
430             continue;
431         }
432
433     // in managed mode push frame stop and frame start first
434         if (!isBackside) {
435             if (newtype && newtype != oldtype) {
436                 if (close1) {
437                     elements1.push_back(close1);
438                     total_width1 += close1->glyph->get_width() * close1->scale;
439                     close1 = 0;
440                 }
441                 oldtype = newtype;
442                 SGMaterialGlyph *g = material->get_glyph("stop-frame");
443                 if (g)
444                     close1 = new element_info(material, g, sign_height, 0);
445                 g = material->get_glyph("start-frame");
446                 if (g) {
447                     element_info* e1 = new element_info(material, g, sign_height, 0);
448                     elements1.push_back(e1);
449                     total_width1 += e1->glyph->get_width() * e1->scale;
450                 }
451             }
452             // now the actually requested glyph (front)
453             element_info* e1 = new element_info(material, glyph, sign_height, 0);
454             elements1.push_back(e1);
455             total_width1 += e1->glyph->get_width() * e1->scale;
456         } else {
457             if (newtype && newtype != oldtype) {
458                 if (close2) {
459                     elements2.push_back(close2);
460                     total_width2 += close2->glyph->get_width() * close2->scale;
461                     close2 = 0;
462                 }
463                 oldtype = newtype;
464                 SGMaterialGlyph *g = material->get_glyph("stop-frame");
465                 if (g)
466                     close2 = new element_info(material, g, sign_height, 0);
467                 g = material->get_glyph("start-frame");
468                 if (g) {
469                     element_info* e2 = new element_info(material, g, sign_height, 0);
470                     elements2.push_back(e2);
471                     total_width2 += e2->glyph->get_width() * e2->scale;
472                 }
473             }
474             // now the actually requested glyph (back)
475             element_info* e2 = new element_info(material, glyph, sign_height, 0);
476             elements2.push_back(e2);
477             total_width2 += e2->glyph->get_width() * e2->scale;
478         }
479     }
480
481     // in managed mode close frame
482     if (close1) {
483         elements1.push_back(close1);
484         total_width1 += close1->glyph->get_width() * close1->scale;
485         close1 = 0;
486     }
487
488     if (close2) {
489         elements2.push_back(close2);
490         total_width2 += close2->glyph->get_width() * close2->scale;
491         close2 = 0;
492     }
493
494   // Part II: typeset
495     double boxwidth = std::max(total_width1, total_width2) * 0.5;
496     double hpos = -boxwidth;
497     SGMaterial *mat = d->materials->find("signcase");
498   
499     double coverSize = fabs(total_width1 - total_width2) * 0.5;
500     element_info* s1 = new element_info(mat, mat->get_glyph("cover1"), sign_height, coverSize);
501     element_info* s2 = new element_info(mat, mat->get_glyph("cover2"), sign_height, coverSize);
502   
503     if (total_width1 < total_width2) {            
504         elements1.insert(elements1.begin(), s1);
505         elements1.push_back(s2);
506     } else if (total_width2 < total_width1) {
507         elements2.insert(elements2.begin(), s1);
508         elements2.push_back(s2);
509     } else {
510         delete s1;
511         delete s2;
512     }
513     
514 // position the sign
515     const osg::Vec3 Z_AXIS(0, 0, 1);
516     osg::Matrix m(makeZUpFrame(pos));
517     m.preMultRotate(osg::Quat(SGMiscd::deg2rad(heading), Z_AXIS));
518     
519     // apply the inverse of the group transform, so sign vertices
520     // are relative to the tile center, and hence have a magnitude which
521     // fits in a float with sufficent precision.
522     m.postMult(d->signsGroup->getInverseMatrix());
523     
524     d->makeFace(elements1, hpos, m);
525 // Create back side
526     osg::Matrix back(m);
527     back.preMultRotate(osg::Quat(M_PI, Z_AXIS));
528     d->makeFace(elements2, hpos, back);
529     
530     d->signCaseGeometry->addSignCase(boxwidth, sign_height, m);
531 }
532
533 } // of namespace simgear
534