1 // apt_signs.cxx -- build airport signs on the fly
3 // Written by Curtis Olson, started July 2001.
5 // Copyright (C) 2001 Curtis L. Olson - http://www.flightgear.org/~curt
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.
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.
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.
24 # include <simgear_config.h>
28 #include <boost/foreach.hpp>
31 #include <osg/Geometry>
33 #include <osg/MatrixTransform>
34 #include <osg/StateSet>
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>
44 #include "apt_signs.hxx"
46 #define SIGN "OBJECT_SIGN: "
50 using namespace simgear;
52 // for temporary storage of sign elements
54 element_info(SGMaterial *m, SGMaterialGlyph *g, double h, double c)
55 : material(m), glyph(g), height(h), coverwidth(c)
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;
62 SGMaterialGlyph *glyph;
69 typedef std::vector<element_info*> ElementVec;
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};
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
79 // translation table for "command" to "glyph name"
82 const char *glyph_name;
100 osg::DrawArrays* quads;
102 osg::Vec3Array* vertices;
103 osg::Vec3Array* normals;
105 void addGlyph(SGMaterialGlyph* glyph, double x, double y, double width, double height, const osg::Matrix& xform)
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)));
113 // texture coordinates
114 double xoffset = glyph->get_left();
115 double texWidth = glyph->get_width();
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));
123 for (int i=0; i<4; ++i)
124 normals->push_back(xform.preMult(osg::Vec3(0, -1, 0)));
126 quads->setCount(vertices->size());
129 void addSignCase(double caseWidth, double caseHeight, const osg::Matrix& xform)
131 int last = vertices->size();
132 double texsize = caseWidth / caseHeight;
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));
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));
145 for (int i=0; i<4; ++i)
146 normals->push_back(osg::Vec3(-1, 0.0, 0));
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));
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));
159 for (int i=0; i<4; ++i)
160 normals->push_back(osg::Vec3(0, 0, 1));
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));
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));
173 for (int i=0; i<4; ++i)
174 normals->push_back(osg::Vec3(1, 0.0, 0));
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]);
183 quads->setCount(vertices->size());
187 typedef std::map<Effect*, GlyphGeometry*> EffectGeometryMap;
189 GlyphGeometry* makeGeometry(Effect* eff, osg::Group* group)
191 GlyphGeometry* gg = new GlyphGeometry;
193 EffectGeode* geode = new EffectGeode;
194 geode->setEffect(eff);
196 gg->vertices = new osg::Vec3Array;
197 gg->normals = new osg::Vec3Array;
198 gg->uvs = new osg::Vec2Array;
200 osg::Vec4Array* cl = new osg::Vec4Array;
201 cl->push_back(osg::Vec4(1, 1, 1, 1));
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);
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);
218 // see $FG_ROOT/Docs/README.scenery
223 class AirportSignBuilder::AirportSignBuilderPrivate
226 SGMaterialLib* materials;
227 EffectGeometryMap geometries;
228 osg::MatrixTransform* signsGroup;
229 GlyphGeometry* signCaseGeometry;
231 GlyphGeometry* getGeometry(Effect* eff)
233 EffectGeometryMap::iterator it = geometries.find(eff);
234 if (it != geometries.end()) {
238 GlyphGeometry* gg = makeGeometry(eff, signsGroup);
239 geometries[eff] = gg;
243 void makeFace(const ElementVec& elements, double hpos, const osg::Matrix& xform)
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;
255 AirportSignBuilder::AirportSignBuilder(SGMaterialLib* mats, const SGGeod& center) :
256 d(new AirportSignBuilderPrivate)
258 d->signsGroup = new osg::MatrixTransform;
259 d->signsGroup->setMatrix(makeZUpFrame(center));
263 d->signCaseGeometry = d->getGeometry(d->materials->find("signcase")->get_effect());
266 osg::Node* AirportSignBuilder::getSignsGroup()
268 if (0 == d->signsGroup->getNumChildren())
270 return d->signsGroup;
273 AirportSignBuilder::~AirportSignBuilder()
275 EffectGeometryMap::iterator it;
276 for (it = d->geometries.begin(); it != d->geometries.end(); ++it) {
281 void AirportSignBuilder::addSign(const SGGeod& pos, double heading, const std::string& content, int size)
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;
291 bool isBackside = false;
292 char oldtype = 0, newtype = 0;
293 SGMaterial *material = 0;
295 if (size < -1 || size > 5){
296 SG_LOG(SG_TERRAIN, SG_INFO, SIGN "Found illegal sign size value of '" << size << "' for " << content << ".");
300 // Part I: parse & measure
301 for (const char *s = content.data(); *s; s++) {
307 SG_LOG(SG_TERRAIN, SG_INFO, SIGN "Illegal taxiway sign syntax. Unexpected '{' in '" << content << "'.");
311 } else if (*s == '}') {
313 SG_LOG(SG_TERRAIN, SG_INFO, SIGN "Illegal taxiway sign syntax. Unexpected '}' in '" << content << "'.");
326 if (s[1] == ',' || s[1] == '}' )
331 SG_LOG(SG_TERRAIN, SG_INFO, SIGN "unclosed { in sign contents");
332 } else if (s[1] == '=') {
333 for (s += 2; *s; s++) {
335 if (s[1] == ',' || s[1] == '}')
339 SG_LOG(SG_TERRAIN, SG_INFO, SIGN "unclosed { in sign contents");
342 if (name == "no-entry") {
343 sign_height = HT[size < 0 ? 3 : size];
348 else if (name == "critical") {
349 sign_height = HT[size < 0 ? 3 : size];
350 newmat = "SpecialSign";
354 else if (name == "safety") {
355 sign_height = HT[size < 0 ? 3 : size];
356 newmat = "SpecialSign";
360 else if (name == "hazard") {
361 sign_height = HT[size < 0 ? 3 : size];
362 newmat = "SpecialSign";
371 if (name.size() == 2 || name.size() == 3) {
373 if (n.size() == 3 && n[2] >= '1' && n[2] <= '5') {
380 SG_LOG(SG_TERRAIN, SG_INFO, SIGN << content << " has wrong size. Allowed values are 1 to 3");
382 sign_height = HT[size < 0 ? 3 : size];
383 newmat = "YellowSign";
387 } else if (n == "@R") {
390 SG_LOG(SG_TERRAIN, SG_INFO, SIGN << content << " has wrong size. Allowed values are 1 to 3");
392 sign_height = HT[size < 0 ? 3 : size];
397 } else if (n == "@L") {
400 SG_LOG(SG_TERRAIN, SG_INFO, SIGN << content << " has wrong size. Allowed values are 1 to 3");
402 sign_height = HT[size < 0 ? 3 : size];
403 newmat = "FramedSign";
407 } else if (n == "@B") {
408 if ( (size != -1) && (size != 4) && (size != 5) ) {
410 SG_LOG(SG_TERRAIN, SG_INFO, SIGN << content << " has wrong size. Allowed values are 4 or 5");
412 sign_height = HT[size < 0 ? 4 : size];
413 newmat = "BlackSign";
419 for (int i = 0; cmds[i].keyword; i++) {
420 if (name == cmds[i].keyword) {
421 name = cmds[i].glyph_name;
426 if (name[0] == '@') {
427 SG_LOG(SG_TERRAIN, SG_INFO, SIGN "ignoring unknown command `" << name << '\'');
432 if (! newmat.empty()) {
433 material = d->materials->find(newmat);
437 SGMaterialGlyph *glyph = material->get_glyph(name);
439 SG_LOG( SG_TERRAIN, SG_INFO, SIGN "unsupported glyph '" << *s << '\'');
443 // in managed mode push frame stop and frame start first
445 if (newtype && newtype != oldtype) {
447 elements1.push_back(close1);
448 total_width1 += close1->glyph->get_width() * close1->scale;
452 SGMaterialGlyph *g = material->get_glyph("stop-frame");
454 close1 = new element_info(material, g, sign_height, 0);
455 g = material->get_glyph("start-frame");
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;
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;
467 if (newtype && newtype != oldtype) {
469 elements2.push_back(close2);
470 total_width2 += close2->glyph->get_width() * close2->scale;
474 SGMaterialGlyph *g = material->get_glyph("stop-frame");
476 close2 = new element_info(material, g, sign_height, 0);
477 g = material->get_glyph("start-frame");
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;
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;
491 // in managed mode close frame
493 elements1.push_back(close1);
494 total_width1 += close1->glyph->get_width() * close1->scale;
499 elements2.push_back(close2);
500 total_width2 += close2->glyph->get_width() * close2->scale;
505 double boxwidth = std::max(total_width1, total_width2) * 0.5;
506 double hpos = -boxwidth;
507 SGMaterial *mat = d->materials->find("signcase");
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);
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);
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));
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());
534 d->makeFace(elements1, hpos, m);
537 back.preMultRotate(osg::Quat(M_PI, Z_AXIS));
538 d->makeFace(elements2, hpos, back);
540 d->signCaseGeometry->addSignCase(boxwidth, sign_height, m);
543 } // of namespace simgear