first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
include(FlightGearComponent)
set(SOURCES
SceneryPager.cxx
redout.cxx
scenery.cxx
terrain_stg.cxx
terrain_pgt.cxx
tilecache.cxx
tileentry.cxx
tilemgr.cxx
marker.cxx
)
set(HEADERS
SceneryPager.hxx
redout.hxx
scenery.hxx
terrain.hxx
terrain_stg.hxx
terrain_pgt.hxx
tilecache.hxx
tileentry.hxx
tilemgr.hxx
marker.hxx
)
flightgear_component(Scenery "${SOURCES}" "${HEADERS}")

View File

@@ -0,0 +1,123 @@
// SceneryPager.cxx -- Interface to OSG database pager
//
// Copyright (C) 2007 Tim Moore timoore@redhat.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <config.h>
#include "SceneryPager.hxx"
#include <algorithm>
#include <functional>
#include <simgear/debug/logstream.hxx>
using namespace osg;
using namespace flightgear;
SceneryPager::SceneryPager()
{
_pagerRequests.reserve(48);
_deleteRequests.reserve(16);
}
SceneryPager::SceneryPager(const SceneryPager& rhs) :
DatabasePager(rhs)
{
}
SceneryPager::~SceneryPager()
{
SG_LOG(SG_TERRAIN, SG_INFO, "Destroying scenery pager");
}
void SceneryPager::clearRequests()
{
_pagerRequests.clear();
_deleteRequests.clear();
}
void SceneryPager::queueRequest(const std::string& fileName, Group* group,
float priority, FrameStamp* frameStamp,
ref_ptr<Referenced>& databaseRequest,
osgDB::ReaderWriter::Options* options)
{
_pagerRequests.push_back(PagerRequest(fileName, group, priority,
frameStamp,
databaseRequest,
options));
}
void SceneryPager::queueDeleteRequest(osg::ref_ptr<osg::Object>& objptr)
{
_deleteRequests.push_back(objptr);
objptr = 0;
}
// Work around interface change in
// osgDB::DatabasePager::requestNodeFile
namespace
{
struct NodePathProxy
{
NodePathProxy(NodePath& nodePath)
: _nodePath(nodePath)
{
}
operator Group* () { return static_cast<Group*>(_nodePath.back()); }
operator NodePath& () { return _nodePath; }
NodePath& _nodePath;
};
}
void SceneryPager::PagerRequest::doRequest(SceneryPager* pager)
{
if (_group->getNumChildren() == 0) {
NodePath path;
path.push_back(_group.get());
pager->requestNodeFile(_fileName, NodePathProxy(path), _priority,
_frameStamp.get(),
*_databaseRequest,
_options.get());
}
}
void SceneryPager::signalEndFrame()
{
using namespace std;
bool areDeleteRequests = false;
bool arePagerRequests = false;
if (!_deleteRequests.empty()) {
areDeleteRequests = true;
OpenThreads::ScopedLock<OpenThreads::Mutex>
lock(_fileRequestQueue->_childrenToDeleteListMutex);
ObjectList& deleteList = _fileRequestQueue->_childrenToDeleteList;
deleteList.insert(deleteList.end(),
_deleteRequests.begin(),
_deleteRequests.end());
_deleteRequests.clear();
}
if (!_pagerRequests.empty()) {
arePagerRequests = true;
for (auto req : _pagerRequests) {
req.doRequest(this);
}
_pagerRequests.clear();
}
if (areDeleteRequests && !arePagerRequests) {
_fileRequestQueue->updateBlock();
}
DatabasePager::signalEndFrame();
}

View File

@@ -0,0 +1,84 @@
// SceneryPager.hxx -- Interface to OSG database pager
//
// Copyright (C) 2007 Tim Moore timoore@redhat.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef FLIGHTGEAR_SCENERYPAGERHXX
#define FLIGHTGEAR_SCENERYPAGERHXX 1
#include <string>
#include <vector>
#include <osg/FrameStamp>
#include <osg/Group>
#include <osgDB/DatabasePager>
namespace flightgear
{
class SceneryPager : public osgDB::DatabasePager
{
public:
SceneryPager();
SceneryPager(const SceneryPager& rhs);
// Unhide DatabasePager::requestNodeFile
using osgDB::DatabasePager::requestNodeFile;
void queueRequest(const std::string& fileName, osg::Group* node,
float priority, osg::FrameStamp* frameStamp,
osg::ref_ptr<osg::Referenced>& databaseRequest,
osgDB::ReaderWriter::Options* options);
// This is passed a ref_ptr so that it can "take ownership" of the
// node to delete and decrement its refcount while holding the
// lock on the delete list.
void queueDeleteRequest(osg::ref_ptr<osg::Object>& objptr);
virtual void signalEndFrame();
void clearRequests();
protected:
// Queue up file requests until the end of the frame
struct PagerRequest
{
PagerRequest() : _priority(0.0f), _databaseRequest(0) {}
PagerRequest(const PagerRequest& rhs) :
_fileName(rhs._fileName), _group(rhs._group),
_priority(rhs._priority), _frameStamp(rhs._frameStamp),
_options(rhs._options), _databaseRequest(rhs._databaseRequest) {}
PagerRequest(const std::string& fileName, osg::Group* group,
float priority, osg::FrameStamp* frameStamp,
osg::ref_ptr<Referenced>& databaseRequest,
osgDB::ReaderWriter::Options* options):
_fileName(fileName), _group(group), _priority(priority),
_frameStamp(frameStamp), _options(options),
_databaseRequest(&databaseRequest)
{}
void doRequest(SceneryPager* pager);
std::string _fileName;
osg::ref_ptr<osg::Group> _group;
float _priority;
osg::ref_ptr<osg::FrameStamp> _frameStamp;
osg::ref_ptr<osgDB::ReaderWriter::Options> _options;
osg::ref_ptr<osg::Referenced>* _databaseRequest;
};
typedef std::vector<PagerRequest> PagerRequestList;
PagerRequestList _pagerRequests;
typedef std::vector<osg::ref_ptr<osg::Object> > DeleteRequestList;
DeleteRequestList _deleteRequests;
virtual ~SceneryPager();
};
}
#endif

46
src/Scenery/design Normal file
View File

@@ -0,0 +1,46 @@
(x) class fgOBJECT {
// material property pointer
int material_ptr;
// culling data
double ref[3];
double radius;
// OpenGL display list for object data
GLint display_list_ptr;
}
(x) class fgTILE {
// culling data
double ref[3];
double radius;
list < fgOBJECT > object_list;
}
class fgMATERIAL {
int list_size;
int counter;
public:
// material properties
GLfloat ambient[4], diffuse[4], specular[4];
GLint texture_ptr;
// transient list of objects with this material type (used for sorting
// by material to reduce GL state changes when rendering the scene
fgOBJECT *material_object_list[lots];
init_list();
append_list();
list_traverse_init();
next_obj()
}
class fgMATERIAL_MGR {
list < fgMATERIAL > material_list;
}

119
src/Scenery/marker.cxx Normal file
View File

@@ -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;
}

16
src/Scenery/marker.hxx Normal file
View File

@@ -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);

117
src/Scenery/redout.cxx Normal file
View File

@@ -0,0 +1,117 @@
// redout.hxx
//
// Written by Mathias Froehlich,
//
// Copyright (C) 2007 Mathias Froehlich
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "redout.hxx"
#include <assert.h>
#include <osg/BlendFunc>
#include <osg/Depth>
#include <osg/Geometry>
#include <osg/Geode>
#include <osg/Switch>
#include <osg/StateSet>
#include <simgear/props/props.hxx>
#include <Main/fg_props.hxx>
class FGRedoutCallback : public osg::NodeCallback {
public:
FGRedoutCallback(osg::Vec4Array* colorArray) :
_colorArray(colorArray),
_redoutNode(fgGetNode("/sim/rendering/redout", true))
{
fgGetNode("/sim/rendering/redout/alpha", true);
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
assert(dynamic_cast<osg::Switch*>(node));
osg::Switch* sw = static_cast<osg::Switch*>(node);
// Check if we need to do something further ...
float alpha = _redoutNode->getFloatValue("alpha", 0);
bool enabled = (0 < alpha);
sw->setValue(0, enabled);
if (!enabled)
return;
(*_colorArray)[0][0] = _redoutNode->getFloatValue("red", 1);
(*_colorArray)[0][1] = _redoutNode->getFloatValue("green", 0);
(*_colorArray)[0][2] = _redoutNode->getFloatValue("blue", 0);
(*_colorArray)[0][3] = alpha;
_colorArray->dirty();
}
private:
osg::ref_ptr<osg::Vec4Array> _colorArray;
SGSharedPtr<SGPropertyNode> _redoutNode;
};
osg::Node* FGCreateRedoutNode()
{
osg::Geometry* geometry = new osg::Geometry;
geometry->setUseDisplayList(false);
osg::StateSet* stateSet = geometry->getOrCreateStateSet();
stateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF);
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
stateSet->setAttribute(new osg::BlendFunc);
stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
stateSet->setAttribute(new osg::Depth(osg::Depth::ALWAYS, 0, 1, false));
stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
stateSet->setRenderBinDetails(1000, "RenderBin");
osg::Vec3Array* vertexArray = new osg::Vec3Array;
vertexArray->push_back(osg::Vec3(-1, -1, 0));
vertexArray->push_back(osg::Vec3( 1, -1, 0));
vertexArray->push_back(osg::Vec3( 1, 1, 0));
vertexArray->push_back(osg::Vec3(-1, 1, 0));
geometry->setVertexArray(vertexArray);
osg::Vec4Array* colorArray = new osg::Vec4Array;
colorArray->push_back(osg::Vec4(1, 0, 0, 1));
geometry->setColorArray(colorArray);
geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
geometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 4));
osg::Geode* geode = new osg::Geode;
geode->addDrawable(geometry);
osg::Camera* camera = new osg::Camera;
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
camera->setProjectionMatrix(osg::Matrix::ortho2D(-1, 1, -1, 1));
camera->setViewMatrix(osg::Matrix::identity());
camera->setRenderOrder(osg::Camera::NESTED_RENDER);
camera->setClearMask(0);
camera->setAllowEventFocus(false);
camera->setCullingActive(false);
camera->addChild(geode);
osg::Switch* sw = new osg::Switch;
sw->setUpdateCallback(new FGRedoutCallback(colorArray));
sw->addChild(camera);
return sw;
}

29
src/Scenery/redout.hxx Normal file
View File

@@ -0,0 +1,29 @@
// redout.hxx
//
// Written by Mathias Froehlich,
//
// Copyright (C) 2007 Mathias Froehlich
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef FG_REDOUT_HXX
#define FG_REDOUT_HXX
#include <osg/Node>
extern osg::Node* FGCreateRedoutNode();
#endif

585
src/Scenery/scenery.cxx Normal file
View File

