diff --git a/src/Model/modelmgr.cxx b/src/Model/modelmgr.cxx index 3a4d3599c..65dcceed4 100644 --- a/src/Model/modelmgr.cxx +++ b/src/Model/modelmgr.cxx @@ -24,8 +24,10 @@ #include <Main/fg_props.hxx> #include <Scenery/scenery.hxx> +#include <Scenery/marker.hxx> #include <osg/ProxyNode> +#include <osgText/String> #include "modelmgr.hxx" @@ -121,6 +123,8 @@ FGModelMgr::add_model (SGPropertyNode * node) SG_LOG(SG_AIRCRAFT, SG_WARN, "add_model called with empty path"); return; } + + const std::string internal_model{node->getStringValue("internal-model", "external")}; osg::Node *object; @@ -128,21 +132,38 @@ FGModelMgr::add_model (SGPropertyNode * node) instance->loaded_node = node->addChild("loaded"); instance->loaded_node->setBoolValue(false); - try { + if (internal_model == "marker") { + std::string label{node->getStringValue("marker/text", "MARKER")}; + float r = node->getFloatValue("marker/color[0]", 1.0f); + float g = node->getFloatValue("marker/color[1]", 1.0f); + float b = node->getFloatValue("marker/color[2]", 1.0f); + osg::Vec4f color(r, g, b, 1.0f); + float font_size = node->getFloatValue("marker/size", 1.0f); + float pin_height = node->getFloatValue("marker/height", 1000.0f); + float tip_height = node->getFloatValue("marker/tip-height", 0.0f); + object = fgCreateMarkerNode(osgText::String(label, osgText::String::ENCODING_UTF8), font_size, pin_height, tip_height, color); + } + else if (internal_model == "external") { + try { std::string fullPath = simgear::SGModelLib::findDataFile(model_path); if (fullPath.empty()) { - SG_LOG(SG_AIRCRAFT, SG_ALERT, "add_model: unable to find model with name '" << model_path << "'"); - return; + SG_LOG(SG_AIRCRAFT, SG_ALERT, "add_model: unable to find model with name '" << model_path << "'"); + return; } object = SGModelLib::loadDeferredModel(fullPath, globals->get_props()); - } catch (const sg_throwable& t) { - SG_LOG(SG_AIRCRAFT, SG_ALERT, "Error loading " << model_path << ":\n " - << t.getFormattedMessage() << t.getOrigin()); - return; + } catch (const sg_throwable& t) { + SG_LOG(SG_AIRCRAFT, SG_ALERT, "Error loading " << model_path << ":\n " + << t.getFormattedMessage() << t.getOrigin()); + return; + } } - - const std::string modelName{node->getStringValue("name", model_path.c_str())}; - SG_LOG(SG_AIRCRAFT, SG_INFO, "Adding model " << modelName); + else { + object = new osg::Node; + SG_LOG(SG_AIRCRAFT, SG_WARN, "Unsupported internal-model type " << internal_model); + } + + const std::string modelName{node->getStringValue("name", model_path.c_str())}; + SG_LOG(SG_AIRCRAFT, SG_INFO, "Adding model " << modelName); SGModelPlacement *model = new SGModelPlacement; instance->model = model; diff --git a/src/Scenery/CMakeLists.txt b/src/Scenery/CMakeLists.txt index 7a4d0e3f8..aad5b4cab 100644 --- a/src/Scenery/CMakeLists.txt +++ b/src/Scenery/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES tilecache.cxx tileentry.cxx tilemgr.cxx + marker.cxx ) set(HEADERS @@ -21,6 +22,7 @@ set(HEADERS tilecache.hxx tileentry.hxx tilemgr.hxx + marker.hxx ) flightgear_component(Scenery "${SOURCES}" "${HEADERS}") diff --git a/src/Scenery/marker.cxx b/src/Scenery/marker.cxx new file mode 100644 index 000000000..4b56eefa3 --- /dev/null +++ b/src/Scenery/marker.cxx @@ -0,0 +1,119 @@ +// marker.cxx - 3D Marker pins in FlightGear +// Copyright (C) 2022 Tobias Dammers +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "Scenery/marker.hxx" +#include <simgear/scene/util/SGNodeMasks.hxx> +#include <simgear/scene/util/SGReaderWriterOptions.hxx> +#include <simgear/scene/material/Effect.hxx> +#include <simgear/scene/material/EffectGeode.hxx> +#include <osg/Geometry> +#include <osg/Material> +#include <osg/Node> +#include <osg/Billboard> +#include <osgText/Text> +#include <osgText/String> +#include <osgDB/ReadFile> +#include <osgDB/WriteFile> +#include <osgDB/Registry> + +osg::Node* fgCreateMarkerNode(const osgText::String& label, float font_size, float pin_height, float tip_height, const osg::Vec4f& color) +{ + auto mainNode = new osg::Group; + + auto textNode = new osg::Billboard; + mainNode->addChild(textNode); + + textNode->setMode(osg::Billboard::AXIAL_ROT); + + auto text = new osgText::Text; + text->setText(label); + text->setAlignment(osgText::Text::CENTER_BOTTOM); + text->setAxisAlignment(osgText::Text::XZ_PLANE); + text->setFont("Fonts/LiberationFonts/LiberationSans-Regular.ttf"); + // text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS_WITH_MAXIMUM_SCREEN_SIZE_CAPPED_BY_FONT_HEIGHT); + text->setCharacterSize(font_size, 1.0f); + text->setFontResolution(std::max(32.0f, font_size), std::max(32.0f, font_size)); + text->setColor(color); + text->setPosition(osg::Vec3(0, 0, pin_height)); + text->setBackdropType(osgText::Text::OUTLINE); + text->setBackdropColor(osg::Vec4(0, 0, 0, 0.75)); + textNode->addDrawable(text); + + float top_spacing = font_size * 0.25; + + if (pin_height - top_spacing > tip_height) { + osg::Vec4f solid = color; + osg::Vec4f transparent = color; + osg::Vec3f nvec(0, 1, 0); + + solid[3] = 1.0f; + transparent[3] = 0.0f; + + auto geoNode = new simgear::EffectGeode; + mainNode->addChild(geoNode); + auto pinGeo = new osg::Geometry; + osg::Vec3Array* vtx = new osg::Vec3Array; + osg::Vec3Array* nor = new osg::Vec3Array; + osg::Vec4Array* rgb = new osg::Vec4Array; + + nor->push_back(nvec); + + vtx->push_back(osg::Vec3f(0, 0, tip_height)); + rgb->push_back(solid); + + vtx->push_back(osg::Vec3f(-font_size * 0.125, 0, pin_height - top_spacing)); + rgb->push_back(transparent); + + vtx->push_back(osg::Vec3f(0, 0, tip_height)); + rgb->push_back(solid); + + vtx->push_back(osg::Vec3f(0, font_size * 0.125, pin_height - top_spacing)); + rgb->push_back(transparent); + + vtx->push_back(osg::Vec3f(0, 0, tip_height)); + rgb->push_back(solid); + + vtx->push_back(osg::Vec3f(font_size * 0.125, 0, pin_height - top_spacing)); + rgb->push_back(transparent); + + vtx->push_back(osg::Vec3f(0, 0, tip_height)); + rgb->push_back(solid); + + vtx->push_back(osg::Vec3f(0, -font_size * 0.125, pin_height - top_spacing)); + rgb->push_back(transparent); + + vtx->push_back(osg::Vec3f(0, 0, tip_height)); + rgb->push_back(solid); + + vtx->push_back(osg::Vec3f(-font_size * 0.125, 0, pin_height - top_spacing)); + rgb->push_back(transparent); + + pinGeo->setVertexArray(vtx); + pinGeo->setColorArray(rgb, osg::Array::BIND_PER_VERTEX); + pinGeo->setNormalArray(nor, osg::Array::BIND_OVERALL); + pinGeo->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUAD_STRIP, 0, vtx->size())); + geoNode->addDrawable(pinGeo); + + auto stateSet = geoNode->getOrCreateStateSet(); + + stateSet->setMode(GL_FOG, osg::StateAttribute::OFF); + stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateSet->setMode(GL_BLEND, osg::StateAttribute::OFF); + stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON); + stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON); + + osg::ref_ptr<simgear::SGReaderWriterOptions> opt; + opt = simgear::SGReaderWriterOptions::copyOrCreate(osgDB::Registry::instance()->getOptions()); + simgear::Effect* effect = simgear::makeEffect("Effects/marker-pin", true, opt); + if (effect) + geoNode->setEffect(effect); + } + + mainNode->setNodeMask(~simgear::CASTSHADOW_BIT); + return mainNode; +} diff --git a/src/Scenery/marker.hxx b/src/Scenery/marker.hxx new file mode 100644 index 000000000..0ea65981d --- /dev/null +++ b/src/Scenery/marker.hxx @@ -0,0 +1,16 @@ +// marker.hxx - 3D Marker pins in FlightGear +// Copyright (C) 2022 Tobias Dammers +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT + +#pragma once + +namespace osgText { + class String; +} + +namespace osg { + class Node; + class Vec4f; +} + +osg::Node* fgCreateMarkerNode(const osgText::String&, float font_size, float pin_height, float tip_height, const osg::Vec4f& color); diff --git a/src/Scripting/NasalPositioned.cxx b/src/Scripting/NasalPositioned.cxx index 3cd636d62..abaa10b90 100644 --- a/src/Scripting/NasalPositioned.cxx +++ b/src/Scripting/NasalPositioned.cxx @@ -676,6 +676,9 @@ static naRef f_geodinfo(naContext c, naRef me, int argc, naRef* args) const simgear::BVHMaterial *material; SGGeod geod = SGGeod::fromDegM(lon, lat, elev); + if (globals == nullptr) + return naNil(); + const auto scenery = globals->get_scenery(); if (scenery == nullptr) return naNil();