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