@@ -0,0 +1,585 @@
// scenery.cxx -- data structures and routines for managing scenery.
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <config.h>
#include <simgear/simgear_config.h>
#include <stdio.h>
#include <string.h>
#include <osg/Camera>
#include <osg/Transform>
#include <osg/MatrixTransform>
#include <osg/PositionAttitudeTransform>
#include <osg/CameraView>
#include <osg/LOD>
#include <osgViewer/Viewer>
#include <simgear/constants.h>
#include <simgear/sg_inlines.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/scene/tgdb/userdata.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/util/SGNodeMasks.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/util/SGSceneUserData.hxx>
#include <simgear/scene/model/CheckSceneryVisitor.hxx>
#include <simgear/scene/sky/sky.hxx>
#include <simgear/scene/util/SGSceneFeatures.hxx>
#include <simgear/bvh/BVHNode.hxx>
#include <simgear/bvh/BVHLineSegmentVisitor.hxx>
#include <simgear/structure/commands.hxx>
#include <Viewer/renderer.hxx>
#include <Main/fg_props.hxx>
#include <GUI/MouseCursor.hxx>
#include <Main/sentryIntegration.hxx>
#include "scenery.hxx"
#include "terrain_stg.hxx"
#ifdef ENABLE_GDAL
#include "terrain_pgt.hxx"
#endif
using namespace flightgear;
using namespace simgear;
class FGGroundPickCallback : public SGPickCallback {
public:
FGGroundPickCallback() : SGPickCallback(PriorityScenery)
{ }
virtual bool buttonPressed( int button,
const osgGA::GUIEventAdapter&,
const Info& info )
{
// only on left mouse button
if (button != 0)
return false;
SGGeod geod = SGGeod::fromCart(info.wgs84);
SG_LOG( SG_TERRAIN, SG_INFO, "Got ground pick at " << geod );
SGPropertyNode *c = fgGetNode("/sim/input/click", true);
c->setDoubleValue("longitude-deg", geod.getLongitudeDeg());
c->setDoubleValue("latitude-deg", geod.getLatitudeDeg());
c->setDoubleValue("elevation-m", geod.getElevationM());
c->setDoubleValue("elevation-ft", geod.getElevationFt());
fgSetBool("/sim/signals/click", 1);
return true;
}
};
class FGSceneryIntersect : public osg::NodeVisitor {
public:
FGSceneryIntersect(const SGLineSegmentd& lineSegment,
const osg::Node* skipNode) :
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN),
_lineSegment(lineSegment),
_skipNode(skipNode),
_material(0),
_haveHit(false)
{ }
bool getHaveHit() const
{ return _haveHit; }
const SGLineSegmentd& getLineSegment() const
{ return _lineSegment; }
const simgear::BVHMaterial* getMaterial() const
{ return _material; }
virtual void apply(osg::Node& node)
{
if (&node == _skipNode)
return;
if (!testBoundingSphere(node.getBound()))
return;
addBoundingVolume(node);
}
virtual void apply(osg::Group& group)
{
if (&group == _skipNode)
return;
if (!testBoundingSphere(group.getBound()))
return;
traverse(group);
addBoundingVolume(group);
}
virtual void apply(osg::Transform& transform)
{ handleTransform(transform); }
virtual void apply(osg::Camera& camera)
{
if (camera.getRenderOrder() != osg::Camera::NESTED_RENDER)
return;
handleTransform(camera);
}
virtual void apply(osg::CameraView& transform)
{ handleTransform(transform); }
virtual void apply(osg::MatrixTransform& transform)
{ handleTransform(transform); }
virtual void apply(osg::PositionAttitudeTransform& transform)
{ handleTransform(transform); }
private:
void handleTransform(osg::Transform& transform)
{
if (&transform == _skipNode)
return;
// Hmm, may be this needs to be refined somehow ...
if (transform.getReferenceFrame() != osg::Transform::RELATIVE_RF)
return;
if (!testBoundingSphere(transform.getBound()))
return;
osg::Matrix inverseMatrix;
if (!transform.computeWorldToLocalMatrix(inverseMatrix, this))
return;
osg::Matrix matrix;
if (!transform.computeLocalToWorldMatrix(matrix, this))
return;
SGLineSegmentd lineSegment = _lineSegment;
bool haveHit = _haveHit;
const simgear::BVHMaterial* material = _material;
_haveHit = false;
_lineSegment = lineSegment.transform(SGMatrixd(inverseMatrix.ptr()));
addBoundingVolume(transform);
traverse(transform);
if (_haveHit) {
_lineSegment = _lineSegment.transform(SGMatrixd(matrix.ptr()));
} else {
_lineSegment = lineSegment;
_material = material;
_haveHit = haveHit;
}
}
simgear::BVHNode* getNodeBoundingVolume(osg::Node& node)
{
SGSceneUserData* userData = SGSceneUserData::getSceneUserData(&node);
if (!userData)
return 0;
return userData->getBVHNode();
}
void addBoundingVolume(osg::Node& node)
{
simgear::BVHNode* bvNode = getNodeBoundingVolume(node);
if (!bvNode)
return;
// Find ground intersection on the bvh nodes
simgear::BVHLineSegmentVisitor lineSegmentVisitor(_lineSegment,
0/*startTime*/);
bvNode->accept(lineSegmentVisitor);
if (!lineSegmentVisitor.empty()) {
_lineSegment = lineSegmentVisitor.getLineSegment();
_material = lineSegmentVisitor.getMaterial();
_haveHit = true;
}
}
bool testBoundingSphere(const osg::BoundingSphere& bound) const
{
if (!bound.valid())
return false;
SGSphered sphere(toVec3d(toSG(bound._center)), bound._radius);
return intersects(_lineSegment, sphere);
}
SGLineSegmentd _lineSegment;
const osg::Node* _skipNode;
const simgear::BVHMaterial* _material;
bool _haveHit;
};
class FGScenery::TextureCacheListener : public SGPropertyChangeListener
{
protected:
const char* root_node_path = "/sim/rendering/texture-cache";
public:
TextureCacheListener()
{
SGPropertyNode_ptr textureCacheNode = fgGetNode(root_node_path, true);
setupPropertyListener(textureCacheNode, "cache-enabled");
setupPropertyListener(textureCacheNode, "compress-transparent");
setupPropertyListener(textureCacheNode, "compress-solid");
setupPropertyListener(textureCacheNode, "compress");
}
~TextureCacheListener()
{
SGPropertyNode_ptr maskNode = fgGetNode(root_node_path);
for (int i = 0; i < maskNode->nChildren(); ++i) {
maskNode->getChild(i)->removeChangeListener(this);
}
}
void setupPropertyListener(SGPropertyNode_ptr textureCacheNode, const char *node)
{
textureCacheNode->getChild(node, 0, true)->addChangeListener(this, true);
}
virtual void valueChanged(SGPropertyNode * node)
{
bool b = node->getBoolValue();
std::string name(node->getNameString());
if (name == "cache-enabled") {
SGSceneFeatures::instance()->setTextureCacheActive(b);
}
else if (name == "compress-transparent" || name == "compress") {
SGSceneFeatures::instance()->setTextureCacheCompressionActiveTransparent(b);
}
else if (name == "compress-solid" || name == "compress") {
SGSceneFeatures::instance()->setTextureCacheCompressionActive(b);
}
}
};
class FGScenery::ElevationMeshListener : public SGPropertyChangeListener
{
protected:
const char* root_node_path = "/scenery/elevation-mesh";
const char* lod_node_path = "/sim/rendering/static-lod";
public:
ElevationMeshListener()
{
SGPropertyNode_ptr elevationMeshNode = fgGetNode(root_node_path, true);
setupPropertyListener(elevationMeshNode, "constraint-gap-m");
setupPropertyListener(elevationMeshNode, "lod-range-factor");
setupPropertyListener(elevationMeshNode, "sample-ratio");
setupPropertyListener(elevationMeshNode, "vertical-scale");
setupPropertyListener(elevationMeshNode, "separate-water-mesh");
// We also need to set the maximum range based on the LOD ranges
SGPropertyNode_ptr lodNode = fgGetNode(lod_node_path, true);
setupPropertyListener(lodNode, "detailed");
setupPropertyListener(lodNode, "rough-delta");
setupPropertyListener(lodNode, "bare-delta");
}
~ElevationMeshListener()
{
SGPropertyNode_ptr node = fgGetNode(root_node_path);
for (int i = 0; i < node->nChildren(); ++i) {
node->getChild(i)->removeChangeListener(this);
}
}
void setupPropertyListener(SGPropertyNode_ptr elevationMeshNode, const char *node)
{
elevationMeshNode->getChild(node, 0, true)->addChangeListener(this, true);
}
virtual void valueChanged(SGPropertyNode * node)
{
float f = node->getFloatValue();
bool b = node->getBoolValue();
std::string name(node->getNameString());
if (name == "constraint-gap-m") {
SGSceneFeatures::instance()->setVPBConstraintGap(f);
} else if (name == "sample-ratio") {
SGSceneFeatures::instance()->setVPBSampleRatio(f);
} else if (name == "vertical-scale") {
SGSceneFeatures::instance()->setVPBVerticalScale(f);
} else if (name == "separate-water-mesh") {
SGSceneFeatures::instance()->setVPBSeparateWaterMesh(b);
} else {
SG_LOG(SG_TERRAIN, SG_ALERT, "Unexpected property in listener " << node->getPath());
}
}
};
class FGScenery::ScenerySwitchListener : public SGPropertyChangeListener
{
public:
ScenerySwitchListener(FGScenery* scenery) :
_scenery(scenery)
{
SGPropertyNode_ptr maskNode = fgGetNode("/sim/rendering/draw-mask", true);
maskNode->getChild("terrain", 0, true)->addChangeListener(this, true);
maskNode->getChild("models", 0, true)->addChangeListener(this, true);
maskNode->getChild("aircraft", 0, true)->addChangeListener(this, true);
maskNode->getChild("clouds", 0, true)->addChangeListener(this, true);
// legacy compatability option
fgGetNode("/sim/rendering/draw-otw")->addChangeListener(this);
// badly named property, this is what is set by --enable/disable-clouds
fgGetNode("/environment/clouds/status")->addChangeListener(this);
auto vpb_active = fgGetNode("/scenery/use-vpb");
if (vpb_active) {
vpb_active->addChangeListener(this);
SGSceneFeatures::instance()->setVPBActive(vpb_active->getBoolValue());
}
}
~ScenerySwitchListener()
{
SGPropertyNode_ptr maskNode = fgGetNode("/sim/rendering/draw-mask");
for (int i=0; i < maskNode->nChildren(); ++i) {
maskNode->getChild(i)->removeChangeListener(this);
}
fgGetNode("/sim/rendering/draw-otw")->removeChangeListener(this);
fgGetNode("/environment/clouds/status")->removeChangeListener(this);
fgGetNode("/scenery/use-vpb")->removeChangeListener(this);
}
virtual void valueChanged (SGPropertyNode * node)
{
bool b = node->getBoolValue();
std::string name(node->getNameString());
if (name == "use-vpb") {
SGSceneFeatures::instance()->setVPBActive(b);
} else if (name == "terrain") {
_scenery->scene_graph->setChildValue(_scenery->terrain_branch, b);
} else if (name == "models") {
_scenery->scene_graph->setChildValue(_scenery->models_branch, b);
} else if (name == "aircraft") {
_scenery->scene_graph->setChildValue(_scenery->aircraft_branch, b);
} else if (name == "clouds") {
// clouds live elsewhere in the scene, but we handle them here
globals->get_renderer()->getSky()->set_clouds_enabled(b);
} else if (name == "draw-otw") {
// legacy setting but let's keep it working
fgGetNode("/sim/rendering/draw-mask")->setBoolValue("terrain", b);
fgGetNode("/sim/rendering/draw-mask")->setBoolValue("models", b);
} else if (name == "status") {
fgGetNode("/sim/rendering/draw-mask")->setBoolValue("clouds", b);
}
}
private:
FGScenery* _scenery;
};
////////////////////////////////////////////////////////////////////////////
// Scenery Management system
FGScenery::FGScenery() :
_listener(nullptr), _textureCacheListener(nullptr), _elevationMeshListener(nullptr)
{
// keep reference to pager singleton, so it cannot be destroyed while FGScenery lives
_pager = FGScenery::getPagerSingleton();
// Initialise the state of the scene graph.
_inited = false;
}
FGScenery::~FGScenery()
{
delete _listener;
delete _textureCacheListener;
}
// Initialize the Scenery Management system
void FGScenery::init() {
// Already set up.
if (_inited)
return;
// Scene graph root
scene_graph = new osg::Switch;
scene_graph->setName( "FGScenery" );
// Terrain branch
terrain_branch = new osg::Group;
terrain_branch->setName( "Terrain" );
scene_graph->addChild( terrain_branch.get() );
SGSceneUserData* userData;
userData = SGSceneUserData::getOrCreateSceneUserData(terrain_branch.get());
userData->setPickCallback(new FGGroundPickCallback);
models_branch = new osg::Group;
models_branch->setName( "Models" );
scene_graph->addChild( models_branch.get() );
aircraft_branch = new osg::Group;
aircraft_branch->setName( "Aircraft" );
scene_graph->addChild( aircraft_branch.get() );
// choosing to make the interior branch a child of the main
// aircraft group, for the moment. This simplifes places which
// assume all aircraft elements are within this group - principally
// FGODGuage::set_aircraft_texture.
interior_branch = new osg::Group;
interior_branch->setName( "Interior" );
osg::LOD* interiorLOD = new osg::LOD;
interiorLOD->addChild(interior_branch.get(), 0.0, 50.0);
aircraft_branch->addChild( interiorLOD );
// Set up the particle system as a directly accessible branch of the scene graph.
auto paricles = simgear::ParticlesGlobalManager::instance();
particles_branch = paricles->getCommonRoot();
particles_branch->setName("Particles");
scene_graph->addChild(particles_branch.get());
paricles->setSwitchNode(fgGetNode("/sim/rendering/particles", true));
paricles->initFromMainThread();
// Set up the precipitation system.
precipitation_branch = new osg::Group;
precipitation_branch->setName("Precipitation");
scene_graph->addChild(precipitation_branch.get());
// initialize the terrian based on selected engine
std::string engine = fgGetString("/sim/scenery/engine", "tilecache" );
SG_LOG( SG_TERRAIN, SG_INFO, "Selected scenery is " << engine );
if ( engine == "pagedLOD" ) {
#ifdef ENABLE_GDAL
_terrain.reset(new FGPgtTerrain);
#else
_terrain.reset(new FGStgTerrain);
#endif
} else {
_terrain.reset(new FGStgTerrain);
}
_terrain->init( terrain_branch.get() );
_listener = new ScenerySwitchListener(this);
_textureCacheListener = new TextureCacheListener();
_elevationMeshListener = new ElevationMeshListener();
// Toggle the setup flag.
_inited = true;
}
void FGScenery::reinit()
{
flightgear::addSentryBreadcrumb("reloading scenery", "info");
fgSetBool("/sim/rendering/scenery-reload-required", false);
_terrain->reinit();
}
void FGScenery::shutdown()
{
_terrain->shutdown();
scene_graph = NULL;
terrain_branch = NULL;
models_branch = NULL;
aircraft_branch = NULL;
particles_branch = NULL;
precipitation_branch = NULL;
_terrain.reset();
// Toggle the setup flag.
_inited = false;
simgear::ParticlesGlobalManager::clear();
}
void FGScenery::update(double dt)
{
_terrain->update(dt);
}
void FGScenery::bind() {
}
void FGScenery::unbind() {
}
bool
FGScenery::get_cart_elevation_m(const SGVec3d& pos, double max_altoff,
double& alt,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom)
{
return _terrain->get_cart_elevation_m(pos, max_altoff, alt,
material, butNotFrom);
}
bool
FGScenery::get_elevation_m(const SGGeod& geod, double& alt,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom)
{
return _terrain->get_elevation_m( geod, alt, material,
butNotFrom );
}
bool
FGScenery::get_cart_ground_intersection(const SGVec3d& pos, const SGVec3d& dir,
SGVec3d& nearestHit,
const osg::Node* butNotFrom)
{
return _terrain->get_cart_ground_intersection( pos, dir, nearestHit, butNotFrom );
}
bool FGScenery::scenery_available(const SGGeod& position, double range_m)
{
return _terrain->scenery_available( position, range_m );
}
bool FGScenery::schedule_scenery(const SGGeod& position, double range_m, double duration)
{
return _terrain->schedule_scenery( position, range_m, duration );
}
void FGScenery::materialLibChanged()
{
_terrain->materialLibChanged();
}
static osg::ref_ptr<SceneryPager> pager;
SceneryPager* FGScenery::getPagerSingleton()
{
if (!pager)
pager = new SceneryPager;
return pager.get();
}
void FGScenery::resetPagerSingleton()
{
pager = NULL;
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGScenery> registrantFGScenery(
SGSubsystemMgr::DISPLAY,
{{"FGRenderer", SGSubsystemMgr::Dependency::NONSUBSYSTEM_HARD},
{"SGSky", SGSubsystemMgr::Dependency::NONSUBSYSTEM_HARD}});

160
src/Scenery/scenery.hxx Normal file
View File

@@ -0,0 +1,160 @@
// scenery.hxx -- data structures and routines for managing scenery.
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _SCENERY_HXX
#define _SCENERY_HXX
#ifndef __cplusplus
# error This library requires C++
#endif
#include <osg/ref_ptr>
#include <osg/Switch>
#include <simgear/compiler.h>
#include <simgear/math/SGMath.hxx>
#include <simgear/scene/model/particles.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include "SceneryPager.hxx"
#include "terrain.hxx"
namespace simgear {
class BVHMaterial;
}
class FGTerrain;
// Define a structure containing global scenery parameters
class FGScenery : public SGSubsystem
{
class ScenerySwitchListener;
friend class ScenerySwitchListener;
// scene graph
osg::ref_ptr<osg::Switch> scene_graph;
osg::ref_ptr<osg::Group> terrain_branch;
osg::ref_ptr<osg::Group> models_branch;
osg::ref_ptr<osg::Group> aircraft_branch;
osg::ref_ptr<osg::Group> interior_branch;
osg::ref_ptr<osg::Group> particles_branch;
osg::ref_ptr<osg::Group> precipitation_branch;
osg::ref_ptr<flightgear::SceneryPager> _pager;
ScenerySwitchListener* _listener;
class TextureCacheListener;
friend class TextureCacheListener;
TextureCacheListener* _textureCacheListener;
class ElevationMeshListener;
friend class ElevationMeshListener;
ElevationMeshListener* _elevationMeshListener;
public:
FGScenery();
~FGScenery();
// 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 "scenery"; }
/// Compute the elevation of the scenery at geodetic latitude lat,
/// geodetic longitude lon and not higher than max_alt.
/// If the exact flag is set to true, the scenery center is moved to
/// gain a higher accuracy of that query. The center is restored past
/// that to the original value.
/// The altitude hit is returned in the alt argument.
/// The method returns true if the scenery is available for the given
/// lat/lon pair. If there is no scenery for that point, the altitude
/// value is undefined.
/// All values are meant to be in meters or degrees.
bool get_elevation_m(const SGGeod& geod, double& alt,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom = 0);
/// Compute the elevation of the scenery below the cartesian point pos.
/// you the returned scenery altitude is not higher than the position
/// pos plus an offset given with max_altoff.
/// If the exact flag is set to true, the scenery center is moved to
/// gain a higher accuracy of that query. The center is restored past
/// that to the original value.
/// The altitude hit is returned in the alt argument.
/// The method returns true if the scenery is available for the given
/// lat/lon pair. If there is no scenery for that point, the altitude
/// value is undefined.
/// All values are meant to be in meters.
bool get_cart_elevation_m(const SGVec3d& pos, double max_altoff,
double& elevation,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom = 0);
/// Compute the nearest intersection point of the line starting from
/// start going in direction dir with the terrain.
/// The input and output values should be in cartesian coordinates in the
/// usual earth centered wgs84 coordinate system. Units are meters.
/// On success, true is returned.
bool get_cart_ground_intersection(const SGVec3d& start, const SGVec3d& dir,
SGVec3d& nearestHit,
const osg::Node* butNotFrom = 0);
osg::Group *get_scene_graph () const { return scene_graph.get(); }
osg::Group *get_terrain_branch () const { return terrain_branch.get(); }
osg::Group *get_models_branch () const { return models_branch.get(); }
osg::Group *get_aircraft_branch () const { return aircraft_branch.get(); }
osg::Group *get_interior_branch () const { return interior_branch.get(); }
osg::Group *get_particles_branch () const { return particles_branch.get(); }
osg::Group *get_precipitation_branch () const { return precipitation_branch.get(); }
/// Returns true if scenery is available for the given lat, lon position
/// within a range of range_m.
/// lat and lon are expected to be in degrees.
bool scenery_available(const SGGeod& position, double range_m);
// Static because access to the pager is needed before the rest of
// the scenery is initialized.
static flightgear::SceneryPager* getPagerSingleton();
static void resetPagerSingleton();
flightgear::SceneryPager* getPager() { return _pager.get(); }
// tile mgr api
bool schedule_scenery(const SGGeod& position, double range_m, double duration=0.0);
void materialLibChanged();
private:
// the terrain engine
std::unique_ptr<FGTerrain> _terrain;
// The state of the scene graph.
bool _inited;
};
#endif // _SCENERY_HXX

112
src/Scenery/terrain.hxx Normal file
View File

@@ -0,0 +1,112 @@
// scenery.hxx -- data structures and routines for managing scenery.
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _TERRAIN_HXX
#define _TERRAIN_HXX
#ifndef __cplusplus
# error This library requires C++
#endif
#include <osg/ref_ptr>
#include <osg/Switch>
#include <simgear/compiler.h>
#include <simgear/math/SGMath.hxx>
#include <simgear/scene/model/particles.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include "scenery.hxx"
#include "SceneryPager.hxx"
#include "tilemgr.hxx"
namespace simgear {
class BVHMaterial;
}
// Define a structure containing global scenery parameters
class FGTerrain
{
public:
FGTerrain() = default;
virtual ~FGTerrain() = default;
// Implementation of SGSubsystem. - called from Scenery
virtual void init ( osg::Group* terrain ) = 0;
virtual void reinit() = 0;
virtual void shutdown () = 0;
virtual void bind () = 0;
virtual void unbind () = 0;
virtual void update (double dt) = 0;
/// Compute the elevation of the scenery at geodetic latitude lat,
/// geodetic longitude lon and not higher than max_alt.
/// If the exact flag is set to true, the scenery center is moved to
/// gain a higher accuracy of that query. The center is restored past
/// that to the original value.
/// The altitude hit is returned in the alt argument.
/// The method returns true if the scenery is available for the given
/// lat/lon pair. If there is no scenery for that point, the altitude
/// value is undefined.
/// All values are meant to be in meters or degrees.
virtual bool get_elevation_m(const SGGeod& geod, double& alt,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom = 0) = 0;
/// Compute the elevation of the scenery below the cartesian point pos.
/// you the returned scenery altitude is not higher than the position
/// pos plus an offset given with max_altoff.
/// If the exact flag is set to true, the scenery center is moved to
/// gain a higher accuracy of that query. The center is restored past
/// that to the original value.
/// The altitude hit is returned in the alt argument.
/// The method returns true if the scenery is available for the given
/// lat/lon pair. If there is no scenery for that point, the altitude
/// value is undefined.
/// All values are meant to be in meters.
virtual bool get_cart_elevation_m(const SGVec3d& pos, double max_altoff,
double& elevation,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom = 0) = 0;
/// Compute the nearest intersection point of the line starting from
/// start going in direction dir with the terrain.
/// The input and output values should be in cartesian coordinates in the
/// usual earth centered wgs84 coordinate system. Units are meters.
/// On success, true is returned.
virtual bool get_cart_ground_intersection(const SGVec3d& start, const SGVec3d& dir,
SGVec3d& nearestHit,
const osg::Node* butNotFrom = 0) = 0;
/// Returns true if scenery is available for the given lat, lon position
/// within a range of range_m.
/// lat and lon are expected to be in degrees.
virtual bool scenery_available(const SGGeod& position, double range_m) = 0;
// tile mgr api
virtual bool schedule_scenery(const SGGeod& position, double range_m, double duration=0.0) = 0;
virtual void materialLibChanged() = 0;
};
#endif // _TERRAIN_HXX

256
src/Scenery/terrain_pgt.cxx Normal file
View File

@@ -0,0 +1,256 @@
// terrain_pgt.cxx -- data structures and routines for managing scenery.
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <config.h>
#include <simgear/simgear_config.h>
#ifdef ENABLE_GDAL
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Viewer/splash.hxx>
#include "terrain_pgt.hxx"
#include "scenery.hxx"
using namespace flightgear;
using namespace simgear;
using flightgear::SceneryPager;
// Terrain Management system
FGPgtTerrain::FGPgtTerrain() :
_scenery_loaded(fgGetNode("/sim/sceneryloaded", true)),
_scenery_override(fgGetNode("/sim/sceneryloaded-override", true))
{
_inited = false;
}
FGPgtTerrain::~FGPgtTerrain()
{
}
// Initialize the Scenery Management system
void FGPgtTerrain::init( osg::Group* terrain ) {
// Already set up.
if (_inited)
return;
SG_LOG(SG_TERRAIN, SG_INFO, "FGPgtTerrain::init");
// remember the scene terrain branch on scenegraph
terrain_branch = terrain;
// load the whole planet tile - database pager handles
// the quad tree / loading the highres tiles
osg::ref_ptr<simgear::SGReaderWriterOptions> options;
// drops the previous options reference
options = new simgear::SGReaderWriterOptions;
options->setPropertyNode(globals->get_props());
osgDB::FilePathList &fp = options->getDatabasePathList();
const PathList &sc = globals->get_fg_scenery();
fp.clear();
PathList::const_iterator it;
for (it = sc.begin(); it != sc.end(); ++it) {
fp.push_back(it->local8BitStr());
}
options->setPluginStringData("SimGear::FG_ROOT", globals->get_fg_root().utf8Str());
options->setPluginStringData("SimGear::BARE_LOD_RANGE",
fgGetString("/sim/rendering/static-lod/bare-delta",
std::to_string(SG_OBJECT_RANGE_BARE)));
options->setPluginStringData("SimGear::ROUGH_LOD_RANGE",
fgGetString("/sim/rendering/static-lod/rough-delta",
std::to_string(SG_OBJECT_RANGE_ROUGH)));
options->setPluginStringData("SimGear::ROUGH_LOD_DETAILED",
fgGetString("/sim/rendering/static-lod/detailed",
std::to_string(SG_OBJECT_RANGE_DETAILED)));
options->setPluginStringData("SimGear::RENDER_BUILDING_MESH", fgGetBool("/sim/rendering/building-mesh", false) ? "true" : "false");
options->setPluginStringData("SimGear::FG_EARTH", "ON");
// tunables
options->setPluginStringData("SimGear::SPT_PAGE_LEVELS", fgGetString("/sim/scenery/lod-levels", "1 3 5 7 9" ));
options->setPluginStringData("SimGear::SPT_RANGE_MULTIPLIER", fgGetString("/sim/scenery/lod-range-mult", "2" ));
options->setPluginStringData("SimGear::SPT_MESH_RESOLUTION", fgGetString("/sim/scenery/lod-res", "1" ));
options->setPluginStringData("SimGear::SPT_LOD_TEXTURING", fgGetString("/sim/scenery/lod-texturing", "bluemarble" ));
options->setMaterialLib(globals->get_matlib());
// a DEM can contain multiple levels from multiple locations
// priority is based on first found...
_dem = new SGDem;
if ( _dem ) {
for (osgDB::FilePathList::const_iterator i = fp.begin(); i != fp.end(); ++i) {
SGPath demPath(*i);
demPath.append("DEM");
int numLevels = _dem->addRoot(demPath);
if ( numLevels ) {
SG_LOG(SG_TERRAIN, SG_INFO, "Terrain init - dem path " << demPath << " has " << numLevels << " LOD Levels " );
} else {
SG_LOG(SG_TERRAIN, SG_INFO, "Terrain init - dem path " << demPath << " has NO LOD Levels " );
}
}
options->setDem(_dem);
SG_LOG(SG_TERRAIN, SG_INFO, "Terrain init - Load w180s90-360x180.pgt" );
osg::ref_ptr<osg::Node> loadedModel = osgDB::readRefNodeFile("w180s90-360x180.pgt", options.get());
if ( loadedModel ) {
terrain_branch->addChild( loadedModel.get() );
// Toggle the setup flag.
_inited = true;
}
}
}
void FGPgtTerrain::reinit()
{
}
void FGPgtTerrain::shutdown()
{
terrain_branch = NULL;
// Toggle the setup flag.
_inited = false;
}
void FGPgtTerrain::update(double dt)
{
// scenery loading check, triggers after each sim (tile manager) reinit
if (!_scenery_loaded->getBoolValue())
{
bool fdmInited = fgGetBool("sim/fdm-initialized");
bool positionFinalized = fgGetBool("sim/position-finalized");
bool sceneryOverride = _scenery_override->getBoolValue();
// we are done if final position is set and the scenery & FDM are done.
// scenery-override can ignore the last two, but not position finalization.
if (positionFinalized && (sceneryOverride || fdmInited))
{
_scenery_loaded->setBoolValue(true);
fgSplashProgress("");
}
else
{
if (!positionFinalized) {
fgSplashProgress("finalize-position");
} else {
fgSplashProgress("loading-scenery");
}
// be nice to loader threads while waiting for initial scenery, reduce to 20fps
SGTimeStamp::sleepForMSec(50);
}
}
}
void FGPgtTerrain::bind()
{
}
void FGPgtTerrain::unbind()
{
}
bool
FGPgtTerrain::get_cart_elevation_m(const SGVec3d& pos, double max_altoff,
double& alt,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom)
{
SGGeod geod = SGGeod::fromCart(pos);
geod.setElevationM(geod.getElevationM() + max_altoff);
return get_elevation_m(geod, alt, material, butNotFrom);
}
static simgear::BVHMaterial def_mat;
bool
FGPgtTerrain::get_elevation_m(const SGGeod& geod, double& alt,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom)
{
alt = 100.0;
if (material) {
*material = &def_mat;
} else {
// SG_LOG(SG_TERRAIN, SG_INFO, "FGStgTerrain::get_elevation_m: alt " << alt << " no material " );
}
return true;
}
bool FGPgtTerrain::get_cart_ground_intersection(const SGVec3d& pos, const SGVec3d& dir,
SGVec3d& nearestHit,
const osg::Node* butNotFrom)
{
return true;
}
bool FGPgtTerrain::scenery_available(const SGGeod& position, double range_m)
{
if( schedule_scenery(position, range_m, 0.0) )
{
return true;
}
else
{
return false;
}
}
bool FGPgtTerrain::schedule_scenery(const SGGeod& position, double range_m, double duration)
{
// sanity check (unfortunately needed!)
if (!position.isValid()) {
SG_LOG(SG_TERRAIN, SG_INFO, "FGSptTerrain::schedule_scenery - position invalid");
return false;
}
return true;
}
void FGPgtTerrain::materialLibChanged()
{
// PSADRO: TODO - passing down new regional textures won't work. these need to be set in the
// lod tree at init time, as OSGDBPager generates the load request, not the tile cache.
// _options->setMaterialLib(globals->get_matlib());
}
#endif

127
src/Scenery/terrain_pgt.hxx Normal file
View File

@@ -0,0 +1,127 @@
// terrain_pgt.hxx -- data structures and routines for managing scenery.
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _TERRAIN_PGT_HXX
#define _TERRAIN_PGT_HXX
#ifndef __cplusplus
# error This library requires C++
#endif
#include <osg/ref_ptr>
#include <osg/Switch>
#include <simgear/compiler.h>
#include <simgear/props/props.hxx>
#include <simgear/math/SGMath.hxx>
#include <simgear/scene/model/particles.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/scene/dem/SGDem.hxx>
#include "terrain.hxx"
//#include "SceneryPager.hxx"
//#include "tilemgr.hxx"
namespace simgear {
class BVHMaterial;
}
// Define a structure containing global scenery parameters
class FGPgtTerrain : public FGTerrain
{
public:
FGPgtTerrain();
~FGPgtTerrain();
// Implementation of SGSubsystem.
void init ( osg::Group* terrain );
void reinit();
void shutdown ();
void bind ();
void unbind ();
void update (double dt);
/// Compute the elevation of the scenery at geodetic latitude lat,
/// geodetic longitude lon and not higher than max_alt.
/// If the exact flag is set to true, the scenery center is moved to
/// gain a higher accuracy of that query. The center is restored past
/// that to the original value.
/// The altitude hit is returned in the alt argument.
/// The method returns true if the scenery is available for the given
/// lat/lon pair. If there is no scenery for that point, the altitude
/// value is undefined.
/// All values are meant to be in meters or degrees.
bool get_elevation_m(const SGGeod& geod, double& alt,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom = 0);
/// Compute the elevation of the scenery below the cartesian point pos.
/// you the returned scenery altitude is not higher than the position
/// pos plus an offset given with max_altoff.
/// If the exact flag is set to true, the scenery center is moved to
/// gain a higher accuracy of that query. The center is restored past
/// that to the original value.
/// The altitude hit is returned in the alt argument.
/// The method returns true if the scenery is available for the given
/// lat/lon pair. If there is no scenery for that point, the altitude
/// value is undefined.
/// All values are meant to be in meters.
bool get_cart_elevation_m(const SGVec3d& pos, double max_altoff,
double& elevation,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom = 0);
/// Compute the nearest intersection point of the line starting from
/// start going in direction dir with the terrain.
/// The input and output values should be in cartesian coordinates in the
/// usual earth centered wgs84 coordinate system. Units are meters.
/// On success, true is returned.
bool get_cart_ground_intersection(const SGVec3d& start, const SGVec3d& dir,
SGVec3d& nearestHit,
const osg::Node* butNotFrom = 0);
/// Returns true if scenery is available for the given lat, lon position
/// within a range of range_m.
/// lat and lon are expected to be in degrees.
bool scenery_available(const SGGeod& position, double range_m);
// tile mgr api
bool schedule_scenery(const SGGeod& position, double range_m, double duration=0.0);
void materialLibChanged();
static const char* staticSubsystemClassId() { return "scenery"; }
private:
// terrain branch of scene graph
osg::ref_ptr<osg::Group> terrain_branch;
SGPropertyNode_ptr _scenery_loaded, _scenery_override;
bool _inited;
SGDemPtr _dem;
};
#endif // _TERRAIN_PGT_HXX

413
src/Scenery/terrain_stg.cxx Normal file
View File

@@ -0,0 +1,413 @@
// scenery.cxx -- data structures and routines for managing scenery.
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <osg/Camera>
#include <osg/Transform>
#include <osg/MatrixTransform>
#include <osg/PositionAttitudeTransform>
#include <osg/CameraView>
#include <osg/LOD>
#include <osgViewer/Viewer>
#include <simgear/constants.h>
#include <simgear/sg_inlines.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/scene/tgdb/userdata.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/util/SGNodeMasks.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/util/SGSceneUserData.hxx>
#include <simgear/scene/model/CheckSceneryVisitor.hxx>
#include <simgear/scene/sky/sky.hxx>
#include <simgear/bvh/BVHNode.hxx>
#include <simgear/bvh/BVHLineSegmentVisitor.hxx>
#include <simgear/structure/commands.hxx>
#include <Viewer/renderer.hxx>
#include <Main/fg_props.hxx>
#include <GUI/MouseCursor.hxx>
#include "terrain_stg.hxx"
using namespace flightgear;
using namespace simgear;
class FGGroundPickCallback : public SGPickCallback {
public:
FGGroundPickCallback() : SGPickCallback(PriorityScenery)
{ }
virtual bool buttonPressed( int button,
const osgGA::GUIEventAdapter&,
const Info& info )
{
// only on left mouse button
if (button != 0)
return false;
SGGeod geod = SGGeod::fromCart(info.wgs84);
SG_LOG( SG_TERRAIN, SG_INFO, "Got ground pick at " << geod );
SGPropertyNode *c = fgGetNode("/sim/input/click", true);
c->setDoubleValue("longitude-deg", geod.getLongitudeDeg());
c->setDoubleValue("latitude-deg", geod.getLatitudeDeg());
c->setDoubleValue("elevation-m", geod.getElevationM());
c->setDoubleValue("elevation-ft", geod.getElevationFt());
fgSetBool("/sim/signals/click", 1);
return true;
}
};
class FGSceneryIntersect : public osg::NodeVisitor {
public:
FGSceneryIntersect(const SGLineSegmentd& lineSegment,
const osg::Node* skipNode) :
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN),
_lineSegment(lineSegment),
_skipNode(skipNode),
_material(0),
_haveHit(false)
{ }
bool getHaveHit() const
{ return _haveHit; }
const SGLineSegmentd& getLineSegment() const
{ return _lineSegment; }
const simgear::BVHMaterial* getMaterial() const
{ return _material; }
virtual void apply(osg::Node& node)
{
if (&node == _skipNode)
return;
if (!testBoundingSphere(node.getBound()))
return;
addBoundingVolume(node);
}
virtual void apply(osg::Group& group)
{
if (&group == _skipNode)
return;
if (!testBoundingSphere(group.getBound()))
return;
traverse(group);
addBoundingVolume(group);
}
virtual void apply(osg::Transform& transform)
{ handleTransform(transform); }
virtual void apply(osg::Camera& camera)
{
if (camera.getRenderOrder() != osg::Camera::NESTED_RENDER)
return;
handleTransform(camera);
}
virtual void apply(osg::CameraView& transform)
{ handleTransform(transform); }
virtual void apply(osg::MatrixTransform& transform)
{ handleTransform(transform); }
virtual void apply(osg::PositionAttitudeTransform& transform)
{ handleTransform(transform); }
private:
void handleTransform(osg::Transform& transform)
{
if (&transform == _skipNode)
return;
// Hmm, may be this needs to be refined somehow ...
if (transform.getReferenceFrame() != osg::Transform::RELATIVE_RF)
return;
if (!testBoundingSphere(transform.getBound()))
return;
osg::Matrix inverseMatrix;
if (!transform.computeWorldToLocalMatrix(inverseMatrix, this))
return;
osg::Matrix matrix;
if (!transform.computeLocalToWorldMatrix(matrix, this))
return;
SGLineSegmentd lineSegment = _lineSegment;
bool haveHit = _haveHit;
const simgear::BVHMaterial* material = _material;
_haveHit = false;
_lineSegment = lineSegment.transform(SGMatrixd(inverseMatrix.ptr()));
addBoundingVolume(transform);
traverse(transform);
if (_haveHit) {
_lineSegment = _lineSegment.transform(SGMatrixd(matrix.ptr()));
} else {
_lineSegment = lineSegment;
_material = material;
_haveHit = haveHit;
}
}
simgear::BVHNode* getNodeBoundingVolume(osg::Node& node)
{
SGSceneUserData* userData = SGSceneUserData::getSceneUserData(&node);
if (!userData)
return 0;
return userData->getBVHNode();
}
void addBoundingVolume(osg::Node& node)
{
simgear::BVHNode* bvNode = getNodeBoundingVolume(node);
if (!bvNode)
return;
// Find ground intersection on the bvh nodes
simgear::BVHLineSegmentVisitor lineSegmentVisitor(_lineSegment,
0/*startTime*/);
bvNode->accept(lineSegmentVisitor);
if (!lineSegmentVisitor.empty()) {
_lineSegment = lineSegmentVisitor.getLineSegment();
_material = lineSegmentVisitor.getMaterial();
_haveHit = true;
}
}
bool testBoundingSphere(const osg::BoundingSphere& bound) const
{
if (!bound.valid())
return false;
SGSphered sphere(toVec3d(toSG(bound._center)), bound._radius);
return intersects(_lineSegment, sphere);
}
SGLineSegmentd _lineSegment;
const osg::Node* _skipNode;
const simgear::BVHMaterial* _material;
bool _haveHit;
};
////////////////////////////////////////////////////////////////////////////
// Terrain Management system
FGStgTerrain::FGStgTerrain() :
_tilemgr()
{
_inited = false;
}
FGStgTerrain::~FGStgTerrain()
{
SG_LOG(SG_TERRAIN, SG_INFO, "FGStgTerrain::dtor");
}
// Initialize the Scenery Management system
void FGStgTerrain::init( osg::Group* terrain ) {
// Already set up.
if (_inited)
return;
SG_LOG(SG_TERRAIN, SG_INFO, "FGStgTerrain::init - init tilemgr");
// remember the scene terrain branch on scenegraph
terrain_branch = terrain;
// initialize the tile manager
_tilemgr.init();
// Toggle the setup flag.
_inited = true;
}
void FGStgTerrain::reinit()
{
SG_LOG(SG_TERRAIN, SG_INFO, "FGStgTerrain::reinit - reinit tilemgr");
_tilemgr.reinit();
}
void FGStgTerrain::shutdown()
{
SG_LOG(SG_TERRAIN, SG_INFO, "FGStgTerrain::shutdown - shutdown tilemgr");
_tilemgr.shutdown();
terrain_branch = NULL;
// Toggle the setup flag.
_inited = false;
}
void FGStgTerrain::update(double dt)
{
_tilemgr.update(dt);
}
void FGStgTerrain::bind()
{
SG_LOG(SG_TERRAIN, SG_INFO, "FGStgTerrain::bind - noop");
}
void FGStgTerrain::unbind()
{
SG_LOG(SG_TERRAIN, SG_INFO, "FGStgTerrain::unbind - noop");
}
bool
FGStgTerrain::get_cart_elevation_m(const SGVec3d& pos, double max_altoff,
double& alt,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom)
{
bool ok;
SGGeod geod = SGGeod::fromCart(pos);
if (!geod.isValid())
return false;
geod.setElevationM(geod.getElevationM() + max_altoff);
ok = get_elevation_m(geod, alt, material, butNotFrom);
return ok;
}
bool
FGStgTerrain::get_elevation_m(const SGGeod& geod, double& alt,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom)
{
if (!geod.isValid())
return false;
SGVec3d start = SGVec3d::fromGeod(geod);
SGGeod geodEnd = geod;
geodEnd.setElevationM(SGMiscd::min(geod.getElevationM() - 10, -10000));
SGVec3d end = SGVec3d::fromGeod(geodEnd);
FGSceneryIntersect intersectVisitor(SGLineSegmentd(start, end), butNotFrom);
intersectVisitor.setTraversalMask(SG_NODEMASK_TERRAIN_BIT);
terrain_branch->accept(intersectVisitor);
if (!intersectVisitor.getHaveHit())
return false;
geodEnd = SGGeod::fromCart(intersectVisitor.getLineSegment().getEnd());
alt = geodEnd.getElevationM();
if (material) {
*material = intersectVisitor.getMaterial();
}
return true;
}
bool
FGStgTerrain::get_cart_ground_intersection(const SGVec3d& pos, const SGVec3d& dir,
SGVec3d& nearestHit,
const osg::Node* butNotFrom)
{
// We assume that starting positions in the center of the earth are invalid
if ( norm1(pos) < 1 )
return false;
// Make really sure the direction is normalized, is really cheap compared to
// computation of ground intersection.
SGVec3d start = pos;
SGVec3d end = start + 1e5*normalize(dir); // FIXME visibility ???
FGSceneryIntersect intersectVisitor(SGLineSegmentd(start, end), butNotFrom);
intersectVisitor.setTraversalMask(SG_NODEMASK_TERRAIN_BIT);
terrain_branch->accept(intersectVisitor);
if (!intersectVisitor.getHaveHit())
return false;
nearestHit = intersectVisitor.getLineSegment().getEnd();
return true;
}
bool FGStgTerrain::scenery_available(const SGGeod& position, double range_m)
{
if( schedule_scenery(position, range_m, 0.0) )
{
double elev = 0.0;
bool use_vpb = globals->get_props()->getNode("scenery/use-vpb")->getBoolValue();
bool got_elev = get_elevation_m(SGGeod::fromGeodM(position, SG_MAX_ELEVATION_M), elev, 0, 0);
if (!use_vpb && !got_elev)
{
SG_LOG(SG_TERRAIN, SG_DEBUG, "FGStgTerrain::scenery_available - false" );
return false;
}
SGVec3f p = SGVec3f::fromGeod(SGGeod::fromGeodM(position, elev));
osg::FrameStamp* framestamp
= globals->get_renderer()->getFrameStamp();
FGScenery* pSceneryManager = globals->get_scenery();
simgear::CheckSceneryVisitor csnv(pSceneryManager->getPager(), toOsg(p), range_m, framestamp);
// currently the PagedLODs will not be loaded by the DatabasePager
// while the splashscreen is there, so CheckSceneryVisitor force-loads
// missing objects in the main thread
terrain_branch->accept(csnv);
if(!csnv.isLoaded()) {
SG_LOG(SG_TERRAIN, SG_DEBUG, "FGScenery::scenery_available: waiting on CheckSceneryVisitor");
return false;
}
SG_LOG(SG_TERRAIN, SG_DEBUG, "FGStgTerrain::scenery_available - true" );
return true;
} else {
SG_LOG(SG_TERRAIN, SG_DEBUG, "FGScenery::scenery_available: waiting on tile manager");
}
SG_LOG(SG_TERRAIN, SG_DEBUG, "FGStgTerrain::scenery_available - false" );
return false;
}
bool FGStgTerrain::schedule_scenery(const SGGeod& position, double range_m, double duration)
{
SG_LOG(SG_TERRAIN, SG_BULK, "FGStgTerrain::schedule_scenery");
return _tilemgr.schedule_scenery( position, range_m, duration );
}
void FGStgTerrain::materialLibChanged()
{
_tilemgr.materialLibChanged();
}

