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