# include <simgear_config.h>
#endif
+#include <vector>
+#include <boost/foreach.hpp>
+
+#include <osg/Geode>
+#include <osg/Geometry>
+#include <osg/Group>
+#include <osg/MatrixTransform>
+#include <osg/StateSet>
+
#include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_types.hxx>
-#include <simgear/scene/tgdb/leaf.hxx>
+#include <simgear/scene/material/Effect.hxx>
+#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/material/matlib.hxx>
#include "apt_signs.hxx"
-#define TAXI "OBJECT_SIGN: "
-#define RWY "OBJECT_RUNWAY_SIGN: "
+#define SIGN "OBJECT_SIGN: "
+using std::vector;
+using namespace simgear;
// for temporary storage of sign elements
struct element_info {
- element_info(SGMaterial *m, SGMaterialGlyph *g, double h, bool l)
- : material(m), glyph(g), height(h), lighted(l)
+ element_info(SGMaterial *m, Effect *s, SGMaterialGlyph *g, double h, double c)
+ : material(m), state(s), glyph(g), height(h), coverwidth(c)
{
scale = h * m->get_xsize()
/ (m->get_ysize() < 0.001 ? 1.0 : m->get_ysize());
+ abswidth = c == 0 ? g->get_width() * scale : c;
}
SGMaterial *material;
+ Effect *state;
SGMaterialGlyph *glyph;
double height;
double scale;
- bool lighted;
+ double abswidth;
+ double coverwidth;
};
+typedef std::vector<element_info*> ElementVec;
+
+
+
-// standard panel height sizes
-const double HT[5] = {0.460, 0.610, 0.760, 1.220, 0.760};
+const double HT[5] = {0.460, 0.610, 0.760, 1.220, 0.760}; // standard panel height sizes
+const double grounddist = 0.2; // hard-code sign distance from surface for now
+const double thick = 0.1; // half the thickness of the 3D sign
// translation table for "command" to "glyph name"
const char *keyword;
const char *glyph_name;
} cmds[] = {
- {"@u", "up"},
- {"@d", "down"},
- {"@l", "left"},
- {"@lu", "left-up"},
- {"@ul", "left-up"},
- {"@ld", "left-down"},
- {"@dl", "left-down"},
- {"@r", "right"},
- {"@ru", "right-up"},
- {"@ur", "right-up"},
- {"@rd", "right-down"},
- {"@dr", "right-down"},
+ {"@u", "^u"},
+ {"@d", "^d"},
+ {"@l", "^l"},
+ {"@lu", "^lu"},
+ {"@ul", "^lu"},
+ {"@ld", "^ld"},
+ {"@dl", "^ld"},
+ {"@r", "^r"},
+ {"@ru", "^ru"},
+ {"@ur", "^ru"},
+ {"@rd", "^rd"},
+ {"@dr", "^rd"},
{0, 0},
};
+struct GlyphGeometry
+{
+ osg::DrawArrays* quads;
+ osg::Vec2Array* uvs;
+ osg::Vec3Array* vertices;
+ osg::Vec3Array* normals;
+
+ void addGlyph(SGMaterialGlyph* glyph, double x, double y, double width, double height, const osg::Matrix& xform)
+ {
+
+ vertices->push_back(xform.preMult(osg::Vec3(thick, x, y)));
+ vertices->push_back(xform.preMult(osg::Vec3(thick, x + width, y)));
+ vertices->push_back(xform.preMult(osg::Vec3(thick, x + width, y + height)));
+ vertices->push_back(xform.preMult(osg::Vec3(thick, x, y + height)));
+
+ // texture coordinates
+ double xoffset = glyph->get_left();
+ double texWidth = glyph->get_width();
+
+ uvs->push_back(osg::Vec2(xoffset, 0));
+ uvs->push_back(osg::Vec2(xoffset + texWidth, 0));
+ uvs->push_back(osg::Vec2(xoffset + texWidth, 1));
+ uvs->push_back(osg::Vec2(xoffset, 1));
+
+ // normals
+ for (int i=0; i<4; ++i)
+ normals->push_back(xform.preMult(osg::Vec3(0, -1, 0)));
+
+ quads->setCount(vertices->size());
+ }
+
+ void addSignCase(double caseWidth, double caseHeight, const osg::Matrix& xform)
+ {
+ vertices->push_back(osg::Vec3(-thick, -caseWidth, grounddist));
+ vertices->push_back(osg::Vec3(thick, -caseWidth, grounddist));
+ vertices->push_back(osg::Vec3(thick, -caseWidth, grounddist + caseHeight));
+ vertices->push_back(osg::Vec3(-thick, -caseWidth, grounddist + caseHeight));
+
+
+ for (int i=0; i<4; ++i)
+ normals->push_back(xform.preMult(osg::Vec3(-1, 0.0, 0)));
+
+ vertices->push_back(osg::Vec3(-thick, -caseWidth, grounddist + caseHeight));
+ vertices->push_back(osg::Vec3(thick, -caseWidth, grounddist + caseHeight));
+ vertices->push_back(osg::Vec3(thick, caseWidth, grounddist + caseHeight));
+ vertices->push_back(osg::Vec3(-thick, caseWidth, grounddist + caseHeight));
+
+ for (int i=0; i<4; ++i)
+ normals->push_back(xform.preMult(osg::Vec3(0, 0, 1)));
+
+ vertices->push_back(osg::Vec3(-thick, caseWidth, grounddist + caseHeight));
+ vertices->push_back(osg::Vec3(thick, caseWidth, grounddist + caseHeight));
+ vertices->push_back(osg::Vec3(thick, caseWidth, grounddist));
+ vertices->push_back(osg::Vec3(-thick, caseWidth, grounddist));
+
+ for (int i=0; i<4; ++i)
+ normals->push_back(xform.preMult(osg::Vec3(1, 0.0, 0)));
+
+ for (int i = 0; i < 3; ++i) {
+ uvs->push_back(osg::Vec2(1, 1));
+ uvs->push_back(osg::Vec2(0.75, 1));
+ uvs->push_back(osg::Vec2(0.75, 0));
+ uvs->push_back(osg::Vec2(1, 0));
+ }
+
+ quads->setCount(vertices->size());
+ }
+};
-// Y ... black on yellow -> direction, destination, boundary
-// R ... white on red -> mandatory instruction
-// L ... yellow on black with frame -> runway/taxiway location
-// B ... white on black -> runway distance remaining
-//
-// u/d/l/r ... up/down/left/right arrow or two-letter combinations
-// thereof (ur, dl, ...)
-//
-// Example: {l}E|{L}[T]|{Y,ur}L|E{r}
+typedef std::map<Effect*, GlyphGeometry*> EffectGeometryMap;
+
+void SGMakeSignFace(EffectGeometryMap& glyphs, const ElementVec& elements, double hpos, const osg::Matrix& xform)
+{
+ BOOST_FOREACH(element_info* element, elements) {
+ GlyphGeometry* gg = glyphs[element->state];
+ gg->addGlyph(element->glyph, hpos, grounddist, element->abswidth, element->height, xform);
+ hpos += element->abswidth;
+ delete element;
+ }
+}
+
+GlyphGeometry* makeGeometry(Effect* eff, osg::Group* group)
+{
+ GlyphGeometry* gg = new GlyphGeometry;
+
+ EffectGeode* geode = new EffectGeode;
+ geode->setEffect(eff);
+
+ gg->vertices = new osg::Vec3Array;
+ gg->normals = new osg::Vec3Array;
+ gg->uvs = new osg::Vec2Array;
+
+ osg::Vec4Array* cl = new osg::Vec4Array;
+ cl->push_back(osg::Vec4(1, 1, 1, 1));
+
+ osg::Geometry* geometry = new osg::Geometry;
+ geometry->setVertexArray(gg->vertices);
+ geometry->setNormalArray(gg->normals);
+ geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
+ geometry->setColorArray(cl);
+ geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
+ geometry->setTexCoordArray(0, gg->uvs);
+
+ gg->quads = new osg::DrawArrays(GL_QUADS, 0, gg->vertices->size());
+ geometry->addPrimitiveSet(gg->quads);
+ geode->addDrawable(geometry);
+ group->addChild(geode);
+ return gg;
+}
+
+// see $FG_ROOT/Docs/README.scenery
//
-ssgBranch *sgMakeSign(SGMaterialLib *matlib, const string path, const string content)
+osg::Node*
+SGMakeSign(SGMaterialLib *matlib, const string& content)
{
double sign_height = 1.0; // meter
- bool lighted = true;
-
- vector<element_info *> elements;
- element_info *close = 0;
- double total_width = 0.0;
+ string newmat = "BlackSign";
+ ElementVec elements1, elements2;
+ element_info *close1 = 0;
+ element_info *close2 = 0;
+ double total_width1 = 0.0;
+ double total_width2 = 0.0;
bool cmd = false;
- char *newmat = "BlackSign";
+ bool isBackside = false;
int size = -1;
char oldtype = 0, newtype = 0;
- ssgBranch *object = new ssgBranch();
- object->setName((char *)content.c_str());
- SGMaterial *material = matlib->find(newmat);
+ EffectGeometryMap geoms;
+
+ osg::Group* object = new osg::Group;
+ object->setName(content);
+
+ SGMaterial *material = 0;
// Part I: parse & measure
for (const char *s = content.data(); *s; s++) {
string name;
- const char *newmat = 0;
+ string value;
if (*s == '{') {
if (cmd)
- SG_LOG(SG_TERRAIN, SG_ALERT, TAXI "unexpected { in sign contents");
+ SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "Illegal taxiway sign syntax. Unexpected '{' in '" << content << "'.");
cmd = true;
continue;
} else if (*s == '}') {
if (!cmd)
- SG_LOG(SG_TERRAIN, SG_ALERT, TAXI "unexpected } in sign contents");
+ SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "Illegal taxiway sign syntax. Unexpected '}' in '" << content << "'.");
cmd = false;
continue;
if (*s == ',')
continue;
- string value;
for (; *s; s++) {
name += *s;
if (s[1] == ',' || s[1] == '}' || s[1] == '=')
break;
}
if (!*s) {
- SG_LOG(SG_TERRAIN, SG_ALERT, TAXI "unclosed { in sign contents");
+ SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "unclosed { in sign contents");
} else if (s[1] == '=') {
for (s += 2; *s; s++) {
value += *s;
break;
}
if (!*s)
- SG_LOG(SG_TERRAIN, SG_ALERT, TAXI "unclosed { in sign contents");
+ SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "unclosed { in sign contents");
}
- if (name == "@size") {
- sign_height = strtod(value.data(), 0);
+ if (name == "@@") {
+ isBackside = true;
continue;
}
- if (name == "@light") {
- lighted = (value != "0" && value != "no" && value != "off" && value != "false");
+ if (name == "@size") {
+ sign_height = strtod(value.data(), 0);
continue;
}
- if (name == "@material")
- newmat = value.data();
- else if (name.size() == 2 || name.size() == 3) {
- if (name.size() == 3 && name[2] >= '1' && name[2] <= '5') {
- size = name[2] - '1';
- name = name.substr(0, 2);
+ if (name.size() == 2 || name.size() == 3) {
+ string n = name;
+ if (n.size() == 3 && n[2] >= '1' && n[2] <= '5') {
+ size = n[2] - '1';
+ n = n.substr(0, 2);
}
-
- if (name == "@Y") {
+ if (n == "@Y") {
if (size < 3) {
sign_height = HT[size < 0 ? 2 : size];
newmat = "YellowSign";
newtype = 'Y';
+ continue;
}
- } else if (name == "@R") {
+
+ } else if (n == "@R") {
if (size < 3) {
sign_height = HT[size < 0 ? 2 : size];
newmat = "RedSign";
newtype = 'R';
+ continue;
}
- } else if (name == "@L") {
+
+ } else if (n == "@L") {
if (size < 3) {
sign_height = HT[size < 0 ? 2 : size];
newmat = "FramedSign";
newtype = 'L';
+ continue;
}
- } else if (name == "@B") {
+
+ } else if (n == "@B") {
if (size < 0 || size == 3 || size == 4) {
sign_height = HT[size < 0 ? 3 : size];
newmat = "BlackSign";
newtype = 'B';
+ continue;
}
}
}
- if (newmat) {
- material = matlib->find(newmat);
- continue;
- }
-
for (int i = 0; cmds[i].keyword; i++) {
if (name == cmds[i].keyword) {
name = cmds[i].glyph_name;
}
if (name[0] == '@') {
- SG_LOG(SG_TERRAIN, SG_ALERT, TAXI "ignoring unknown command `" << name << '\'');
+ SG_LOG(SG_TERRAIN, SG_ALERT, SIGN "ignoring unknown command `" << name << '\'');
continue;
}
}
- if (!material) {
- SG_LOG( SG_TERRAIN, SG_ALERT, TAXI "material doesn't exit");
- continue;
+ if (newmat.size()) {
+ material = matlib->find(newmat);
+ newmat.clear();
}
-
+
SGMaterialGlyph *glyph = material->get_glyph(name);
if (!glyph) {
- SG_LOG( SG_TERRAIN, SG_ALERT, TAXI "unsupported glyph `" << *s << '\'');
+ SG_LOG( SG_TERRAIN, SG_ALERT, SIGN "unsupported glyph '" << *s << '\'');
continue;
}
// in managed mode push frame stop and frame start first
- element_info *e;
- if (newtype && newtype != oldtype) {
- if (close) {
- elements.push_back(close);
- total_width += close->glyph->get_width() * close->scale;
- close = 0;
+ Effect *state = material->get_effect();
+ if (geoms[state] == NULL) {
+ geoms[state] = makeGeometry(state, object);
+ }
+
+ element_info *e1;
+ element_info *e2;
+ if (!isBackside) {
+ if (newtype && newtype != oldtype) {
+ if (close1) {
+ elements1.push_back(close1);
+ total_width1 += close1->glyph->get_width() * close1->scale;
+ close1 = 0;
+ }
+ oldtype = newtype;
+ SGMaterialGlyph *g = material->get_glyph("stop-frame");
+ if (g)
+ close1 = new element_info(material, state, g, sign_height, 0);
+ g = material->get_glyph("start-frame");
+ if (g) {
+ e1 = new element_info(material, state, g, sign_height, 0);
+ elements1.push_back(e1);
+ total_width1 += e1->glyph->get_width() * e1->scale;
+ }
}
- oldtype = newtype;
- SGMaterialGlyph *g = material->get_glyph("stop-frame");
- if (g)
- close = new element_info(material, g, sign_height, lighted);
- g = material->get_glyph("start-frame");
- if (g) {
- e = new element_info(material, g, sign_height, lighted);
- elements.push_back(e);
- total_width += e->glyph->get_width() * e->scale;
+ // now the actually requested glyph (front)
+ e1 = new element_info(material, state, glyph, sign_height, 0);
+ elements1.push_back(e1);
+ total_width1 += e1->glyph->get_width() * e1->scale;
+ } else {
+ if (newtype && newtype != oldtype) {
+ if (close2) {
+ elements2.push_back(close2);
+ total_width2 += close2->glyph->get_width() * close2->scale;
+ close2 = 0;
+ }
+ oldtype = newtype;
+ SGMaterialGlyph *g = material->get_glyph("stop-frame");
+ if (g)
+ close2 = new element_info(material, state, g, sign_height, 0);
+ g = material->get_glyph("start-frame");
+ if (g) {
+ e2 = new element_info(material, state, g, sign_height, 0);
+ elements2.push_back(e2);
+ total_width2 += e2->glyph->get_width() * e2->scale;
+ }
}
+ // now the actually requested glyph (back)
+ e2 = new element_info(material, state, glyph, sign_height, 0);
+ elements2.push_back(e2);
+ total_width2 += e2->glyph->get_width() * e2->scale;
}
- // now the actually requested glyph
- e = new element_info(material, glyph, sign_height, lighted);
- elements.push_back(e);
- total_width += e->glyph->get_width() * e->scale;
}
// in managed mode close frame
- if (close) {
- elements.push_back(close);
- total_width += close->glyph->get_width() * close->scale;
- close = 0;
+ if (close1) {
+ elements1.push_back(close1);
+ total_width1 += close1->glyph->get_width() * close1->scale;
+ close1 = 0;
}
-
- // Part II: typeset
- double hpos = -total_width / 2;
- const double dist = 0.25; // hard-code distance from surface for now
-
- sgVec3 normal;
- sgSetVec3(normal, 0, -1, 0);
-
- sgVec4 color;
- sgSetVec4(color, 1.0, 1.0, 1.0, 1.0);
-
- for (unsigned int i = 0; i < elements.size(); i++) {
- element_info *element = elements[i];
-
- double xoffset = element->glyph->get_left();
- double height = element->height;
- double width = element->glyph->get_width();
- double abswidth = width * element->scale;
-
- // vertices
- ssgVertexArray *vl = new ssgVertexArray(4);
- vl->add(0, hpos, dist);
- vl->add(0, hpos + abswidth, dist);
- vl->add(0, hpos, dist + height);
- vl->add(0, hpos + abswidth, dist + height);
-
- // texture coordinates
- ssgTexCoordArray *tl = new ssgTexCoordArray(4);
- tl->add(xoffset, 0);
- tl->add(xoffset + width, 0);
- tl->add(xoffset, 1);
- tl->add(xoffset + width, 1);
-
- // normals
- ssgNormalArray *nl = new ssgNormalArray(1);
- nl->add(normal);
-
- // colors
- ssgColourArray *cl = new ssgColourArray(1);
- cl->add(color);
-
- ssgLeaf *leaf = new ssgVtxTable(GL_TRIANGLE_STRIP, vl, nl, tl, cl);
- ssgSimpleState *state = element->material->get_state();
- if (!element->lighted) {
- // FIXME: clone state for unlighted elements only once per material
- state = (ssgSimpleState *)state->clone(SSG_CLONE_STATE);
- state->setMaterial(GL_EMISSION, 0.3, 0.3, 0.3, 1);
- }
- leaf->setState(state);
-
- object->addKid(leaf);
- hpos += abswidth;
- delete element;
+ if (close2) {
+ elements2.push_back(close2);
+ total_width2 += close2->glyph->get_width() * close2->scale;
+ close2 = 0;
}
- // minimalistic backside
- ssgVertexArray *vl = new ssgVertexArray(4);
- vl->add(0, hpos, dist);
- vl->add(0, hpos - total_width, dist);
- vl->add(0, hpos, dist + sign_height);
- vl->add(0, hpos - total_width, dist + sign_height);
-
- ssgNormalArray *nl = new ssgNormalArray(1);
- nl->add(0, 1, 0);
-
- ssgLeaf *leaf = new ssgVtxTable(GL_TRIANGLE_STRIP, vl, nl, 0, 0);
- SGMaterial *mat = matlib->find("BlackSign");
- if (mat)
- leaf->setState(mat->get_state());
- object->addKid(leaf);
-
- return object;
-}
-
-
-
-
-
-ssgBranch *sgMakeRunwaySign( SGMaterialLib *matlib,
- const string path, const string name )
-{
- // for demo purposes we assume each element (letter) is 1x1 meter.
- // Sign is placed 0.25 meters above the ground
-
- ssgBranch *object = new ssgBranch();
- object->setName( (char *)name.c_str() );
-
- double width = name.length() / 3.0;
-
- string material = name;
-
- point_list nodes;
- point_list normals;
- point_list texcoords;
- int_list vertex_index;
- int_list normal_index;
- int_list tex_index;
-
- nodes.push_back( Point3D( -width, 0, 0.25 ) );
- nodes.push_back( Point3D( width + 1, 0, 0.25 ) );
- nodes.push_back( Point3D( -width, 0, 1.25 ) );
- nodes.push_back( Point3D( width + 1, 0, 1.25 ) );
-
- normals.push_back( Point3D( 0, -1, 0 ) );
-
- texcoords.push_back( Point3D( 0, 0, 0 ) );
- texcoords.push_back( Point3D( 1, 0, 0 ) );
- texcoords.push_back( Point3D( 0, 1, 0 ) );
- texcoords.push_back( Point3D( 1, 1, 0 ) );
-
- vertex_index.push_back( 0 );
- vertex_index.push_back( 1 );
- vertex_index.push_back( 2 );
- vertex_index.push_back( 3 );
-
- normal_index.push_back( 0 );
- normal_index.push_back( 0 );
- normal_index.push_back( 0 );
- normal_index.push_back( 0 );
-
- tex_index.push_back( 0 );
- tex_index.push_back( 1 );
- tex_index.push_back( 2 );
- tex_index.push_back( 3 );
+ // Part II: typeset
+ double boxwidth = std::max(total_width1, total_width2) * 0.5;
+ double hpos = -boxwidth;
+ SGMaterial *mat = matlib->find("signcase");
+ geoms[mat->get_effect()] = makeGeometry(mat->get_effect(), object);
+
+ double coverSize = fabs(total_width1 - total_width2) * 0.5;
+
+ if (total_width1 < total_width2) {
+ if (mat) {
+ element_info* s1 = new element_info(mat, mat->get_effect(), mat->get_glyph("cover"), sign_height, coverSize);
+ element_info* s2 = new element_info(mat, mat->get_effect(), mat->get_glyph("cover"), sign_height, coverSize);
+ elements1.insert(elements1.begin(), s1);
+ elements1.push_back(s2);
+ }
- ssgLeaf *leaf = sgMakeLeaf( path, GL_TRIANGLE_STRIP, matlib, material,
- nodes, normals, texcoords,
- vertex_index, normal_index, tex_index,
- false, NULL );
+ } else if (total_width2 < total_width1) {
+ if (mat) {
+ element_info* s1 = new element_info(mat, mat->get_effect(), mat->get_glyph("cover"), sign_height, coverSize);
+ element_info* s2 = new element_info(mat, mat->get_effect(), mat->get_glyph("cover"), sign_height, coverSize);
+ elements2.insert(elements2.begin(), s1);
+ elements2.push_back(s2);
+ }
+ }
- object->addKid( leaf );
+ // Create front side
+ SGMakeSignFace(geoms, elements1, hpos, osg::Matrix::identity());
+ // Create back side
+ const osg::Vec3d axis(0, 0, 1);
+ SGMakeSignFace(geoms, elements2, hpos, osg::Matrix::rotate( M_PI, axis));
+ geoms[mat->get_effect()]->addSignCase(boxwidth, sign_height, osg::Matrix::identity());
+
return object;
}