126
src/Scenery/terrain_stg.hxx Normal file
View File

@@ -0,0 +1,126 @@
// scenery.hxx -- data structures and routines for managing scenery.
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _TERRAIN_STG_HXX
#define _TERRAIN_STG_HXX
#ifndef __cplusplus
# error This library requires C++
#endif
#include <osg/ref_ptr>
#include <osg/Switch>
#include <simgear/compiler.h>
#include <simgear/math/SGMath.hxx>
#include <simgear/scene/model/particles.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include "terrain.hxx"
#include "SceneryPager.hxx"
#include "tilemgr.hxx"
namespace simgear {
class BVHMaterial;
}
// Define a structure containing global scenery parameters
class FGStgTerrain : public FGTerrain
{
public:
FGStgTerrain();
virtual ~FGStgTerrain();
// Implementation of SGSubsystem.
void init ( osg::Group* terrain );
void reinit();
void shutdown ();
void bind ();
void unbind ();
void update (double dt);
/// Compute the elevation of the scenery at geodetic latitude lat,
/// geodetic longitude lon and not higher than max_alt.
/// If the exact flag is set to true, the scenery center is moved to
/// gain a higher accuracy of that query. The center is restored past
/// that to the original value.
/// The altitude hit is returned in the alt argument.
/// The method returns true if the scenery is available for the given
/// lat/lon pair. If there is no scenery for that point, the altitude
/// value is undefined.
/// All values are meant to be in meters or degrees.
bool get_elevation_m(const SGGeod& geod, double& alt,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom = 0);
/// Compute the elevation of the scenery below the cartesian point pos.
/// you the returned scenery altitude is not higher than the position
/// pos plus an offset given with max_altoff.
/// If the exact flag is set to true, the scenery center is moved to
/// gain a higher accuracy of that query. The center is restored past
/// that to the original value.
/// The altitude hit is returned in the alt argument.
/// The method returns true if the scenery is available for the given
/// lat/lon pair. If there is no scenery for that point, the altitude
/// value is undefined.
/// All values are meant to be in meters.
bool get_cart_elevation_m(const SGVec3d& pos, double max_altoff,
double& elevation,
const simgear::BVHMaterial** material,
const osg::Node* butNotFrom = 0);
/// Compute the nearest intersection point of the line starting from
/// start going in direction dir with the terrain.
/// The input and output values should be in cartesian coordinates in the
/// usual earth centered wgs84 coordinate system. Units are meters.
/// On success, true is returned.
bool get_cart_ground_intersection(const SGVec3d& start, const SGVec3d& dir,
SGVec3d& nearestHit,
const osg::Node* butNotFrom = 0);
/// Returns true if scenery is available for the given lat, lon position
/// within a range of range_m.
/// lat and lon are expected to be in degrees.
bool scenery_available(const SGGeod& position, double range_m);
// tile mgr api
bool schedule_scenery(const SGGeod& position, double range_m, double duration=0.0);
void materialLibChanged();
static const char* staticSubsystemClassId() { return "scenery"; }
private:
// tile manager
FGTileMgr _tilemgr;
// terrain branch of scene graph
osg::ref_ptr<osg::Group> terrain_branch;
bool _inited;
};
#endif // _TERRAIN_STG_HXX

