first commit
This commit is contained in:
15
src/Model/CMakeLists.txt
Normal file
15
src/Model/CMakeLists.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
include(FlightGearComponent)
|
||||
|
||||
set(SOURCES
|
||||
acmodel.cxx
|
||||
modelmgr.cxx
|
||||
panelnode.cxx
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
acmodel.hxx
|
||||
modelmgr.hxx
|
||||
panelnode.hxx
|
||||
)
|
||||
|
||||
flightgear_component(Model "${SOURCES}" "${HEADERS}")
|
||||
19
src/Model/README
Normal file
19
src/Model/README
Normal file
@@ -0,0 +1,19 @@
|
||||
Last updated $Date$
|
||||
|
||||
This directory contains code for loading, positioning, orienting, and
|
||||
animating 3D models.
|
||||
|
||||
acmodel.cxx
|
||||
acmodel.hxx
|
||||
This module defines the FGAircraftModel subsystem, which manages the 3D
|
||||
model representing the aircraft the user is flying.
|
||||
|
||||
model.cxx
|
||||
model.hxx
|
||||
This module defines the FG3DModel class, which represents any 3D
|
||||
model in the FlightGear world.
|
||||
|
||||
modelmgr.cxx
|
||||
modelmgr.hxx
|
||||
This module defines the FGModelMgr subsystem, which manages all 3D
|
||||
models except for the aircraft.
|
||||
7
src/Model/TODO
Normal file
7
src/Model/TODO
Normal file
@@ -0,0 +1,7 @@
|
||||
- add animations for the model as a whole
|
||||
|
||||
- add submodels
|
||||
|
||||
- add billboards
|
||||
|
||||
- add UV animations
|
||||
322
src/Model/acmodel.cxx
Normal file
322
src/Model/acmodel.cxx
Normal file
@@ -0,0 +1,322 @@
|
||||
// acmodel.cxx - manage a 3D aircraft model.
|
||||
// Written by David Megginson, started 2002.
|
||||
//
|
||||
// This file is in the Public Domain, and comes with no warranty.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <cstring> // for strcmp()
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/scene/model/animation.hxx>
|
||||
#include <simgear/scene/model/placement.hxx>
|
||||
#include <simgear/scene/util/SGNodeMasks.hxx>
|
||||
#include <simgear/scene/model/modellib.hxx>
|
||||
|
||||
#include <Main/globals.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Viewer/renderer.hxx>
|
||||
#include <Viewer/viewmgr.hxx>
|
||||
#include <Viewer/view.hxx>
|
||||
#include <Scenery/scenery.hxx>
|
||||
#include <Sound/fg_fx.hxx>
|
||||
#include <GUI/Highlight.hxx>
|
||||
|
||||
#include "acmodel.hxx"
|
||||
|
||||
|
||||
static osg::Node *
|
||||
fgLoad3DModelPanel(const SGPath &path, SGPropertyNode *prop_root)
|
||||
{
|
||||
bool loadPanels = true;
|
||||
bool autoTooltipsMaster = fgGetBool("/sim/rendering/automatic-animation-tooltips/enabled");
|
||||
int autoTooltipsMasterMax = fgGetInt("/sim/rendering/automatic-animation-tooltips/max-count");
|
||||
SG_LOG(SG_INPUT, SG_DEBUG, ""
|
||||
<< " autoTooltipsMaster=" << autoTooltipsMaster
|
||||
<< " autoTooltipsMasterMax=" << autoTooltipsMasterMax
|
||||
);
|
||||
osg::Node* node = simgear::SGModelLib::loadModel(path.utf8Str(), prop_root, NULL, loadPanels, autoTooltipsMaster, autoTooltipsMasterMax);
|
||||
if (node)
|
||||
node->setNodeMask(~SG_NODEMASK_TERRAIN_BIT);
|
||||
return node;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Implementation of FGAircraftModel
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FGAircraftModel::FGAircraftModel ()
|
||||
: _velocity(SGVec3d::zeros()),
|
||||
_fx(0),
|
||||
_speed_n(0),
|
||||
_speed_e(0),
|
||||
_speed_d(0)
|
||||
{
|
||||
}
|
||||
|
||||
FGAircraftModel::~FGAircraftModel ()
|
||||
{
|
||||
// drop reference
|
||||
_fx = 0;
|
||||
shutdown();
|
||||
}
|
||||
|
||||
|
||||
// Gathers information about all animated nodes and the properties that they
|
||||
// depend on, and registers with Highlight::add_property_node().
|
||||
//
|
||||
struct VisitorHighlight : osg::NodeVisitor
|
||||
{
|
||||
VisitorHighlight()
|
||||
{
|
||||
m_highlight = globals->get_subsystem<Highlight>();
|
||||
setTraversalMode(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN);
|
||||
}
|
||||
std::string spaces()
|
||||
{
|
||||
return std::string(m_level*4, ' ');
|
||||
}
|
||||
virtual void apply(osg::Node& node)
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, spaces() << "node: " << node.libraryName() << "::" << node.className());
|
||||
m_level += 1;
|
||||
traverse(node);
|
||||
m_level -= 1;
|
||||
}
|
||||
virtual void apply(osg::Group& group)
|
||||
{
|
||||
// Parent nodes of <group> are animated by properties in
|
||||
// <m_highlight_names>, so register the association between <group> and
|
||||
// these properties.
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, spaces() << "group: " << group.libraryName() << "::" << group.className());
|
||||
for (auto name: m_highlight_names)
|
||||
{
|
||||
if (m_highlight) {
|
||||
m_highlight->addPropertyNode(name, &group);
|
||||
}
|
||||
}
|
||||
m_level += 1;
|
||||
traverse(group);
|
||||
m_level -= 1;
|
||||
}
|
||||
virtual void apply( osg::Transform& node )
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, spaces() << "transform: "
|
||||
<< node.libraryName() << "::" << node.className()
|
||||
<< ": " << typeid(node).name()
|
||||
);
|
||||
|
||||
SGSharedPtr<SGExpressiond const> expression = TransformExpression(&node);
|
||||
int num_added = 0;
|
||||
if (expression) {
|
||||
// All <nodes>'s child nodes will be affected by <expression> so
|
||||
// add all of <expression>'s properties to <m_highlight_names> so
|
||||
// that we can call Highlight::add_property_node() for each child
|
||||
// node.
|
||||
std::set<const SGPropertyNode*> properties;
|
||||
expression->collectDependentProperties(properties);
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, spaces() << typeid(node).name() << ":");
|
||||
for (auto p: properties) {
|
||||
SG_LOG(SG_GENERAL, SG_DEBUG, spaces() << " " << p->getPath(true));
|
||||
num_added += 1;
|
||||
m_highlight_names.push_back(p->getPath(true /*simplify*/));
|
||||
}
|
||||
}
|
||||
|
||||
m_level += 1;
|
||||
traverse(node);
|
||||
m_level -= 1;
|
||||
|
||||
// Remove the names we appended to <m_highlight_names> earlier.
|
||||
assert((int) m_highlight_names.size() >= num_added);
|
||||
m_highlight_names.resize(m_highlight_names.size() - num_added);
|
||||
}
|
||||
unsigned int m_level = 0; // Only used to indent diagnostics.
|
||||
std::vector<std::string> m_highlight_names;
|
||||
Highlight* m_highlight = nullptr;
|
||||
};
|
||||
|
||||
|
||||
void
|
||||
FGAircraftModel::init ()
|
||||
{
|
||||
if (_aircraft.get()) {
|
||||
SG_LOG(SG_AIRCRAFT, SG_ALERT, "FGAircraftModel::init: already inited");
|
||||
return;
|
||||
}
|
||||
|
||||
simgear::ErrorReportContext ec("primary-aircraft", "yes");
|
||||
_fx = new FGFX("fx");
|
||||
_fx->init();
|
||||
|
||||
SGPropertyNode_ptr sim = fgGetNode("/sim", true);
|
||||
for (auto model : sim->getChildren("model")) {
|
||||
std::string path = model->getStringValue("path", "Models/Geometry/glider.ac");
|
||||
std::string usage = model->getStringValue("usage", "external");
|
||||
|
||||
simgear::ErrorReportContext ec("aircraft-model", path);
|
||||
|
||||
SGPath resolvedPath = globals->resolve_aircraft_path(path);
|
||||
if (resolvedPath.isNull()) {
|
||||
simgear::reportFailure(simgear::LoadFailure::NotFound,
|
||||
simgear::ErrorCode::XMLModelLoad,
|
||||
"Failed to find aircraft model", SGPath::fromUtf8(path));
|
||||
SG_LOG(SG_AIRCRAFT, SG_ALERT, "Failed to find aircraft model: " << path);
|
||||
continue;
|
||||
}
|
||||
|
||||
osg::Node* node = NULL;
|
||||
try {
|
||||
node = fgLoad3DModelPanel( resolvedPath, globals->get_props());
|
||||
} catch (const sg_exception &ex) {
|
||||
simgear::reportFailure(simgear::LoadFailure::BadData,
|
||||
simgear::ErrorCode::XMLModelLoad,
|
||||
"Failed to load aircraft model:" + ex.getFormattedMessage(),
|
||||
ex.getLocation());
|
||||
SG_LOG(SG_AIRCRAFT, SG_ALERT, "Failed to load aircraft from " << path << ':');
|
||||
SG_LOG(SG_AIRCRAFT, SG_ALERT, " " << ex.getFormattedMessage());
|
||||
}
|
||||
|
||||
if (usage == "interior") {
|
||||
// interior model
|
||||
if (!_interior.get()) {
|
||||
_interior.reset(new SGModelPlacement);
|
||||
_interior->init(node);
|
||||
} else {
|
||||
_interior->add(node);
|
||||
}
|
||||
} else {
|
||||
// normal / exterior model
|
||||
if (!_aircraft.get()) {
|
||||
_aircraft.reset(new SGModelPlacement);
|
||||
_aircraft->init(node);
|
||||
} else {
|
||||
_aircraft->add(node);
|
||||
}
|
||||
} // of model usage switch
|
||||
} // of models iteration
|
||||
|
||||
// no models loaded, load the glider instead
|
||||
if (!_aircraft.get()) {
|
||||
SG_LOG(SG_AIRCRAFT, SG_ALERT, "(Falling back to glider.ac.)");
|
||||
osg::Node* model = fgLoad3DModelPanel( SGPath::fromUtf8("Models/Geometry/glider.ac"),
|
||||
globals->get_props());
|
||||
_aircraft.reset(new SGModelPlacement);
|
||||
_aircraft->init(model);
|
||||
|
||||
}
|
||||
|
||||
osg::Node* node = _aircraft->getSceneGraph();
|
||||
globals->get_scenery()->get_aircraft_branch()->addChild(node);
|
||||
|
||||
if (_interior.get()) {
|
||||
node = _interior->getSceneGraph();
|
||||
globals->get_scenery()->get_interior_branch()->addChild(node);
|
||||
}
|
||||
|
||||
// Register animated nodes and associated properties with Highlight.
|
||||
VisitorHighlight visitor_highlight;
|
||||
visitor_highlight.traverse(*node);
|
||||
}
|
||||
|
||||
void
|
||||
FGAircraftModel::reinit()
|
||||
{
|
||||
shutdown();
|
||||
_fx->reinit();
|
||||
init();
|
||||
// TODO globally create signals for all subsystems (re)initialized
|
||||
fgSetBool("/sim/signals/model-reinit", true);
|
||||
}
|
||||
|
||||
void
|
||||
FGAircraftModel::shutdown()
|
||||
{
|
||||
FGScenery* scenery = globals->get_scenery();
|
||||
|
||||
if (_aircraft.get()) {
|
||||
if (scenery && scenery->get_aircraft_branch()) {
|
||||
scenery->get_aircraft_branch()->removeChild(_aircraft->getSceneGraph());
|
||||
}
|
||||
}
|
||||
|
||||
if (_interior.get()) {
|
||||
if (scenery && scenery->get_interior_branch()) {
|
||||
scenery->get_interior_branch()->removeChild(_interior->getSceneGraph());
|
||||
}
|
||||
}
|
||||
|
||||
_aircraft.reset();
|
||||
_interior.reset();
|
||||
}
|
||||
|
||||
void
|
||||
FGAircraftModel::bind ()
|
||||
{
|
||||
_speed_n = fgGetNode("velocities/speed-north-fps", true);
|
||||
_speed_e = fgGetNode("velocities/speed-east-fps", true);
|
||||
_speed_d = fgGetNode("velocities/speed-down-fps", true);
|
||||
}
|
||||
|
||||
void
|
||||
FGAircraftModel::unbind ()
|
||||
{
|
||||
_fx->unbind();
|
||||
}
|
||||
|
||||
void
|
||||
FGAircraftModel::update (double dt)
|
||||
{
|
||||
int view_number = globals->get_viewmgr()->getCurrentViewIndex();
|
||||
int is_internal = fgGetBool("/sim/current-view/internal");
|
||||
|
||||
if (view_number == 0 && !is_internal) {
|
||||
_aircraft->setVisible(false);
|
||||
} else {
|
||||
_aircraft->setVisible(true);
|
||||
}
|
||||
|
||||
double heading, pitch, roll;
|
||||
globals->get_aircraft_orientation(heading, pitch, roll);
|
||||
SGQuatd orient = SGQuatd::fromYawPitchRollDeg(heading, pitch, roll);
|
||||
|
||||
SGGeod pos = globals->get_aircraft_position();
|
||||
|
||||
_aircraft->setPosition(pos);
|
||||
_aircraft->setOrientation(orient);
|
||||
_aircraft->update();
|
||||
|
||||
if (_interior.get()) {
|
||||
_interior->setPosition(pos);
|
||||
_interior->setOrientation(orient);
|
||||
_interior->update();
|
||||
}
|
||||
|
||||
// update model's audio sample values
|
||||
_fx->set_position_geod( pos );
|
||||
_fx->set_orientation( orient );
|
||||
|
||||
_velocity = SGVec3d( _speed_n->getDoubleValue(),
|
||||
_speed_e->getDoubleValue(),
|
||||
_speed_d->getDoubleValue() );
|
||||
_fx->set_velocity( _velocity );
|
||||
|
||||
float temp_c = fgGetFloat("/environment/temperature-degc");
|
||||
float humidity = fgGetFloat("/environment/relative-humidity");
|
||||
float pressure = fgGetFloat("/environment/pressure-inhg")*SG_INHG_TO_PA/1000.0f;
|
||||
_fx->set_atmosphere( temp_c, humidity, pressure );
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
SGSubsystemMgr::Registrant<FGAircraftModel> registrantFGAircraftModel(
|
||||
SGSubsystemMgr::DISPLAY);
|
||||
|
||||
// end of model.cxx
|
||||
56
src/Model/acmodel.hxx
Normal file
56
src/Model/acmodel.hxx
Normal file
@@ -0,0 +1,56 @@
|
||||
// acmodel.hxx - manage a 3D aircraft model.
|
||||
// Written by David Megginson, started 2002.
|
||||
//
|
||||
// This file is in the Public Domain, and comes with no warranty.
|
||||
|
||||
#ifndef __ACMODEL_HXX
|
||||
#define __ACMODEL_HXX 1
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Group>
|
||||
#include <osg/Switch>
|
||||
|
||||
#include <memory>
|
||||
#include <simgear/structure/subsystem_mgr.hxx> // for SGSubsystem
|
||||
#include <simgear/math/SGVec3.hxx>
|
||||
|
||||
|
||||
// Don't pull in the headers, since we don't need them here.
|
||||
class SGModelPlacement;
|
||||
class FGFX;
|
||||
|
||||
class FGAircraftModel : public SGSubsystem
|
||||
{
|
||||
public:
|
||||
FGAircraftModel ();
|
||||
virtual ~FGAircraftModel ();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void reinit() override;
|
||||
void shutdown() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "aircraft-model"; }
|
||||
|
||||
virtual SGModelPlacement * get3DModel() { return _aircraft.get(); }
|
||||
virtual SGVec3d& getVelocity() { return _velocity; }
|
||||
|
||||
private:
|
||||
void deinit ();
|
||||
|
||||
std::unique_ptr<SGModelPlacement> _aircraft;
|
||||
std::unique_ptr<SGModelPlacement> _interior;
|
||||
|
||||
SGVec3d _velocity;
|
||||
SGSharedPtr<FGFX> _fx;
|
||||
|
||||
SGPropertyNode_ptr _speed_n;
|
||||
SGPropertyNode_ptr _speed_e;
|
||||
SGPropertyNode_ptr _speed_d;
|
||||
};
|
||||
|
||||
#endif // __ACMODEL_HXX
|
||||
423
src/Model/modelmgr.cxx
Normal file
423
src/Model/modelmgr.cxx
Normal file
@@ -0,0 +1,423 @@
|
||||
// modelmgr.cxx - manage a collection of 3D models.
|
||||
// Written by David Megginson, started 2002.
|
||||
//
|
||||
// This file is in the Public Domain, and comes with no warranty.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning( disable: 4355 )
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
#include <simgear/scene/model/placement.hxx>
|
||||
#include <simgear/scene/model/modellib.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Scenery/scenery.hxx>
|
||||
#include <Scenery/marker.hxx>
|
||||
|
||||
#include <osg/ProxyNode>
|
||||
#include <osgText/String>
|
||||
|
||||
#include "modelmgr.hxx"
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
namespace {
|
||||
|
||||
class CheckInstanceModelLoadedVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
CheckInstanceModelLoadedVisitor() :
|
||||
osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR, osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
|
||||
{}
|
||||
|
||||
~CheckInstanceModelLoadedVisitor() = default;
|
||||
|
||||
void apply(osg::Node& node) override
|
||||
{
|
||||
if (!_loaded)
|
||||
return;
|
||||
traverse(node);
|
||||
}
|
||||
|
||||
void apply(osg::ProxyNode& node) override
|
||||
{
|
||||
if (!_loaded)
|
||||
return;
|
||||
|
||||
for (unsigned i = 0; i < node.getNumFileNames(); ++i) {
|
||||
if (node.getFileName(i).empty())
|
||||
continue;
|
||||
|
||||
// Check if this is already loaded.
|
||||
if (i < node.getNumChildren() && node.getChild(i))
|
||||
continue;
|
||||
|
||||
_loaded=false;
|
||||
return;
|
||||
}
|
||||
traverse(node);
|
||||
}
|
||||
|
||||
bool isLoaded() const
|
||||
{
|
||||
return _loaded;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _loaded = true;
|
||||
};
|
||||
|
||||
} // of anonymous namespace
|
||||
|
||||
FGModelMgr::FGModelMgr ()
|
||||
{
|
||||
}
|
||||
|
||||
FGModelMgr::~FGModelMgr ()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
FGModelMgr::init ()
|
||||
{
|
||||
std::vector<SGPropertyNode_ptr> model_nodes = _models->getChildren("model");
|
||||
|
||||
for (unsigned int i = 0; i < model_nodes.size(); i++)
|
||||
add_model(model_nodes[i]);
|
||||
}
|
||||
|
||||
void FGModelMgr::shutdown()
|
||||
{
|
||||
osg::Group *scene_graph = NULL;
|
||||
if (globals->get_scenery()) {
|
||||
scene_graph = globals->get_scenery()->get_scene_graph();
|
||||
}
|
||||
|
||||
// always delete instances, even if the scene-graph is gone
|
||||
for (unsigned int i = 0; i < _instances.size(); i++) {
|
||||
if (scene_graph) {
|
||||
scene_graph->removeChild(_instances[i]->model->getSceneGraph());
|
||||
}
|
||||
|
||||
delete _instances[i];
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGModelMgr::add_model (SGPropertyNode * node)
|
||||
{
|
||||
const std::string model_path{node->getStringValue("path", "Models/Geometry/glider.ac")};
|
||||
if (model_path.empty()) {
|
||||
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;
|
||||
|
||||
Instance * instance = new Instance;
|
||||
instance->loaded_node = node->addChild("loaded");
|
||||
instance->loaded_node->setBoolValue(false);
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
instance->node = node;
|
||||
|
||||
model->init( object );
|
||||
double lon = node->getDoubleValue("longitude-deg"),
|
||||
lat = node->getDoubleValue("latitude-deg"),
|
||||
elevFt = node->getDoubleValue("elevation-ft");
|
||||
|
||||
model->setPosition(SGGeod::fromDegFt(lon, lat, elevFt));
|
||||
// Set position and orientation either
|
||||
// indirectly through property refs
|
||||
// or directly with static values.
|
||||
SGPropertyNode * child = node->getChild("longitude-deg-prop");
|
||||
if (child != 0)
|
||||
instance->lon_deg_node = fgGetNode(child->getStringValue(), true);
|
||||
|
||||
child = node->getChild("latitude-deg-prop");
|
||||
if (child != 0)
|
||||
instance->lat_deg_node = fgGetNode(child->getStringValue(), true);
|
||||
|
||||
child = node->getChild("elevation-ft-prop");
|
||||
if (child != 0)
|
||||
instance->elev_ft_node = fgGetNode(child->getStringValue(), true);
|
||||
|
||||
child = node->getChild("roll-deg-prop");
|
||||
if (child != 0)
|
||||
instance->roll_deg_node = fgGetNode(child->getStringValue(), true);
|
||||
else
|
||||
model->setRollDeg(node->getDoubleValue("roll-deg"));
|
||||
|
||||
child = node->getChild("pitch-deg-prop");
|
||||
if (child != 0)
|
||||
instance->pitch_deg_node = fgGetNode(child->getStringValue(), true);
|
||||
else
|
||||
model->setPitchDeg(node->getDoubleValue("pitch-deg"));
|
||||
|
||||
child = node->getChild("heading-deg-prop");
|
||||
if (child != 0)
|
||||
instance->heading_deg_node = fgGetNode(child->getStringValue(), true);
|
||||
else
|
||||
model->setHeadingDeg(node->getDoubleValue("heading-deg"));
|
||||
|
||||
if (node->hasChild("enable-hot")) {
|
||||
osg::Node::NodeMask mask = model->getSceneGraph()->getNodeMask();
|
||||
if (node->getBoolValue("enable-hot")) {
|
||||
mask |= SG_NODEMASK_TERRAIN_BIT;
|
||||
} else {
|
||||
mask &= ~SG_NODEMASK_TERRAIN_BIT;
|
||||
}
|
||||
model->getSceneGraph()->setNodeMask(mask);
|
||||
}
|
||||
|
||||
// Add this model to the global scene graph
|
||||
globals->get_scenery()->get_scene_graph()->addChild(model->getSceneGraph());
|
||||
|
||||
|
||||
// Save this instance for updating
|
||||
add_instance(instance);
|
||||
}
|
||||
|
||||
void
|
||||
FGModelMgr::bind ()
|
||||
{
|
||||
_models = fgGetNode("/models", true);
|
||||
|
||||
_listener.reset(new Listener(this));
|
||||
_models->addChangeListener(_listener.get());
|
||||
}
|
||||
|
||||
void
|
||||
FGModelMgr::unbind ()
|
||||
{
|
||||
// work-around for FLIGHTGEAR-37D : crash when quitting during
|
||||
// early startup
|
||||
if (_listener) {
|
||||
_models->removeChangeListener(_listener.get());
|
||||
}
|
||||
|
||||
_listener.reset();
|
||||
_models.clear();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
double testNan(double val)
|
||||
{
|
||||
if (SGMisc<double>::isNaN(val))
|
||||
throw sg_range_exception("value is nan");
|
||||
|
||||
return val;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void FGModelMgr::update(double dt)
|
||||
{
|
||||
std::for_each(_instances.begin(), _instances.end(), [](FGModelMgr::Instance* instance) {
|
||||
SGModelPlacement* model = instance->model;
|
||||
double roll, pitch, heading;
|
||||
roll = pitch = heading = 0.0;
|
||||
SGGeod pos = model->getPosition();
|
||||
|
||||
try {
|
||||
// Optionally set position from properties
|
||||
if (instance->lon_deg_node != 0)
|
||||
pos.setLongitudeDeg(testNan(instance->lon_deg_node->getDoubleValue()));
|
||||
if (instance->lat_deg_node != 0)
|
||||
pos.setLatitudeDeg(testNan(instance->lat_deg_node->getDoubleValue()));
|
||||
if (instance->elev_ft_node != 0)
|
||||
pos.setElevationFt(testNan(instance->elev_ft_node->getDoubleValue()));
|
||||
|
||||
// Optionally set orientation from properties
|
||||
if (instance->roll_deg_node != 0)
|
||||
roll = testNan(instance->roll_deg_node->getDoubleValue());
|
||||
if (instance->pitch_deg_node != 0)
|
||||
pitch = testNan(instance->pitch_deg_node->getDoubleValue());
|
||||
if (instance->heading_deg_node != 0)
|
||||
heading = testNan(instance->heading_deg_node->getDoubleValue());
|
||||
} catch (const sg_range_exception&) {
|
||||
string path = instance->node->getStringValue("path", "unknown");
|
||||
SG_LOG(SG_AIRCRAFT, SG_INFO, "Instance of model " << path
|
||||
<< " has invalid values");
|
||||
return;
|
||||
}
|
||||
|
||||
model->setPosition(pos);
|
||||
// Optionally set orientation from properties
|
||||
if (instance->roll_deg_node != 0)
|
||||
model->setRollDeg(roll);
|
||||
if (instance->pitch_deg_node != 0)
|
||||
model->setPitchDeg(pitch);
|
||||
if (instance->heading_deg_node != 0)
|
||||
model->setHeadingDeg(heading);
|
||||
|
||||
instance->model->update();
|
||||
instance->checkLoaded();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
FGModelMgr::add_instance (Instance * instance)
|
||||
{
|
||||
_instances.push_back(instance);
|
||||
}
|
||||
|
||||
void
|
||||
FGModelMgr::remove_instance (Instance * instance)
|
||||
{
|
||||
std::vector<Instance *>::iterator it;
|
||||
for (it = _instances.begin(); it != _instances.end(); it++) {
|
||||
if (*it == instance) {
|
||||
_instances.erase(it);
|
||||
delete instance;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FGModelMgr::Instance*
|
||||
FGModelMgr::findInstanceByNodePath(const std::string& node_path) const
|
||||
{
|
||||
if (node_path.empty())
|
||||
return nullptr;
|
||||
|
||||
SGPropertyNode* node = fgGetNode(node_path, false);
|
||||
if (!node)
|
||||
return nullptr;
|
||||
|
||||
auto it = std::find_if(_instances.begin(), _instances.end(),
|
||||
[node](const Instance* instance)
|
||||
{ return instance->node == node; });
|
||||
|
||||
if (it == _instances.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Implementation of FGModelMgr::Instance
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FGModelMgr::Instance::~Instance ()
|
||||
{
|
||||
delete model;
|
||||
}
|
||||
|
||||
bool FGModelMgr::Instance::checkLoaded() const
|
||||
{
|
||||
if (!model)
|
||||
return false;
|
||||
|
||||
if (loaded_node->getBoolValue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
CheckInstanceModelLoadedVisitor cilv;
|
||||
model->getSceneGraph()->accept(cilv);
|
||||
const bool loadedNow = cilv.isLoaded();
|
||||
|
||||
if (loadedNow) {
|
||||
loaded_node->setBoolValue(true);
|
||||
}
|
||||
return loadedNow;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Implementation of FGModelMgr::Listener
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
FGModelMgr::Listener::childAdded(SGPropertyNode * parent, SGPropertyNode * child)
|
||||
{
|
||||
if (parent->getNameString() != "model" || child->getNameString() != "load")
|
||||
return;
|
||||
|
||||
_mgr->add_model(parent);
|
||||
}
|
||||
|
||||
void
|
||||
FGModelMgr::Listener::childRemoved(SGPropertyNode * parent, SGPropertyNode * child)
|
||||
{
|
||||
if (parent->getNameString() != "models" || child->getNameString() != "model")
|
||||
return;
|
||||
|
||||
// search instance by node and remove it from scenegraph
|
||||
std::vector<Instance *>::iterator it = _mgr->_instances.begin();
|
||||
std::vector<Instance *>::iterator end = _mgr->_instances.end();
|
||||
|
||||
for (; it != end; ++it) {
|
||||
Instance *instance = *it;
|
||||
if (instance->node != child)
|
||||
continue;
|
||||
|
||||
_mgr->_instances.erase(it);
|
||||
osg::Node *branch = instance->model->getSceneGraph();
|
||||
// OSGFIXME
|
||||
// if (shadows && instance->shadow)
|
||||
// shadows->deleteOccluder(branch);
|
||||
|
||||
if (globals->get_scenery() && globals->get_scenery()->get_scene_graph()) {
|
||||
globals->get_scenery()->get_scene_graph()->removeChild(branch);
|
||||
}
|
||||
|
||||
delete instance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
SGSubsystemMgr::Registrant<FGModelMgr> registrantFGModelMgr(
|
||||
SGSubsystemMgr::DISPLAY);
|
||||
|
||||
// end of modelmgr.cxx
|
||||
124
src/Model/modelmgr.hxx
Normal file
124
src/Model/modelmgr.hxx
Normal file
@@ -0,0 +1,124 @@
|
||||
// model-mgr.hxx - manage user-specified 3D models.
|
||||
// Written by David Megginson, started 2002.
|
||||
//
|
||||
// This file is in the Public Domain, and comes with no warranty.
|
||||
|
||||
#ifndef __MODELMGR_HXX
|
||||
#define __MODELMGR_HXX 1
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <simgear/compiler.h> // for SG_USING_STD
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
|
||||
#include <Scripting/NasalModelData.hxx>
|
||||
|
||||
// Don't pull in headers, since we don't need them here.
|
||||
class SGPropertyNode;
|
||||
class SGModelPlacement;
|
||||
|
||||
|
||||
/**
|
||||
* Manage a list of user-specified models.
|
||||
*/
|
||||
class FGModelMgr : public SGSubsystem
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* A dynamically-placed model using properties.
|
||||
*
|
||||
* The model manager uses the property nodes to update the model's
|
||||
* position and orientation; any of the property node pointers may
|
||||
* be set to zero to avoid update. Normally, a caller should
|
||||
* load the model by instantiating SGModelPlacement with the path
|
||||
* to the model or its XML wrapper, then assign any relevant
|
||||
* property node pointers.
|
||||
*
|
||||
* @see SGModelPlacement
|
||||
* @see FGModelMgr#add_instance
|
||||
*/
|
||||
struct Instance
|
||||
{
|
||||
virtual ~Instance ();
|
||||
SGModelPlacement * model = nullptr;
|
||||
SGPropertyNode_ptr node;
|
||||
SGPropertyNode_ptr lon_deg_node;
|
||||
SGPropertyNode_ptr lat_deg_node;
|
||||
SGPropertyNode_ptr elev_ft_node;
|
||||
SGPropertyNode_ptr roll_deg_node;
|
||||
SGPropertyNode_ptr pitch_deg_node;
|
||||
SGPropertyNode_ptr heading_deg_node;
|
||||
SGPropertyNode_ptr loaded_node;
|
||||
bool shadow = false;
|
||||
|
||||
bool checkLoaded() const;
|
||||
};
|
||||
|
||||
FGModelMgr ();
|
||||
virtual ~FGModelMgr ();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void shutdown() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "model-manager"; }
|
||||
|
||||
virtual void add_model (SGPropertyNode * node);
|
||||
|
||||
/**
|
||||
* Add an instance of a dynamic model to the manager.
|
||||
*
|
||||
* NOTE: pointer ownership is transferred to the model manager!
|
||||
*
|
||||
* The caller is responsible for setting up the Instance structure
|
||||
* as required. The model manager will continuously update the
|
||||
* location and orientation of the model based on the current
|
||||
* values of the properties.
|
||||
*/
|
||||
virtual void add_instance (Instance * instance);
|
||||
|
||||
|
||||
/**
|
||||
* Remove an instance of a dynamic model from the manager.
|
||||
*
|
||||
* NOTE: the manager will delete the instance as well.
|
||||
*/
|
||||
virtual void remove_instance (Instance * instance);
|
||||
|
||||
|
||||
/**
|
||||
* Finds an instance in the model manager from a given node path in the property tree.
|
||||
* A possible path could be "models/model[0]"
|
||||
*
|
||||
* NOTE: the manager will delete the instance as well.
|
||||
*/
|
||||
Instance* findInstanceByNodePath(const std::string& nodePath) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Listener class that adds models at runtime.
|
||||
*/
|
||||
class Listener : public SGPropertyChangeListener
|
||||
{
|
||||
public:
|
||||
Listener(FGModelMgr *mgr) : _mgr(mgr) {}
|
||||
virtual void childAdded (SGPropertyNode * parent, SGPropertyNode * child);
|
||||
virtual void childRemoved (SGPropertyNode * parent, SGPropertyNode * child);
|
||||
|
||||
private:
|
||||
FGModelMgr * _mgr;
|
||||
};
|
||||
|
||||
SGPropertyNode_ptr _models;
|
||||
std::unique_ptr<Listener> _listener;
|
||||
|
||||
std::vector<Instance *> _instances;
|
||||
};
|
||||
|
||||
#endif // __MODELMGR_HXX
|
||||
432
src/Model/panelnode.cxx
Normal file
432
src/Model/panelnode.cxx
Normal file
@@ -0,0 +1,432 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include "panelnode.hxx"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include <osg/Geode>
|
||||
#include <osg/Switch>
|
||||
#include <osg/BlendFunc>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/scene/util/SGPickCallback.hxx>
|
||||
#include <simgear/scene/util/SGSceneUserData.hxx>
|
||||
#include <simgear/scene/util/SGNodeMasks.hxx>
|
||||
|
||||
#include <GUI/FlightGear_pu.h>
|
||||
#include <Main/fg_os.hxx>
|
||||
#include <Cockpit/panel.hxx>
|
||||
#include <Cockpit/panel_io.hxx>
|
||||
#include "Viewer/view.hxx"
|
||||
#include "Viewer/viewmgr.hxx"
|
||||
|
||||
using std::vector;
|
||||
|
||||
class PanelTransformListener : public SGPropertyChangeListener
|
||||
{
|
||||
public:
|
||||
PanelTransformListener(FGPanelNode* pn) : _panelNode(pn) {}
|
||||
|
||||
virtual void valueChanged (SGPropertyNode * node)
|
||||
{
|
||||
_panelNode->dirtyBound();
|
||||
}
|
||||
private:
|
||||
FGPanelNode* _panelNode;
|
||||
};
|
||||
|
||||
class PanelPathListener : public SGPropertyChangeListener
|
||||
{
|
||||
public:
|
||||
PanelPathListener(FGPanelNode* pn) : _panelNode(pn) {}
|
||||
|
||||
virtual void valueChanged (SGPropertyNode * node)
|
||||
{
|
||||
_panelNode->setPanelPath(node->getStringValue());
|
||||
}
|
||||
private:
|
||||
FGPanelNode* _panelNode;
|
||||
};
|
||||
|
||||
class FGPanelPickCallback : public SGPickCallback {
|
||||
public:
|
||||
FGPanelPickCallback(FGPanelNode* p) :
|
||||
panel(p)
|
||||
{}
|
||||
|
||||
virtual bool buttonPressed( int b,
|
||||
const osgGA::GUIEventAdapter&,
|
||||
const Info& info )
|
||||
{
|
||||
if (!panel->getPanel()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
button = b;
|
||||
// convert to panel coordinates
|
||||
osg::Matrixd m = osg::Matrixd::inverse(panel->transformMatrix());
|
||||
picked = toOsg(info.local) * m;
|
||||
SG_LOG( SG_INSTR, SG_DEBUG, "panel pick: " << toSG(picked) );
|
||||
|
||||
// send to the panel
|
||||
return panel->getPanel()->doLocalMouseAction(button, MOUSE_BUTTON_DOWN,
|
||||
picked.x(), picked.y());
|
||||
}
|
||||
|
||||
virtual void update(double dt, int keyModState)
|
||||
{
|
||||
panel->getPanel()->updateMouseDelay(dt);
|
||||
}
|
||||
|
||||
virtual void buttonReleased( int,
|
||||
const osgGA::GUIEventAdapter&,
|
||||
const Info* )
|
||||
{
|
||||
panel->getPanel()->doLocalMouseAction(button, MOUSE_BUTTON_UP,
|
||||
picked.x(), picked.y());
|
||||
}
|
||||
|
||||
private:
|
||||
FGPanelNode* panel;
|
||||
int button;
|
||||
osg::Vec3 picked;
|
||||
};
|
||||
|
||||
class FGPanelSwitchCallback : public osg::NodeCallback {
|
||||
public:
|
||||
FGPanelSwitchCallback(FGPanelNode* pn) :
|
||||
panel(pn),
|
||||
visProp(fgGetNode("/sim/panel/visibility"))
|
||||
{
|
||||
}
|
||||
|
||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
assert(dynamic_cast<osg::Switch*>(node));
|
||||
osg::Switch* sw = static_cast<osg::Switch*>(node);
|
||||
|
||||
if (!visProp->getBoolValue()) {
|
||||
sw->setValue(0, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
panel->lazyLoad(); // isVisible check needs the panel loaded for auto-hide flag
|
||||
bool enabled = panel->isVisible2d();
|
||||
sw->setValue(0, enabled);
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
traverse(node, nv);
|
||||
}
|
||||
|
||||
private:
|
||||
FGPanelNode* panel;
|
||||
SGPropertyNode_ptr visProp;
|
||||
};
|
||||
|
||||
|
||||
FGPanelNode::FGPanelNode(SGPropertyNode* props) :
|
||||
_is2d(false),
|
||||
_resizeToViewport(false),
|
||||
_listener(NULL)
|
||||
{
|
||||
commonInit();
|
||||
_panelPath = props->getStringValue("path");
|
||||
|
||||
// And the corner points
|
||||
SGPropertyNode* pt = props->getChild("bottom-left");
|
||||
_bottomLeft[0] = pt->getFloatValue("x-m");
|
||||
_bottomLeft[1] = pt->getFloatValue("y-m");
|
||||
_bottomLeft[2] = pt->getFloatValue("z-m");
|
||||
|
||||
pt = props->getChild("top-left");
|
||||
_topLeft[0] = pt->getFloatValue("x-m");
|
||||
_topLeft[1] = pt->getFloatValue("y-m");
|
||||
_topLeft[2] = pt->getFloatValue("z-m");
|
||||
|
||||
pt = props->getChild("bottom-right");
|
||||
_bottomRight[0] = pt->getFloatValue("x-m");
|
||||
_bottomRight[1] = pt->getFloatValue("y-m");
|
||||
_bottomRight[2] = pt->getFloatValue("z-m");
|
||||
|
||||
_depthTest = props->getBoolValue("depth-test");
|
||||
}
|
||||
|
||||
FGPanelNode::FGPanelNode() :
|
||||
_is2d(true),
|
||||
_resizeToViewport(true),
|
||||
_depthTest(false)
|
||||
{
|
||||
globals->get_commands()->addCommand("panel-mouse-click", this, &FGPanelNode::panelMouseClickCommand);
|
||||
|
||||
SGPropertyNode* pathNode = fgGetNode("/sim/panel/path");
|
||||
_pathListener.reset(new PanelPathListener(this));
|
||||
pathNode->addChangeListener(_pathListener.get());
|
||||
setPanelPath(pathNode->getStringValue());
|
||||
|
||||
// for a 2D panel, various options adjust the transformation
|
||||
// matrix. We need to pass this data on to OSG or its bounding box
|
||||
// will be stale, and picking will break.
|
||||
// http://code.google.com/p/flightgear-bugs/issues/detail?id=864
|
||||
_listener = new PanelTransformListener(this);
|
||||
fgGetNode("/sim/panel/x-offset", true)->addChangeListener(_listener);
|
||||
fgGetNode("/sim/panel/y-offset", true)->addChangeListener(_listener);
|
||||
fgGetNode("/sim/startup/xsize", true)->addChangeListener(_listener);
|
||||
fgGetNode("/sim/startup/ysize", true)->addChangeListener(_listener);
|
||||
|
||||
commonInit();
|
||||
}
|
||||
|
||||
FGPanelNode::~FGPanelNode()
|
||||
{
|
||||
if (_is2d) {
|
||||
globals->get_commands()->removeCommand("panel-mouse-click");
|
||||
SGPropertyNode* pathNode = fgGetNode("/sim/panel/path");
|
||||
pathNode->removeChangeListener(_pathListener.get());
|
||||
}
|
||||
|
||||
if (_listener) {
|
||||
fgGetNode("/sim/panel/x-offset", true)->removeChangeListener(_listener);
|
||||
fgGetNode("/sim/panel/y-offset", true)->removeChangeListener(_listener);
|
||||
fgGetNode("/sim/startup/xsize", true)->removeChangeListener(_listener);
|
||||
fgGetNode("/sim/startup/ysize", true)->removeChangeListener(_listener);
|
||||
delete _listener;
|
||||
}
|
||||
}
|
||||
|
||||
void FGPanelNode::setPanelPath(const std::string& panel)
|
||||
{
|
||||
if (panel == _panelPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
_panelPath = panel;
|
||||
if (_panel) {
|
||||
_panel.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void FGPanelNode::lazyLoad()
|
||||
{
|
||||
if (!_panelPath.empty() && !_panel) {
|
||||
_panel = fgReadPanel(_panelPath);
|
||||
if (!_panel) {
|
||||
SG_LOG(SG_COCKPIT, SG_WARN, "failed to read panel from:" << _panelPath);
|
||||
_panelPath = std::string(); // don't keep trying to read
|
||||
return;
|
||||
}
|
||||
|
||||
_panel->setDepthTest(_depthTest);
|
||||
initWithPanel();
|
||||
}
|
||||
}
|
||||
|
||||
void FGPanelNode::commonInit()
|
||||
{
|
||||
setUseDisplayList(false);
|
||||
setDataVariance(Object::DYNAMIC);
|
||||
getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
||||
getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON);
|
||||
}
|
||||
|
||||
void FGPanelNode::initWithPanel()
|
||||
{
|
||||
int i;
|
||||
|
||||
// Read out the pixel-space info
|
||||
float panelWidth = _panel->getWidth();
|
||||
float panelHeight = _panel->getHeight();
|
||||
|
||||
_panel->getLogicalExtent(_xmin, _ymin, _xmax, _ymax);
|
||||
|
||||
// Now generate our transformation matrix. For shorthand, use
|
||||
// "a", "b", and "c" as our corners and "m" as the matrix. The
|
||||
// vector u goes from a to b, v from a to c, and w is a
|
||||
// perpendicular cross product.
|
||||
osg::Vec3 a = _bottomLeft;
|
||||
osg::Vec3 b = _bottomRight;
|
||||
osg::Vec3 c = _topLeft;
|
||||
osg::Vec3 u = b - a;
|
||||
osg::Vec3 v = c - a;
|
||||
osg::Vec3 w = u^v;
|
||||
|
||||
osg::Matrix& m = _xform;
|
||||
if ((u.length2() == 0.0) || (b.length2() == 0.0)) {
|
||||
m.makeIdentity();
|
||||
} else {
|
||||
// Now generate a trivial basis transformation matrix. If we want
|
||||
// to map the three unit vectors to three arbitrary vectors U, V,
|
||||
// and W, then those just become the columns of the 3x3 matrix.
|
||||
m(0,0) = u[0]; m(1,0) = v[0]; m(2,0) = w[0]; m(3,0) = a[0];// |Ux Vx Wx|
|
||||
m(0,1) = u[1]; m(1,1) = v[1]; m(2,1) = w[1]; m(3,1) = a[1];//m = |Uy Vy Wy|
|
||||
m(0,2) = u[2]; m(1,2) = v[2]; m(2,2) = w[2]; m(3,2) = a[2];// |Uz Vz Wz|
|
||||
m(0,3) = 0; m(1,3) = 0; m(2,3) = 0; m(3,3) = 1;
|
||||
}
|
||||
|
||||
// The above matrix maps the unit (!) square to the panel
|
||||
// rectangle. Postmultiply scaling factors that match the
|
||||
// pixel-space size of the panel.
|
||||
for(i=0; i<4; ++i) {
|
||||
m(0,i) *= 1.0/panelWidth;
|
||||
m(1,i) *= 1.0/panelHeight;
|
||||
}
|
||||
|
||||
dirtyBound();
|
||||
}
|
||||
|
||||
osg::Matrix FGPanelNode::transformMatrix() const
|
||||
{
|
||||
if (!_panel) {
|
||||
return osg::Matrix();
|
||||
}
|
||||
|
||||
if (!_resizeToViewport) {
|
||||
return _xform;
|
||||
}
|
||||
|
||||
double s = _panel->getAspectScale();
|
||||
osg::Matrix m = osg::Matrix::scale(s, s, 1.0);
|
||||
m *= osg::Matrix::translate(_panel->getXOffset(), _panel->getYOffset(), 0.0);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
void
|
||||
FGPanelNode::drawImplementation(osg::State& state) const
|
||||
{
|
||||
if (!_panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::RefMatrix> mv = new osg::RefMatrix;
|
||||
mv->set(transformMatrix() * state.getModelViewMatrix());
|
||||
state.applyModelViewMatrix(mv.get());
|
||||
|
||||
_panel->draw(state);
|
||||
}
|
||||
|
||||
osg::BoundingBox FGPanelNode::computeBoundingBox() const
|
||||
{
|
||||
|
||||
osg::Vec3 coords[3];
|
||||
osg::Matrix m(transformMatrix());
|
||||
coords[0] = m.preMult(osg::Vec3(_xmin,_ymin,0));
|
||||
coords[1] = m.preMult(osg::Vec3(_xmax,_ymin,0));
|
||||
coords[2] = m.preMult(osg::Vec3(_xmin,_ymax,0));
|
||||
|
||||
osg::BoundingBox bb;
|
||||
bb.expandBy(coords[0]);
|
||||
bb.expandBy(coords[1]);
|
||||
bb.expandBy(coords[2]);
|
||||
return bb;
|
||||
}
|
||||
|
||||
void FGPanelNode::accept(osg::PrimitiveFunctor& functor) const
|
||||
{
|
||||
osg::Vec3 coords[4];
|
||||
osg::Matrix m(transformMatrix());
|
||||
|
||||
coords[0] = m.preMult(osg::Vec3(_xmin,_ymin,0));
|
||||
coords[1] = m.preMult(osg::Vec3(_xmax,_ymin,0));
|
||||
coords[2] = m.preMult(osg::Vec3(_xmax, _ymax, 0));
|
||||
coords[3] = m.preMult(osg::Vec3(_xmin,_ymax,0));
|
||||
|
||||
functor.setVertexArray(4, coords);
|
||||
functor.drawArrays( GL_QUADS, 0, 4);
|
||||
}
|
||||
|
||||
bool FGPanelNode::isVisible2d() const
|
||||
{
|
||||
if (!_panel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_hideNonDefaultViews) {
|
||||
_hideNonDefaultViews = fgGetNode("/sim/panel/hide-nonzero-view", true);
|
||||
}
|
||||
|
||||
if (_hideNonDefaultViews->getBoolValue()) {
|
||||
if (globals->get_viewmgr()->getCurrentViewIndex() != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_autoHide2d) {
|
||||
_autoHide2d = fgGetNode("/sim/panel/hide-nonzero-heading-offset", true);
|
||||
}
|
||||
|
||||
if (_panel->getAutohide() && _autoHide2d->getBoolValue()) {
|
||||
if (!globals->get_current_view()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return globals->get_current_view()->getHeadingOffset_deg() == 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static osg::Node* createGeode(FGPanelNode* panel)
|
||||
{
|
||||
osg::Geode* geode = new osg::Geode;
|
||||
geode->addDrawable(panel);
|
||||
|
||||
geode->setNodeMask(SG_NODEMASK_PICK_BIT | SG_NODEMASK_2DPANEL_BIT);
|
||||
|
||||
SGSceneUserData* userData;
|
||||
userData = SGSceneUserData::getOrCreateSceneUserData(geode);
|
||||
userData->setPickCallback(new FGPanelPickCallback(panel));
|
||||
return geode;
|
||||
}
|
||||
|
||||
osg::Node* FGPanelNode::create2DPanelNode()
|
||||
{
|
||||
FGPanelNode* drawable = new FGPanelNode;
|
||||
|
||||
osg::Switch* ps = new osg::Switch;
|
||||
osg::StateSet* stateSet = ps->getOrCreateStateSet();
|
||||
stateSet->setRenderBinDetails(1000, "RenderBin");
|
||||
ps->addChild(createGeode(drawable));
|
||||
|
||||
// speed optimization?
|
||||
stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
|
||||
stateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA));
|
||||
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
|
||||
stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
|
||||
stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
|
||||
|
||||
ps->setUpdateCallback(new FGPanelSwitchCallback(drawable));
|
||||
return ps;
|
||||
}
|
||||
|
||||
osg::Node* FGPanelNode::load(SGPropertyNode *n)
|
||||
{
|
||||
FGPanelNode* drawable = new FGPanelNode(n);
|
||||
drawable->lazyLoad(); // force load now for 2.5D panels
|
||||
return createGeode(drawable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in command: pass a mouse click to the panel.
|
||||
*
|
||||
* button: the mouse button number, zero-based.
|
||||
* is-down: true if the button is down, false if it is up.
|
||||
* x-pos: the x position of the mouse click.
|
||||
* y-pos: the y position of the mouse click.
|
||||
*/
|
||||
bool
|
||||
FGPanelNode::panelMouseClickCommand(const SGPropertyNode * arg, SGPropertyNode * root)
|
||||
{
|
||||
return _panel->doMouseAction(arg->getIntValue("button"),
|
||||
arg->getBoolValue("is-down") ? PU_DOWN : PU_UP,
|
||||
arg->getIntValue("x-pos"),
|
||||
arg->getIntValue("y-pos"));
|
||||
}
|
||||
90
src/Model/panelnode.hxx
Normal file
90
src/Model/panelnode.hxx
Normal file
@@ -0,0 +1,90 @@
|
||||
#ifndef FG_PANELNODE_HXX
|
||||
#define FG_PANELNODE_HXX
|
||||
|
||||
#include <osg/Vec3>
|
||||
#include <osg/Matrix>
|
||||
#include <osg/Drawable>
|
||||
#include <osg/Version>
|
||||
|
||||
#include <memory>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
class FGPanel;
|
||||
class SGPropertyNode;
|
||||
|
||||
// PanelNode defines an OSG drawable wrapping the 2D panel drawing code
|
||||
|
||||
class FGPanelNode : public osg::Drawable
|
||||
{
|
||||
public:
|
||||
FGPanelNode(SGPropertyNode* props);
|
||||
virtual ~FGPanelNode();
|
||||
|
||||
// OSGFIXME
|
||||
virtual osg::Object* cloneType() const { return 0; }
|
||||
virtual osg::Object* clone(const osg::CopyOp& copyop) const { return 0; }
|
||||
|
||||
FGPanel* getPanel() { return _panel; }
|
||||
|
||||
virtual void drawImplementation(osg::RenderInfo& renderInfo) const
|
||||
{ drawImplementation(*renderInfo.getState()); }
|
||||
|
||||
void drawImplementation(osg::State& state) const;
|
||||
|
||||
osg::BoundingBox computeBoundingBox() const override;
|
||||
|
||||
|
||||
/** Return true, FGPanelNode does support accept(PrimitiveFunctor&). */
|
||||
virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }
|
||||
|
||||
virtual void accept(osg::PrimitiveFunctor& functor) const;
|
||||
|
||||
static osg::Node* load(SGPropertyNode *n);
|
||||
static osg::Node* create2DPanelNode();
|
||||
|
||||
osg::Matrix transformMatrix() const;
|
||||
|
||||
void setPanelPath(const std::string& panel);
|
||||
void lazyLoad();
|
||||
|
||||
/**
|
||||
* is visible in 2D mode or not?
|
||||
*/
|
||||
bool isVisible2d() const;
|
||||
private:
|
||||
FGPanelNode(); // for 2D panels
|
||||
|
||||
void commonInit();
|
||||
void initWithPanel();
|
||||
|
||||
bool panelMouseClickCommand(const SGPropertyNode * arg, SGPropertyNode * root);
|
||||
|
||||
const bool _is2d;
|
||||
SGSharedPtr<FGPanel> _panel;
|
||||
std::string _panelPath;
|
||||
|
||||
bool _resizeToViewport;
|
||||
bool _depthTest;
|
||||
|
||||
// Panel corner coordinates
|
||||
osg::Vec3 _bottomLeft, _topLeft, _bottomRight;
|
||||
|
||||
int _xmin, _ymin, _xmax, _ymax;
|
||||
|
||||
// The matrix that results, which transforms 2D x/y panel
|
||||
// coordinates into 3D coordinates of the panel quadrilateral.
|
||||
osg::Matrix _xform;
|
||||
|
||||
SGPropertyChangeListener* _listener;
|
||||
std::unique_ptr<SGPropertyChangeListener> _pathListener;
|
||||
|
||||
/// should the 2D panel auto-hide when the view orientation changes
|
||||
mutable SGPropertyNode_ptr _autoHide2d;
|
||||
|
||||
/// should the 2D panel be hidden in views other than the default (view 0)
|
||||
mutable SGPropertyNode_ptr _hideNonDefaultViews;
|
||||
};
|
||||
|
||||
|
||||
#endif // FG_PANELNODE_HXX
|
||||
Reference in New Issue
Block a user