]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tgdb/apt_signs.cxx
Add support for double-sided taxiway signs and create 3D models for them
[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
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
42 #include "apt_signs.hxx"
43
44 #define SIGN "OBJECT_SIGN: "
45
46 using std::vector;
47 using namespace simgear;
48
49 // for temporary storage of sign elements
50 struct element_info {
51     element_info(SGMaterial *m, Effect *s, SGMaterialGlyph *g, double h, double c)
52         : material(m), state(s), glyph(g), height(h), coverwidth(c)
53     {
54         scale = h * m->get_xsize()
55                 / (m->get_ysize() < 0.001 ? 1.0 : m->get_ysize());
56         abswidth = c == 0 ? g->get_width() * scale : c;
57     }
58     SGMaterial *material;
59     Effect *state;
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 const double HT[5] = {0.460, 0.610, 0.760, 1.220, 0.760}; // standard panel height sizes
70 const double grounddist = 0.2;     // hard-code sign distance from surface for now
71 const double thick = 0.1;    // half the thickness of the 3D sign
72
73
74 // translation table for "command" to "glyph name"
75 struct pair {
76     const char *keyword;
77     const char *glyph_name;
78 } cmds[] = {
79     {"@u",       "^u"},
80     {"@d",       "^d"},
81     {"@l",       "^l"},
82     {"@lu",      "^lu"},
83     {"@ul",      "^lu"},
84     {"@ld",      "^ld"},
85     {"@dl",      "^ld"},
86     {"@r",       "^r"},
87     {"@ru",      "^ru"},
88     {"@ur",      "^ru"},
89     {"@rd",      "^rd"},
90     {"@dr",      "^rd"},
91     {0, 0},
92 };
93
94 void SGMakeSignFace(osg::Group* object, const ElementVec& elements, double hpos, bool isBackside)
95 {
96     std::map<Effect*, EffectGeode*> geodesByEffect;
97
98     for (unsigned int i = 0; i < elements.size(); i++) {
99         element_info *element = elements[i];
100
101         double xoffset = element->glyph->get_left();
102         double height = element->height;
103         double width = element->glyph->get_width();
104
105         osg::Vec3Array* vl = new osg::Vec3Array;
106         osg::Vec2Array* tl = new osg::Vec2Array;
107
108         // vertices
109         vl->push_back(osg::Vec3(thick, hpos,            grounddist));
110         vl->push_back(osg::Vec3(thick, hpos + element->abswidth, grounddist));
111         vl->push_back(osg::Vec3(thick, hpos,            grounddist + height));
112         vl->push_back(osg::Vec3(thick, hpos + element->abswidth, grounddist + height));
113
114         // texture coordinates
115         tl->push_back(osg::Vec2(xoffset,         0));
116         tl->push_back(osg::Vec2(xoffset + width, 0));
117         tl->push_back(osg::Vec2(xoffset,         1));
118         tl->push_back(osg::Vec2(xoffset + width, 1));
119
120         // normals
121         osg::Vec3Array* nl = new osg::Vec3Array;
122         nl->push_back(osg::Vec3(0, -1, 0));
123
124         // colors
125         osg::Vec4Array* cl = new osg::Vec4Array;
126         cl->push_back(osg::Vec4(1, 1, 1, 1));
127
128         osg::Geometry* geometry = new osg::Geometry;
129         geometry->setVertexArray(vl);
130         geometry->setNormalArray(nl);
131         geometry->setNormalBinding(osg::Geometry::BIND_OVERALL);
132         geometry->setColorArray(cl);
133         geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
134         geometry->setTexCoordArray(0, tl);
135         geometry->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLE_STRIP, 0, vl->size()));
136
137         EffectGeode* geode = geodesByEffect[element->state];
138         if (!geode) {
139             geode = new EffectGeode();
140             geode->setEffect(element->state);
141             geodesByEffect[element->state] = geode;
142         }
143
144         geode->addDrawable(geometry);
145
146         if (isBackside) {
147             osg::MatrixTransform* transform = new osg::MatrixTransform();
148             const double angle = 180;
149             const osg::Vec3d axis(0, 0, 1);
150             transform->setMatrix(osg::Matrix::rotate( osg::DegreesToRadians(angle), axis));
151             transform->addChild(geode);
152             object->addChild(transform);
153         } else
154             object->addChild(geode);
155
156         hpos += element->abswidth;
157         delete element;
158     }
159 }
160
161
162 // see $FG_ROOT/Docs/README.scenery
163 //
164 osg::Node*
165 SGMakeSign(SGMaterialLib *matlib, const string& content)
166 {
167     double sign_height = 1.0;  // meter
168     string newmat = "BlackSign";
169     ElementVec elements1, elements2;
170     element_info *close1 = 0;
171     element_info *close2 = 0;
172     double total_width1 = 0.0;
173     double total_width2 = 0.0;
174     bool cmd = false;
175     bool isBackside = false;
176     int size = -1;
177     char oldtype = 0, newtype = 0;
178
179     osg::Group* object = new osg::Group;
180     object->setName(content);
181
182     SGMaterial *material = 0;
183
184     // Part I: parse & measure
185     for (const char *s = content.data(); *s; s++) {
186         string name;
187         string value;
188
189         if (*s == '{') {
190             if (cmd)
191                 SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "Illegal taxiway sign syntax. Unexpected '{' in '" << content << "'.");
192             cmd = true;
193             continue;
194
195         } else if (*s == '}') {
196             if (!cmd)
197                 SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "Illegal taxiway sign syntax. Unexpected '}' in '" << content << "'.");
198             cmd = false;
199             continue;
200
201         } else if (!cmd) {
202             name = *s;
203
204         } else {
205             if (*s == ',')
206                 continue;
207
208             for (; *s; s++) {
209                 name += *s;
210                 if (s[1] == ',' || s[1] == '}' || s[1] == '=')
211                     break;
212             }
213             if (!*s) {
214                 SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "unclosed { in sign contents");
215             } else if (s[1] == '=') {
216                 for (s += 2; *s; s++) {
217                     value += *s;
218                     if (s[1] == ',' || s[1] == '}')
219                         break;
220                 }
221                 if (!*s)
222                     SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "unclosed { in sign contents");
223             }
224
225             if (name == "@@") {
226                 isBackside = true;
227                 continue;
228             }
229
230             if (name == "@size") {
231                 sign_height = strtod(value.data(), 0);
232                 continue;
233             }
234
235             if (name.size() == 2 || name.size() == 3) {
236                 string n = name;
237                 if (n.size() == 3 && n[2] >= '1' && n[2] <= '5') {
238                     size = n[2] - '1';
239                     n = n.substr(0, 2);
240                 }
241                 if (n == "@Y") {
242                     if (size < 3) {
243                         sign_height = HT[size < 0 ? 2 : size];
244                         newmat = "YellowSign";
245                         newtype = 'Y';
246                         continue;
247                     }
248
249                 } else if (n == "@R") {
250                     if (size < 3) {
251                         sign_height = HT[size < 0 ? 2 : size];
252                         newmat = "RedSign";
253                         newtype = 'R';
254                         continue;
255                     }
256
257                 } else if (n == "@L") {
258                     if (size < 3) {
259                         sign_height = HT[size < 0 ? 2 : size];
260                         newmat = "FramedSign";
261                         newtype = 'L';
262                         continue;
263                     }
264
265                 } else if (n == "@B") {
266                     if (size < 0 || size == 3 || size == 4) {
267                         sign_height = HT[size < 0 ? 3 : size];
268                         newmat = "BlackSign";
269                         newtype = 'B';
270                         continue;
271                     }
272                 }
273             }
274
275             for (int i = 0; cmds[i].keyword; i++) {
276                 if (name == cmds[i].keyword) {
277                     name = cmds[i].glyph_name;
278                     break;
279                 }
280             }
281
282             if (name[0] == '@') {
283                 SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "ignoring unknown command `" << name << '\'');
284                 continue;
285             }
286         }
287
288         if (newmat.size()) {
289             material = matlib->find(newmat);
290             newmat.clear();
291         }
292  
293         SGMaterialGlyph *glyph = material->get_glyph(name);
294         if (!glyph) {
295             SG_LOG( SG_TERRAIN, SG_ALERT, SIGN "unsupported glyph '" << *s << '\'');
296             continue;
297         }
298
299         // in managed mode push frame stop and frame start first
300         Effect *state = material->get_effect();
301         element_info *e1;
302         element_info *e2;
303         if (!isBackside) {
304             if (newtype && newtype != oldtype) {
305                 if (close1) {
306                     elements1.push_back(close1);
307                     total_width1 += close1->glyph->get_width() * close1->scale;
308                     close1 = 0;
309                 }
310                 oldtype = newtype;
311                 SGMaterialGlyph *g = material->get_glyph("stop-frame");
312                 if (g)
313                     close1 = new element_info(material, state, g, sign_height, 0);
314                 g = material->get_glyph("start-frame");
315                 if (g) {
316                     e1 = new element_info(material, state, g, sign_height, 0);
317                     elements1.push_back(e1);
318                     total_width1 += e1->glyph->get_width() * e1->scale;
319                 }
320             }
321             // now the actually requested glyph (front)
322             e1 = new element_info(material, state, glyph, sign_height, 0);
323             elements1.push_back(e1);
324             total_width1 += e1->glyph->get_width() * e1->scale;
325         } else {
326             if (newtype && newtype != oldtype) {
327                 if (close2) {
328                     elements2.push_back(close2);
329                     total_width2 += close2->glyph->get_width() * close2->scale;
330                     close2 = 0;
331                 }
332                 oldtype = newtype;
333                 SGMaterialGlyph *g = material->get_glyph("stop-frame");
334                 if (g)
335                     close2 = new element_info(material, state, g, sign_height, 0);
336                 g = material->get_glyph("start-frame");
337                 if (g) {
338                     e2 = new element_info(material, state, g, sign_height, 0);
339                     elements2.push_back(e2);
340                     total_width2 += e2->glyph->get_width() * e2->scale;
341                 }
342             }
343             // now the actually requested glyph (back)
344             e2 = new element_info(material, state, glyph, sign_height, 0);
345             elements2.push_back(e2);
346             total_width2 += e2->glyph->get_width() * e2->scale;
347         }
348     }
349
350     // in managed mode close frame
351     if (close1) {
352         elements1.push_back(close1);
353         total_width1 += close1->glyph->get_width() * close1->scale;
354         close1 = 0;
355     }
356
357     if (close2) {
358         elements2.push_back(close2);
359         total_width2 += close2->glyph->get_width() * close2->scale;
360         close2 = 0;
361     }
362
363
364     // Part II: typeset
365     double hpos = -total_width1 / 2;
366     double boxwidth = total_width1 > total_width2 ? total_width1/2 : total_width2/2;
367
368     if (total_width1 < total_width2) {
369         double ssize = (total_width2 - total_width1)/2;
370         SGMaterial *mat = matlib->find("signcase");
371         if (mat) {
372             element_info* s1 = new element_info(mat, mat->get_effect(), mat->get_glyph("cover"), sign_height, ssize);
373             element_info* s2 = new element_info(mat, mat->get_effect(), mat->get_glyph("cover"), sign_height, ssize);
374             elements1.insert(elements1.begin(), s1);
375             elements1.push_back(s2);
376         }
377         hpos = -total_width2 / 2;
378
379     } else if (total_width2 < total_width1) {
380         double ssize = (total_width1 - total_width2)/2;
381         SGMaterial *mat = matlib->find("signcase");
382         if (mat) {
383             element_info* s1 = new element_info(mat, mat->get_effect(), mat->get_glyph("cover"), sign_height, ssize);
384             element_info* s2 = new element_info(mat, mat->get_effect(), mat->get_glyph("cover"), sign_height, ssize);
385             elements2.insert(elements2.begin(), s1);
386             elements2.push_back(s2);
387         }
388         hpos = -total_width1 / 2;
389     }
390
391     // Create front side
392     SGMakeSignFace(object, elements1, hpos, false);
393
394     // Create back side
395     SGMakeSignFace(object, elements2, hpos, true);
396
397
398     // Add 3D case for signs
399     osg::Vec3Array* vl = new osg::Vec3Array;
400     osg::Vec2Array* tl = new osg::Vec2Array;
401     osg::Vec3Array* nl = new osg::Vec3Array;
402
403     //left side
404     vl->push_back(osg::Vec3(-thick, -boxwidth,  grounddist));
405     vl->push_back(osg::Vec3(thick, -boxwidth,  grounddist));
406     vl->push_back(osg::Vec3(-thick, -boxwidth,  grounddist + sign_height));
407     vl->push_back(osg::Vec3(thick, -boxwidth,  grounddist + sign_height));
408
409     nl->push_back(osg::Vec3(-1, 0, 0));
410
411     //top
412     vl->push_back(osg::Vec3(-thick, -boxwidth,  grounddist + sign_height));
413     vl->push_back(osg::Vec3(thick,  -boxwidth,  grounddist + sign_height));
414     vl->push_back(osg::Vec3(-thick, boxwidth, grounddist + sign_height));
415     vl->push_back(osg::Vec3(thick,  boxwidth, grounddist + sign_height));
416
417     nl->push_back(osg::Vec3(0, 0, 1));
418
419     //right side
420     vl->push_back(osg::Vec3(-thick, boxwidth, grounddist + sign_height));
421     vl->push_back(osg::Vec3(thick,  boxwidth, grounddist + sign_height));
422     vl->push_back(osg::Vec3(-thick, boxwidth, grounddist));
423     vl->push_back(osg::Vec3(thick,  boxwidth, grounddist));
424
425     for (int i = 0; i < 4; ++i) {
426         tl->push_back(osg::Vec2(1,    1));
427         tl->push_back(osg::Vec2(0.75, 1));
428         tl->push_back(osg::Vec2(1,    0));
429         tl->push_back(osg::Vec2(0.75, 0));
430     }
431
432     nl->push_back(osg::Vec3(1, 0, 0));
433
434     osg::Vec4Array* cl = new osg::Vec4Array;
435     cl->push_back(osg::Vec4(1.0, 1.0, 1.0, 1.0));
436     osg::Geometry* geometry = new osg::Geometry;
437     geometry->setVertexArray(vl);
438     geometry->setNormalArray(nl);
439     geometry->setNormalBinding(osg::Geometry::BIND_OVERALL);
440     geometry->setTexCoordArray(0, tl);
441     geometry->setColorArray(cl);
442     geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
443     geometry->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLE_STRIP, 0, vl->size()));
444     EffectGeode* geode = new EffectGeode;
445     geode->addDrawable(geometry);
446     SGMaterial *mat = matlib->find("signcase");
447     if (mat)
448       geode->setEffect(mat->get_effect());
449     object->addChild(geode);
450
451     return object;
452 }