274
src/Scenery/tilecache.cxx Normal file
View File

@@ -0,0 +1,274 @@
// TileCache.cxx -- routines to handle scenery tile caching
//
// Written by Curtis Olson, started December 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/bucket/newbucket.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include "tileentry.hxx"
#include "tilecache.hxx"
TileCache::TileCache( void ) :
max_cache_size(100), current_time(0.0)
{
tile_cache.clear();
}
TileCache::~TileCache( void )
{
tile_map_iterator it = tile_cache.begin();
for (; it != tile_cache.end(); ++it) {
TileEntry* tile = it->second;
tile->removeFromSceneGraph();
delete tile;
}
}
// Free a tile cache entry
void TileCache::entry_free( long tile_index ) {
SG_LOG( SG_TERRAIN, SG_DEBUG, "FREEING CACHE ENTRY = " << tile_index );
TileEntry *tile = tile_cache[tile_index];
tile->removeFromSceneGraph();
tile_cache.erase( tile_index );
delete tile;
}
// Initialize the tile cache subsystem
void TileCache::init( void ) {
SG_LOG( SG_TERRAIN, SG_INFO, "Initializing the tile cache." );
SG_LOG( SG_TERRAIN, SG_INFO, " max cache size = "
<< max_cache_size );
SG_LOG( SG_TERRAIN, SG_INFO, " current cache size = "
<< tile_cache.size() );
clear_cache();
SG_LOG( SG_TERRAIN, SG_INFO, " done with init()" );
}
// Search for the specified "bucket" in the cache
bool TileCache::exists_stg( const SGBucket& b ) const {
long tile_index = b.gen_index();
const_tile_map_iterator it = tile_cache.find( tile_index );
return ( it != tile_cache.end() );
}
bool TileCache::exists_vpb( const SGBucket& b ) const {
// VPB tiles are stored with negative index to avoid clash with STG index
long tile_index = - b.gen_vpb_index();
const_tile_map_iterator it = tile_cache.find( tile_index );
return ( it != tile_cache.end() );
}
// Return the index of a tile to be dropped from the cache, return -1 if
// nothing available to be removed.
long TileCache::get_drop_tile() {
long min_index = -1;
double min_time = DBL_MAX;
float priority = FLT_MAX;
tile_map_iterator current = tile_cache.begin();
tile_map_iterator end = tile_cache.end();
for ( ; current != end; ++current ) {
long index = current->first;
TileEntry *e = current->second;
if (( !e->is_current_view() )&&
( e->is_expired(current_time) ))
{
if (e->is_expired(current_time - 1.0)&&
!e->is_loaded())
{
/* Immediately drop "empty" tiles which are no longer used/requested, and were last requested > 1 second ago...
* Allow a 1 second timeout since an empty tiles may just be loaded...
*/
SG_LOG( SG_TERRAIN, SG_DEBUG, " dropping an unused and empty tile");
min_index = index;
break;
}
if (( e->get_time_expired() < min_time )||
(( e->get_time_expired() == min_time)&&
( priority > e->get_priority())))
{
// drop oldest tile with lowest priority
min_time = e->get_time_expired();
priority = e->get_priority();
min_index = index;
}
}
}
SG_LOG( SG_TERRAIN, SG_DEBUG, " index = " << min_index );
SG_LOG( SG_TERRAIN, SG_DEBUG, " min_time = " << min_time );
return min_index;
}
long TileCache::get_first_expired_tile() const
{
const_tile_map_iterator current = tile_cache.begin();
const_tile_map_iterator end = tile_cache.end();
for ( ; current != end; ++current ) {
TileEntry *e = current->second;
if (!e->is_current_view() && e->is_expired(current_time))
{
return current->first;
}
}
return -1; // no expired tile found
}
// Clear all flags indicating tiles belonging to the current view
void TileCache::clear_current_view()
{
tile_map_iterator current = tile_cache.begin();
tile_map_iterator end = tile_cache.end();
for ( ; current != end; ++current ) {
TileEntry *e = current->second;
if (e->is_current_view())
{
// update expiry time for tiles belonging to most recent position
e->update_time_expired( current_time );
e->set_current_view( false );
}
}
}
// Clear a cache entry, note that the cache only holds pointers
// and this does not free the object which is pointed to.
void TileCache::clear_entry( long tile_index ) {
tile_cache.erase( tile_index );
}
// Clear all completely loaded tiles (ignores partially loaded tiles)
void TileCache::clear_cache() {
std::vector<long> indexList;
tile_map_iterator current = tile_cache.begin();
tile_map_iterator end = tile_cache.end();
for ( ; current != end; ++current ) {
long index = current->first;
TileEntry *e = current->second;
if ( e->is_loaded() ) {
e->tile_bucket.make_bad();
// entry_free modifies tile_cache, so store index and call entry_free() later;
indexList.push_back( index);
}
}
for (unsigned int it = 0; it < indexList.size(); it++) {
entry_free( indexList[ it]);
}
}
/**
* Create a new tile and schedule it for loading.
*/
bool TileCache::insert_tile( STGTileEntry *e ) {
// register tile in the cache
long tile_index = e->get_tile_bucket().gen_index();
tile_cache[tile_index] = e;
e->update_time_expired(current_time);
return true;
}
/**
* Create a new tile and schedule it for loading. VPB version, with negative index.
*/
bool TileCache::insert_tile( VPBTileEntry *e ) {
// register tile in the cache
long tile_index = - e->get_tile_bucket().gen_vpb_index();
tile_cache[tile_index] = e;
e->update_time_expired(current_time);
return true;
}
// update tile's priority and expiry time according to current request
void TileCache::request_tile(TileEntry* t,float priority,bool current_view,double request_time)
{
if ((!current_view)&&(request_time<=0.0))
return;
// update priority when higher - or old request has expired
if ((t->is_expired(current_time))||
(priority > t->get_priority()))
{
t->set_priority( priority );
}
if (current_view)
{
t->update_time_expired( current_time + request_time );
t->set_current_view( true );
}
else
{
t->update_time_expired( current_time+request_time );
}
}
// Return a pointer to the specified tile cache entry
STGTileEntry* TileCache::get_stg_tile( const SGBucket& b ) const {
const_tile_map_iterator it = std::find_if(tile_cache.begin(), tile_cache.end(),
[b](auto &t) {
return ((b.gen_index() == t.first) && (t.second->getExtension() == TileEntry::Extension::STG));
});
if ( it != tile_cache.end() ) {
return dynamic_cast<STGTileEntry*>(it->second);
} else {
return NULL;
}
}
// Return a pointer to the specified tile cache entry
VPBTileEntry* TileCache::get_vpb_tile( const SGBucket& b ) const {
const_tile_map_iterator it = std::find_if(tile_cache.begin(), tile_cache.end(),
[b](auto &t) {
// Negative indices are used for the VPB tiles.
return (( - b.gen_vpb_index() == t.first) && (t.second->getExtension() == TileEntry::Extension::VPB));
});
if ( it != tile_cache.end() ) {
return dynamic_cast<VPBTileEntry*>(it->second);
} else {
return NULL;
}
}

