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