129
src/Scenery/tilecache.hxx Normal file
View File

@@ -0,0 +1,129 @@
// TileCache.hxx -- routines to handle scenery tile caching
//
// Written by Curtis Olson, started December 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#pragma once
#include <map>
#include <simgear/bucket/newbucket.hxx>
#include "tileentry.hxx"
// A class to store and manage a pile of tiles
class TileCache {
public:
typedef std::map < long, TileEntry * > tile_map;
typedef tile_map::iterator tile_map_iterator;
typedef tile_map::const_iterator const_tile_map_iterator;
private:
// cache storage space
tile_map tile_cache;
// maximum cache size
int max_cache_size;
// pointers to allow an external linear traversal of cache entries
tile_map_iterator current;
double current_time;
// Free a tile cache entry
void entry_free( long cache_index );
public:
tile_map_iterator begin() { return tile_cache.begin(); }
tile_map_iterator end() { return tile_cache.end(); }
const_tile_map_iterator begin() const { return tile_cache.begin(); }
const_tile_map_iterator end() const { return tile_cache.end(); }
// Constructor
TileCache();
// Destructor
~TileCache();
// Initialize the tile cache subsystem
void init( void );
// Check if the specified "bucket" exists in the cache
bool exists_stg( const SGBucket& b ) const;
bool exists_vpb( const SGBucket& b ) const;
// Return the index of a tile to be dropped from the cache, return -1 if
// nothing available to be removed.
long get_drop_tile();
long get_first_expired_tile() const;
// Clear all flags indicating tiles belonging to the current view
void clear_current_view();
// Clear a cache entry, note that the cache only holds pointers
// and this does not free the object which is pointed to.
void clear_entry( long cache_entry );
// Clear all completely loaded tiles (ignores partially loaded tiles)
void clear_cache();
// Return a pointer to the specified tile cache entry
inline TileEntry *get_tile( const long tile_index ) const {
const_tile_map_iterator it = tile_cache.find( tile_index );
if ( it != tile_cache.end() ) {
return it->second;
} else {
return NULL;
}
}
STGTileEntry* get_stg_tile( const SGBucket& b ) const;
VPBTileEntry* get_vpb_tile( const SGBucket& b ) const;
// Return the cache size
inline size_t get_size() const { return tile_cache.size(); }
// External linear traversal of cache
inline void reset_traversal() { current = tile_cache.begin(); }
inline bool at_end() { return current == tile_cache.end(); }
inline TileEntry *get_current() const {
// cout << "index = " << current->first << endl;
return current->second;
}
inline void next() { ++current; }
inline int get_max_cache_size() const { return max_cache_size; }
inline void set_max_cache_size( int m ) { max_cache_size = m; }
/**
* Create a new tile and enqueue it for loading.
* @param b
* @return success/failure
*/
bool insert_tile( STGTileEntry* e );
bool insert_tile( VPBTileEntry* e );
void set_current_time(double val) { current_time = val; }
double get_current_time() const { return current_time; }
// update tile's priority and expiry time according to current request
void request_tile(TileEntry* t,float priority,bool current_view,double requesttime);
};

158
src/Scenery/tileentry.cxx Normal file
View File

@@ -0,0 +1,158 @@
// tileentry.cxx -- routines to handle a scenery tile
//
// Written by Curtis Olson, started May 1998.
//
// Copyright (C) 1998 - 2001 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/compiler.h>
#include <string>
#include <sstream>
#include <istream>
#include <osg/LOD>
#include <simgear/bucket/newbucket.hxx>
#include <simgear/debug/logstream.hxx>
#include "tileentry.hxx"
using std::string;
// Base constructor
TileEntry::TileEntry ( const SGBucket& b )
: tile_bucket( b ),
_node( new osg::LOD ),
_priority(-FLT_MAX),
_current_view(false),
_time_expired(-1.0)
{
_create_orthophoto();
// Give a default LOD range so that traversals that traverse
// active children (like the groundcache lookup) will work before
// tile manager has had a chance to update this node.
_node->setRange(0, 0.0, 10000.0);
}
TileEntry::TileEntry( const TileEntry& t )
: tile_bucket( t.tile_bucket ),
tileFileName(t.tileFileName),
_node( new osg::LOD ),
_priority(t._priority),
_current_view(t._current_view),
_time_expired(t._time_expired)
{
_create_orthophoto();
_node->setName(tileFileName);
// Give a default LOD range so that traversals that traverse
// active children (like the groundcache lookup) will work before
// tile manager has had a chance to update this node.
_node->setRange(0, 0.0, 10000.0);
}
void TileEntry::_create_orthophoto() {
bool use_photoscenery = fgGetBool("/sim/rendering/photoscenery/enabled");
if (use_photoscenery) {
_orthophoto = simgear::Orthophoto::fromBucket(tile_bucket, globals->get_fg_scenery());
if (_orthophoto) {
simgear::OrthophotoManager::instance()->registerOrthophoto(tile_bucket.gen_index(), _orthophoto);
}
}
}
// Destructor
TileEntry::~TileEntry ()
{
}
// Update the ssg transform node for this tile so it can be
// properly drawn relative to our (0,0,0) point
void TileEntry::prep_ssg_node(float vis) {
if (!is_loaded())
return;
// visibility can change from frame to frame so we update the
// range selector cutoff's each time.
float bounding_radius = _node->getChild(0)->getBound().radius();
_node->setRange( 0, 0, vis + bounding_radius );
}
void
TileEntry::addToSceneGraph(osg::Group *terrain_branch)
{
terrain_branch->addChild( _node.get() );
SG_LOG( SG_TERRAIN, SG_DEBUG,
"connected a tile into scene graph. _node = "
<< _node.get() );
SG_LOG( SG_TERRAIN, SG_DEBUG, "num parents now = "
<< _node->getNumParents() );
}
void
TileEntry::removeFromSceneGraph()
{
if (! is_loaded()) {
SG_LOG( SG_TERRAIN, SG_DEBUG, "removing a not-fully loaded tile!" );
} else {
SG_LOG( SG_TERRAIN, SG_DEBUG, "removing a fully loaded tile! _node = " << _node.get() );
}
// find the nodes branch parent
if ( _node->getNumParents() > 0 ) {
// find the first parent (should only be one)
osg::Group *parent = _node->getParent( 0 ) ;
if( parent ) {
parent->removeChild( _node.get() );
}
}
}
// Constructor - STG Variant
STGTileEntry::STGTileEntry ( const SGBucket& b ) : TileEntry(b)
{
tileFileName = b.gen_index_str() + ".stg";
_node->setName(tileFileName);
}
// Destructor - STG Variant
STGTileEntry::~STGTileEntry ()
{
}
// Constructur - VPB version
VPBTileEntry::VPBTileEntry ( const SGBucket& b ) : TileEntry(b)
{
tileFileName = "vpb/" + b.gen_vpb_base() + ".osgb";
_node->setName(tileFileName);
// Give a default LOD range so that traversals that traverse
// active children (like the groundcache lookup) will work before
// tile manager has had a chance to update this node.
_node->setRange(0, 0.0, 160000.0);
}
// Destructor - VPB Variant
VPBTileEntry::~VPBTileEntry ()
{
}

175
src/Scenery/tileentry.hxx Normal file
View File

@@ -0,0 +1,175 @@
// tileentry.hxx -- routines to handle an individual scenery tile
//
// Written by Curtis Olson, started May 1998.
//
// Copyright (C) 1998 - 2001 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _TILEENTRY_HXX
#define _TILEENTRY_HXX
#ifndef __cplusplus
# error This library requires C++
#endif
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <simgear/compiler.h>
#include <vector>
#include <string>
#include <simgear/bucket/newbucket.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/scene/util/OrthophotoManager.hxx>
#include <osg/ref_ptr>
#include <osgDB/ReaderWriter>
#include <osg/Group>
#include <osg/LOD>
/**
* A class to encapsulate everything we need to know about a scenery tile.
*/
class TileEntry {
public:
// this tile's official location in the world
SGBucket tile_bucket;
std::string tileFileName;
protected:
// pointer to ssg range selector for this tile
osg::ref_ptr<osg::LOD> _node;
private:
// Reference to DatabaseRequest object set and used by the
// osgDB::DatabasePager.
osg::ref_ptr<osg::Referenced> _databaseRequest;
// Overlay image/orthophoto for this tile
simgear::OrthophotoRef _orthophoto;
/**
* This value is used by the tile scheduler/loader to load tiles
* in a useful sequence. The priority is set to reflect the tiles
* distance from the center, so all tiles are loaded in an innermost
* to outermost sequence.
*/
float _priority;
/** Flag indicating if tile belongs to current view. */
bool _current_view;
/** Time when tile expires. */
double _time_expired;
void _create_orthophoto();
public:
// Constructor.
TileEntry( const SGBucket& b );
TileEntry( const TileEntry& t );
// Destructor
virtual ~TileEntry() = 0;
// Update the ssg transform node for this tile so it can be
// properly drawn relative to our (0,0,0) point
void prep_ssg_node(float vis);
/**
* Transition to OSG database pager
*/
static osg::Node* loadTileByFileName(const std::string& index_str,
const osgDB::Options*);
/**
* Return true if the tile entry is loaded, otherwise return false
* indicating that the loading thread is still working on this.
*/
inline bool is_loaded() const
{
return _node->getNumChildren() > 0;
}
/**
* Return the "bucket" for this tile
*/
inline const SGBucket& get_tile_bucket() const { return tile_bucket; }
/**
* Add terrain mesh and ground lighting to scene graph.
*/
void addToSceneGraph( osg::Group *terrain_branch);
/**
* disconnect terrain mesh and ground lighting nodes from scene
* graph for this tile.
*/
void removeFromSceneGraph();
/**
* return the scenegraph node for the terrain
*/
osg::LOD *getNode() const { return _node.get(); }
inline double get_time_expired() const { return _time_expired; }
inline void update_time_expired( double time_expired ) { if (_time_expired<time_expired) _time_expired = time_expired; }
inline void set_priority(float priority) { _priority=priority; }
inline float get_priority() const { return _priority; }
inline void set_current_view(bool current_view) { _current_view = current_view; }
inline bool is_current_view() const { return _current_view; }
/**
* Return false if the tile entry is still needed, otherwise return true
* indicating that the tile is no longer in active use.
*/
inline bool is_expired(double current_time) const { return (_current_view) ? false : (current_time > _time_expired); }
// Get the ref_ptr to the DatabaseRequest object, in order to pass
// this to the pager.
osg::ref_ptr<osg::Referenced>& getDatabaseRequest()
{
return _databaseRequest;
}
enum Extension {
STG, VPB
};
virtual TileEntry::Extension getExtension() = 0;
};
class STGTileEntry : public TileEntry {
public:
STGTileEntry ( const SGBucket& b );
~STGTileEntry();
inline TileEntry::Extension getExtension() { return TileEntry::Extension::STG; };
};
class VPBTileEntry : public TileEntry {
public:
VPBTileEntry ( const SGBucket& b );
~VPBTileEntry();
inline TileEntry::Extension getExtension() { return TileEntry::Extension::VPB; };
};
#endif // _TILEENTRY_HXX

674
src/Scenery/tilemgr.cxx Normal file
View File

@@ -0,0 +1,674 @@
// tilemgr.cxx -- routines to handle dynamic management of scenery tiles
//
// Written by Curtis Olson, started January 1998.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <algorithm>
#include <functional>
#include <osgViewer/Viewer>
#include <osgDB/Registry>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/scene/model/modellib.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/scene/tsync/terrasync.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <Main/globals.hxx>
#include <Main/fg_props.hxx>
#include <Viewer/renderer.hxx>
#include <Viewer/splash.hxx>
#include <Scripting/NasalSys.hxx>
#include <Scripting/NasalModelData.hxx>
#include "scenery.hxx"
#include "SceneryPager.hxx"
#include "tilemgr.hxx"
using flightgear::SceneryPager;
class FGTileMgr::TileManagerListener : public SGPropertyChangeListener
{
public:
TileManagerListener(FGTileMgr* manager) :
_manager(manager),
_useVBOsProp(fgGetNode("/sim/rendering/use-vbos", true)),
_enableCacheProp(fgGetNode("/sim/tile-cache/enable", true)),
_pagedLODMaximumProp(fgGetNode("/sim/rendering/max-paged-lod", true)),
_lodDetailed(fgGetNode("/sim/rendering/static-lod/detailed", true)),
_lodRoughDelta(fgGetNode("/sim/rendering/static-lod/rough-delta", true)),
_lodBareDelta(fgGetNode("/sim/rendering/static-lod/bare-delta", true)),
_lodRough(fgGetNode("/sim/rendering/static-lod/rough", true)),
_lodBare(fgGetNode("/sim/rendering/static-lod/bare", true))
{
_useVBOsProp->addChangeListener(this, true);
_enableCacheProp->addChangeListener(this, true);
if (_enableCacheProp->getType() == simgear::props::NONE) {
_enableCacheProp->setBoolValue(true);
}
if (_pagedLODMaximumProp->getType() == simgear::props::NONE) {
// not set, use OSG default / environment value variable
osg::ref_ptr<osgViewer::View> view(globals->get_renderer()->getView());
int current = view->getDatabasePager()->getTargetMaximumNumberOfPageLOD();
_pagedLODMaximumProp->setIntValue(current);
}
_pagedLODMaximumProp->addChangeListener(this, true);
_lodDetailed->addChangeListener(this, true);
_lodBareDelta->addChangeListener(this, true);
_lodRoughDelta->addChangeListener(this, true);
}
~TileManagerListener()
{
_useVBOsProp->removeChangeListener(this);
_enableCacheProp->removeChangeListener(this);
_pagedLODMaximumProp->removeChangeListener(this);
_lodDetailed->removeChangeListener(this);
_lodBareDelta->removeChangeListener(this);
_lodRoughDelta->removeChangeListener(this);
}
virtual void valueChanged(SGPropertyNode* prop)
{
if (prop == _useVBOsProp) {
bool useVBOs = prop->getBoolValue();
_manager->_options->setPluginStringData("SimGear::USE_VBOS",
useVBOs ? "ON" : "OFF");
} else if (prop == _enableCacheProp) {
_manager->_enableCache = prop->getBoolValue();
} else if (prop == _pagedLODMaximumProp) {
int v = prop->getIntValue();
osg::ref_ptr<osgViewer::View> view(globals->get_renderer()->getView());
if (view) {
osgDB::DatabasePager* pager = view->getDatabasePager();
if (pager) pager->setTargetMaximumNumberOfPageLOD(v);
}
} else if (prop == _lodDetailed || prop == _lodBareDelta || prop == _lodRoughDelta) {
// compatibility with earlier versions; set the static lod ranges appropriately as otherwise (bad) self managed
// LOD on scenery with range animations doesn't work.
// see also /sim/rendering/enable-range-lod-animations - which is false by default in > 2019.2 which also fixes
// the scenery but in a more efficient way.
_lodRough->setDoubleValue(_lodDetailed->getDoubleValue() + _lodRoughDelta->getDoubleValue());
_lodBare->setDoubleValue(_lodRough->getDoubleValue() + _lodBareDelta->getDoubleValue());
}
}
private:
FGTileMgr* _manager;
SGPropertyNode_ptr _useVBOsProp,
_enableCacheProp,
_pagedLODMaximumProp,
_lodDetailed,
_lodRoughDelta,
_lodBareDelta,
_lodRough,
_lodBare
;
};
FGTileMgr::FGTileMgr():
state( Start ),
last_state( Running ),
scheduled_visibility(100.0),
_visibilityMeters(fgGetNode("/environment/visibility-m", true)),
_lodDetailed(fgGetNode("/sim/rendering/static-lod/detailed", true)),
_lodRoughDelta(fgGetNode("/sim/rendering/static-lod/rough-delta", true)),
_lodBareDelta(fgGetNode("/sim/rendering/static-lod/bare-delta", true)),
_disableNasalHooks(fgGetNode("/sim/temp/disable-scenery-nasal", true)),
_scenery_loaded(fgGetNode("/sim/sceneryloaded", true)),
_scenery_override(fgGetNode("/sim/sceneryloaded-override", true)),
_pager(FGScenery::getPagerSingleton()),
_enableCache(true),
_use_vpb(false)
{
}
FGTileMgr::~FGTileMgr()
{
}
// Initialize the Tile Manager subsystem
void FGTileMgr::init()
{
reinit();
}
void FGTileMgr::shutdown()
{
_listener.reset();
FGScenery* scenery = globals->get_scenery();
if (scenery && scenery->get_terrain_branch()) {
osg::Group* group = scenery->get_terrain_branch();
group->removeChildren(0, group->getNumChildren());
}
// clear OSG cache
osgDB::Registry::instance()->clearObjectCache();
state = Start; // need to init again
}
void FGTileMgr::reinit()
{
SG_LOG( SG_TERRAIN, SG_INFO, "Initializing Tile Manager subsystem." );
auto terraSync = globals->get_subsystem<simgear::SGTerraSync>();
// drops the previous options reference
_options = new simgear::SGReaderWriterOptions;
_listener.reset(new TileManagerListener(this));
materialLibChanged();
_options->setPropertyNode(globals->get_props());
osgDB::FilePathList &fp = _options->getDatabasePathList();
const PathList &sc = globals->get_fg_scenery();
fp.clear();
for (auto it = sc.begin(); it != sc.end(); ++it) {
fp.push_back(it->utf8Str());
}
_options->setPluginStringData("SimGear::FG_ROOT", globals->get_fg_root().utf8Str());
if (terraSync) {
_options->setPluginStringData("SimGear::TERRASYNC_ROOT", globals->get_terrasync_dir().utf8Str());
}
if (!_disableNasalHooks->getBoolValue())
_options->setModelData(new FGNasalModelDataProxy);
double detailed = fgGetDouble("/sim/rendering/static-lod/detailed", SG_OBJECT_RANGE_DETAILED);
double rough = fgGetDouble("/sim/rendering/static-lod/rough-delta", SG_OBJECT_RANGE_ROUGH) + detailed;
double bare = fgGetDouble("/sim/rendering/static-lod/bare", SG_OBJECT_RANGE_BARE) + rough;
double tile_min_expiry = fgGetDouble("/sim/rendering/plod-minimum-expiry-time-secs", SG_TILE_MIN_EXPIRY);
_use_vpb = fgGetBool("/scenery/use-vpb");
_options->setPluginStringData("SimGear::LOD_RANGE_BARE", std::to_string(bare));
_options->setPluginStringData("SimGear::LOD_RANGE_ROUGH", std::to_string(rough));
_options->setPluginStringData("SimGear::LOD_RANGE_DETAILED", std::to_string(detailed));
_options->setPluginStringData("SimGear::PAGED_LOD_EXPIRY", std::to_string(tile_min_expiry));
string_list scenerySuffixes;
for (auto node : fgGetNode("/sim/rendering/", true)->getChildren("scenery-path-suffix")) {
if (node->getBoolValue("enabled", true)) {
scenerySuffixes.push_back(node->getStringValue("name"));
}
}
if (scenerySuffixes.empty()) {
// if preferences didn't load, use some default
scenerySuffixes = {"Objects", "Terrain"}; // defaut values
}
if (terraSync) {
terraSync->setSceneryPathSuffixes(scenerySuffixes);
}
_options->setSceneryPathSuffixes(scenerySuffixes);
if (state != Start)
{
// protect against multiple scenery reloads and properly reset flags,
// otherwise aircraft fall through the ground while reloading scenery
if (_scenery_loaded->getBoolValue() == false) {
SG_LOG( SG_TERRAIN, SG_INFO, "/sim/sceneryloaded already false, avoiding duplicate re-init of tile manager" );
return;
}
}
_scenery_loaded->setBoolValue(false);
fgSetDouble("/sim/startup/splash-alpha", 1.0);
materialLibChanged();
// remove all old scenery nodes from scenegraph and clear cache
osg::Group* group = globals->get_scenery()->get_terrain_branch();
group->removeChildren(0, group->getNumChildren());
tile_cache.init();
// clear OSG cache, except on initial start-up
if (state != Start)
{
osgDB::Registry::instance()->clearObjectCache();
}
state = Inited;
previous_bucket.make_bad();
current_bucket.make_bad();
scheduled_visibility = 100.0;
// force an update now
update(0.0);
}
void FGTileMgr::materialLibChanged()
{
_options->setMaterialLib(globals->get_matlib());
}
/* schedule a tile for loading, keep request for given amount of time.
* Returns true if tile is already loaded. */
bool FGTileMgr::sched_tile( const SGBucket& b, double priority, bool current_view, double duration)
{
// see if tile already exists in the cache
STGTileEntry *t = tile_cache.get_stg_tile( b );
if (!t)
{
// create a new entry
t = new STGTileEntry( b );
SG_LOG( SG_TERRAIN, SG_INFO, "sched_tile: new STG tile entry for:" << b );
// insert the tile into the cache, update will generate load request
if ( tile_cache.insert_tile( t ) )
{
// Attach to scene graph
t->addToSceneGraph(globals->get_scenery()->get_terrain_branch());
} else
{
// insert failed (cache full with no available entries to
// delete.) Try again later
delete t;
return false;
}
SG_LOG( SG_TERRAIN, SG_DEBUG, " New tile cache size " << (int)tile_cache.get_size() );
}
// update tile's properties
tile_cache.request_tile(t,priority,current_view,duration);
if (_use_vpb) {
VPBTileEntry *v = tile_cache.get_vpb_tile( b );
if (!v)
{
// create a new entry
v = new VPBTileEntry( b );
SG_LOG( SG_TERRAIN, SG_INFO, "sched_tile: new VPB tile entry for:" << b );
// insert the tile into the cache, update will generate load request
if ( tile_cache.insert_tile( v ) )
{
// Attach to scene graph
v->addToSceneGraph(globals->get_scenery()->get_terrain_branch());
} else {
// insert failed (cache full with no available entries to
// delete.) Try again later
delete v;
return false;
}
SG_LOG( SG_TERRAIN, SG_DEBUG, " New tile cache size " << (int)tile_cache.get_size() );
}
// update tile's properties. We ensure VPB tiles have maximum priority - priority is calcualated as
// _negative_ the square of the distance from the viewer to the tile.
// so by multiplying by 0.1 we increase the number towards 0.
tile_cache.request_tile(v,priority * 0.1,current_view,duration);
}
return t->is_loaded();
}
/* schedule needed buckets for the current view position for loading,
* keep request for given amount of time */
void FGTileMgr::schedule_needed(const SGBucket& curr_bucket, double vis)
{
// sanity check (unfortunately needed!)
if (!curr_bucket.isValid() )
{
SG_LOG( SG_TERRAIN, SG_ALERT,
"Attempting to schedule tiles for invalid bucket" );
return;
}
double tile_width = curr_bucket.get_width_m();
double tile_height = curr_bucket.get_height_m();
SG_LOG( SG_TERRAIN, SG_INFO,
"scheduling needed tiles for " << curr_bucket
<< ", tile-width-m:" << tile_width << ", tile-height-m:" << tile_height);
// cout << "tile width = " << tile_width << " tile_height = "
// << tile_height << endl;
// starting with 2018.3 we will use deltas rather than absolutes as it is more intuitive for the user
// and somewhat easier to visualise
double maxTileRange = _lodDetailed->getDoubleValue() + _lodRoughDelta->getDoubleValue() + _lodBareDelta->getDoubleValue();
double tileRangeM = std::min(vis, maxTileRange);
int xrange = (int)(tileRangeM / tile_width) + 1;
int yrange = (int)(tileRangeM / tile_height) + 1;
if ( xrange < 1 ) { xrange = 1; }
if ( yrange < 1 ) { yrange = 1; }
// make the cache twice as large to avoid losing terrain when switching
// between aircraft and tower views
tile_cache.set_max_cache_size( (2*xrange + 2) * (2*yrange + 2) * 2 );
// cout << "xrange = " << xrange << " yrange = " << yrange << endl;
// cout << "max cache size = " << tile_cache.get_max_cache_size()
// << " current cache size = " << tile_cache.get_size() << endl;
// clear flags of all tiles belonging to the previous view set
tile_cache.clear_current_view();
// update timestamps, so all tiles scheduled now are *newer* than any tile previously loaded
osg::FrameStamp* framestamp
= globals->get_renderer()->getFrameStamp();
tile_cache.set_current_time(framestamp->getReferenceTime());
SGBucket b;
int x, y;
auto terraSync = globals->get_subsystem<simgear::SGTerraSync>();
/* schedule all tiles, use distance-based loading priority,
* so tiles are loaded in innermost-to-outermost sequence. */
for ( x = -xrange; x <= xrange; ++x )
{
for ( y = -yrange; y <= yrange; ++y )
{
SGBucket b = curr_bucket.sibling(x, y);
if (!b.isValid()) {
continue;
}
float priority = (-1.0) * (x*x+y*y);
sched_tile( b, priority, true, 0.0 );
if (terraSync) {
terraSync->scheduleTile(b);
}
}
}
}
/**
* Update the various queues maintained by the tilemgr (private
* internal function, do not call directly.)
*/
void FGTileMgr::update_queues(bool& isDownloadingScenery)
{
osg::FrameStamp* framestamp = globals->get_renderer()->getFrameStamp();
double current_time = framestamp->getReferenceTime();
double vis = _visibilityMeters->getDoubleValue();
TileEntry *e;
int loading=0;
int sz=0;
tile_cache.set_current_time( current_time );
tile_cache.reset_traversal();
while ( ! tile_cache.at_end() )
{
e = tile_cache.get_current();
if ( e )
{
// Prepare the ssg nodes corresponding to each tile.
// Set the ssg transform and update it's range selector
// based on current visibilty
e->prep_ssg_node(vis);
if (!e->is_loaded()) {
bool nonExpiredOrCurrent = !e->is_expired(current_time) || e->is_current_view();
bool downloading = isTileDirSyncing(e->tileFileName);
isDownloadingScenery |= downloading;
if ( !downloading && nonExpiredOrCurrent) {
// schedule tile for loading with osg pager
_pager->queueRequest(e->tileFileName,
e->getNode(),
e->get_priority(),
framestamp,
e->getDatabaseRequest(),
_options.get());
loading++;
}
} // of tile not loaded case
} else {
SG_LOG(SG_TERRAIN, SG_ALERT, "Warning: empty tile in cache!");
}
tile_cache.next();
sz++;
}
int drop_count = sz - tile_cache.get_max_cache_size();
bool dropTiles = false;
if (_enableCache) {
dropTiles = ( drop_count > 0 ) && ((loading==0)||(drop_count > 10));
} else {
dropTiles = true;
drop_count = sz; // no limit on tiles to drop
}
if (dropTiles)
{
long drop_index = _enableCache ? tile_cache.get_drop_tile() :
tile_cache.get_first_expired_tile();
while ( drop_index > -1 )
{
// schedule tile for deletion with osg pager
TileEntry* old = tile_cache.get_tile(drop_index);
SG_LOG(SG_TERRAIN, SG_DEBUG, "Dropping:" << old->get_tile_bucket());
tile_cache.clear_entry(drop_index);
osg::ref_ptr<osg::Object> subgraph = old->getNode();
old->removeFromSceneGraph();
delete old;
// zeros out subgraph ref_ptr, so subgraph is owned by
// the pager and will be deleted in the pager thread.
_pager->queueDeleteRequest(subgraph);
if (!_enableCache)
drop_index = tile_cache.get_first_expired_tile();
// limit tiles dropped to drop_count
else if (--drop_count > 0)
drop_index = tile_cache.get_drop_tile();
else
drop_index = -1;
}
} // of dropping tiles loop
}
// given the current lon/lat (in degrees), fill in the array of local
// chunks. If the chunk isn't already in the cache, then read it from
// disk.
void FGTileMgr::update(double)
{
double vis = _visibilityMeters->getDoubleValue();
schedule_tiles_at(globals->get_view_position(), vis);
bool waitingOnTerrasync = false;
update_queues(waitingOnTerrasync);
// scenery loading check, triggers after each sim (tile manager) reinit
if (!_scenery_loaded->getBoolValue())
{
bool fdmInited = fgGetBool("sim/fdm-initialized");
bool positionFinalized = fgGetBool("sim/position-finalized");
bool sceneryOverride = _scenery_override->getBoolValue();
// we are done if final position is set and the scenery & FDM are done.
// scenery-override can ignore the last two, but not position finalization.
if (positionFinalized && (sceneryOverride || (isSceneryLoaded() && fdmInited)))
{
_scenery_loaded->setBoolValue(true);
fgSplashProgress("");
}
else
{
if (!positionFinalized) {
fgSplashProgress("finalize-position");
} else if (waitingOnTerrasync) {
fgSplashProgress("downloading-scenery");
} else {
fgSplashProgress("loading-scenery");
}
// be nice to loader threads while waiting for initial scenery, reduce to 20fps
SGTimeStamp::sleepForMSec(50);
}
}
}
// schedule tiles for the viewer bucket
// (FDM/AI/groundcache/... should use "schedule_scenery" instead)
void FGTileMgr::schedule_tiles_at(const SGGeod& location, double range_m)
{
// SG_LOG( SG_TERRAIN, SG_DEBUG, "FGTileMgr::update() for "
// << longitude << " " << latitude );
current_bucket = SGBucket( location );
// schedule more tiles when visibility increased considerably
// TODO Calculate tile size - instead of using fixed value (5000m)
if (range_m - scheduled_visibility > 5000.0)
previous_bucket.make_bad();
// SG_LOG( SG_TERRAIN, SG_DEBUG, "Updating tile list for "
// << current_bucket );
fgSetInt( "/environment/current-tile-id", current_bucket.gen_index() );
// do tile load scheduling.
// Note that we need keep track of both viewer buckets and fdm buckets.
if ( state == Running ) {
if (last_state != state)
{
SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Running" );
}
if (current_bucket != previous_bucket) {
// We've moved to a new bucket, we need to schedule any
// needed tiles for loading.
SG_LOG( SG_TERRAIN, SG_INFO, "FGTileMgr: at " << location << ", scheduling needed for:" << current_bucket
<< ", visibility=" << range_m);
scheduled_visibility = range_m;
schedule_needed(current_bucket, range_m);
}
// save bucket
previous_bucket = current_bucket;
} else if ( state == Start || state == Inited ) {
SG_LOG( SG_TERRAIN, SG_DEBUG, "State == Start || Inited" );
// do not update bucket yet (position not valid in initial loop)
state = Running;
previous_bucket.make_bad();
}
last_state = state;
}
/** Schedules scenery for given position. Load request remains valid for given duration
* (duration=0.0 => nothing is loaded).
* Used for FDM/AI/groundcache/... requests. Viewer uses "schedule_tiles_at" instead.
* Returns true when all tiles for the given position are already loaded, false otherwise.
*/
bool FGTileMgr::schedule_scenery(const SGGeod& position, double range_m, double duration)
{
// sanity check (unfortunately needed!)
if (!position.isValid())
return false;
const float priority = 0.0;
bool available = true;
SGBucket bucket(position);
available = sched_tile( bucket, priority, false, duration );
if ((!available)&&(duration==0.0)) {
SG_LOG( SG_TERRAIN, SG_DEBUG, "schedule_scenery: Scheduling tile at bucket:" << bucket << " return false" );
return false;
}
SGVec3d cartPos = SGVec3d::fromGeod(position);
// Traverse all tiles required to be there for the given visibility.
double tile_width = bucket.get_width_m();
double tile_height = bucket.get_height_m();
double tile_r = 0.5*sqrt(tile_width*tile_width + tile_height*tile_height);
double max_dist = tile_r + range_m;
double max_dist2 = max_dist*max_dist;
int xrange = (int)fabs(range_m / tile_width) + 1;
int yrange = (int)fabs(range_m / tile_height) + 1;
for ( int x = -xrange; x <= xrange; ++x )
{
for ( int y = -yrange; y <= yrange; ++y )
{
// We have already checked for the center tile.
if ( x != 0 || y != 0 )
{
SGBucket b = bucket.sibling(x, y );
if (!b.isValid()) {
continue;
}
double distance2 = distSqr(cartPos, SGVec3d::fromGeod(b.get_center()));
// Do not ask if it is just the next tile but way out of range.
if (distance2 <= max_dist2)
{
available &= sched_tile( b, priority, false, duration );
if ((!available)&&(duration==0.0))
return false;
}
}
}
}
return available;
}
// Returns true if tiles around current view position have been loaded
bool FGTileMgr::isSceneryLoaded()
{
double range_m = 100.0;
if (scheduled_visibility < range_m)
range_m = scheduled_visibility;
return schedule_scenery(globals->get_view_position(), range_m, 0.0);
}
bool FGTileMgr::isTileDirSyncing(const std::string& tileFileName) const
{
auto terraSync = globals->get_subsystem<simgear::SGTerraSync>();
if (!terraSync) {
return false;
}
// if Models is syncing, also wait for it, since otherwise
// we get load errors
if (terraSync->isDataDirPending("Models")) {
return true;
}
std::string nameWithoutExtension = tileFileName.substr(0, tileFileName.size() - 4);
long int bucketIndex = simgear::strutils::to_int(nameWithoutExtension);
SGBucket bucket(bucketIndex);
return terraSync->isTileDirPending(bucket.gen_base_path());
}

120
src/Scenery/tilemgr.hxx Normal file
View File

@@ -0,0 +1,120 @@
// tilemgr.hxx -- routines to handle dynamic management of scenery tiles
//
// Written by Curtis Olson, started January 1998.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _TILEMGR_HXX
#define _TILEMGR_HXX
#include <simgear/compiler.h>
#include <simgear/bucket/newbucket.hxx>
#include "SceneryPager.hxx"
#include "tilecache.hxx"
namespace osg
{
class Node;
}
namespace simgear
{
class SGReaderWriterOptions;
}
class FGTileMgr {
private:
// Tile loading state
enum load_state {
Start = 0,
Inited = 1,
Running = 2
};
load_state state, last_state;
// schedule a tile for loading, returns true when tile is already loaded
bool sched_tile( const SGBucket& b, double priority,bool current_view, double request_time);
// schedule a needed buckets for loading
void schedule_needed(const SGBucket& curr_bucket, double rangeM);
bool isTileDirSyncing(const std::string& tileFileName) const;
SGBucket previous_bucket;
SGBucket current_bucket;
SGBucket pending;
osg::ref_ptr<simgear::SGReaderWriterOptions> _options;
double scheduled_visibility;
/**
* tile cache
*/
TileCache tile_cache;
class TileManagerListener;
friend class TileManagerListener;
std::unique_ptr<TileManagerListener> _listener;
// update various queues internal queues
void update_queues(bool& isDownloadingScenery);
// schedule tiles for the viewer bucket
void schedule_tiles_at(const SGGeod& location, double rangeM);
SGPropertyNode_ptr _visibilityMeters;
SGPropertyNode_ptr _lodDetailed, _lodRoughDelta, _lodBareDelta, _disableNasalHooks;
SGPropertyNode_ptr _scenery_loaded, _scenery_override;
osg::ref_ptr<flightgear::SceneryPager> _pager;
/// is caching of expired tiles enabled or not?
bool _enableCache;
bool _use_vpb;
public:
FGTileMgr();
~FGTileMgr();
// Initialize the Tile Manager
void init();
void reinit();
void shutdown();
void update(double dt);
const SGBucket& get_current_bucket () const { return current_bucket; }
// Returns true if scenery is available for the given lat, lon position
// within a range of range_m.
// lat and lon are expected to be in degrees.
bool schedule_scenery(const SGGeod& position, double range_m, double duration=0.0);
// Returns true if tiles around current view position have been loaded
bool isSceneryLoaded();
// notify the tile manahger the material library was reloaded,
// so it can pass this through to its options object
void materialLibChanged();
};
#endif // _TILEMGR_HXX