Merge branch 'next' of https://git.code.sf.net/p/flightgear/simgear into next
This commit is contained in:
@@ -6,10 +6,10 @@ using namespace osg;
|
||||
|
||||
|
||||
/**
|
||||
* merge OSG output into our logging system, so it gets recorded to file,
|
||||
* and so we can display a GUI console with renderer issues, especially
|
||||
* shader compilation warnings and errors.
|
||||
*/
|
||||
* merge OSG output into our logging system, so it gets recorded to file,
|
||||
* and so we can display a GUI console with renderer issues, especially
|
||||
* shader compilation warnings and errors.
|
||||
*/
|
||||
class NotifyLogger : public osg::NotifyHandler
|
||||
{
|
||||
public:
|
||||
@@ -22,24 +22,33 @@ public:
|
||||
if (strstr(message, "the final reference count was")) {
|
||||
// as this is going to segfault ignore the translation of severity and always output the message.
|
||||
SG_LOG(SG_GL, SG_ALERT, message);
|
||||
int* trigger_segfault = 0;
|
||||
*trigger_segfault = 0;
|
||||
#ifndef DEBUG
|
||||
throw new std::string(message);
|
||||
//int* trigger_segfault = 0;
|
||||
//*trigger_segfault = 0;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
SG_LOG(SG_GL, translateSeverity(severity), message);
|
||||
char*tmessage = strdup(message);
|
||||
char*lf = strrchr(tmessage, '\n');
|
||||
if (lf)
|
||||
*lf = 0;
|
||||
|
||||
SG_LOG(SG_OSG, translateSeverity(severity), tmessage);
|
||||
free(tmessage);
|
||||
}
|
||||
|
||||
private:
|
||||
sgDebugPriority translateSeverity(osg::NotifySeverity severity) {
|
||||
switch (severity) {
|
||||
case osg::ALWAYS:
|
||||
case osg::FATAL: return SG_ALERT;
|
||||
case osg::WARN: return SG_WARN;
|
||||
case osg::NOTICE:
|
||||
case osg::INFO: return SG_INFO;
|
||||
case osg::DEBUG_FP:
|
||||
case osg::DEBUG_INFO: return SG_DEBUG;
|
||||
default: return SG_ALERT;
|
||||
case osg::ALWAYS:
|
||||
case osg::FATAL: return SG_ALERT;
|
||||
case osg::WARN: return SG_WARN;
|
||||
case osg::NOTICE:
|
||||
case osg::INFO: return SG_INFO;
|
||||
case osg::DEBUG_FP:
|
||||
case osg::DEBUG_INFO: return SG_DEBUG;
|
||||
default: return SG_ALERT;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,7 +35,10 @@ typedef enum {
|
||||
SG_TERRASYNC = 0x01000000,
|
||||
SG_PARTICLES = 0x02000000,
|
||||
SG_HEADLESS = 0x04000000,
|
||||
SG_UNDEFD = 0x08000000, // For range checking
|
||||
// SG_OSG (OSG notify) - will always be displayed regardless of FG log settings as OSG log level is configured
|
||||
// separately and thus it makes more sense to allow these message through.
|
||||
SG_OSG = 0x08000000,
|
||||
SG_UNDEFD = 0x10000000, // For range checking
|
||||
|
||||
SG_ALL = 0xFFFFFFFF
|
||||
} sgDebugClass;
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
|
||||
#if defined (SG_WINDOWS)
|
||||
// for AllocConsole, OutputDebugString
|
||||
@@ -61,7 +62,12 @@ LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
|
||||
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
|
||||
{
|
||||
return ((c & m_class) != 0 && p >= m_priority);
|
||||
|
||||
if ((c & m_class) != 0 && p >= m_priority)
|
||||
return true;
|
||||
if (c == SG_OSG) // always have OSG logging as it OSG logging is configured separately.
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
|
||||
@@ -69,6 +75,18 @@ void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
|
||||
m_priority = p;
|
||||
m_class = c;
|
||||
}
|
||||
const char* LogCallback::debugPriorityToString(sgDebugPriority p)
|
||||
{
|
||||
switch (p) {
|
||||
case SG_ALERT: return "ALRT";
|
||||
case SG_BULK: return "BULK";
|
||||
case SG_DEBUG: return "DBUG";
|
||||
case SG_INFO: return "INFO";
|
||||
case SG_POPUP: return "POPU";
|
||||
case SG_WARN: return "WARN";
|
||||
default: return "UNKN";
|
||||
}
|
||||
}
|
||||
|
||||
const char* LogCallback::debugClassToString(sgDebugClass c)
|
||||
{
|
||||
@@ -101,6 +119,7 @@ const char* LogCallback::debugClassToString(sgDebugClass c)
|
||||
case SG_TERRASYNC: return "terrasync";
|
||||
case SG_PARTICLES: return "particles";
|
||||
case SG_HEADLESS: return "headless";
|
||||
case SG_OSG: return "OSG";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
@@ -112,18 +131,41 @@ const char* LogCallback::debugClassToString(sgDebugClass c)
|
||||
class FileLogCallback : public simgear::LogCallback
|
||||
{
|
||||
public:
|
||||
SGTimeStamp logTimer;
|
||||
FileLogCallback(const SGPath& aPath, sgDebugClass c, sgDebugPriority p) :
|
||||
simgear::LogCallback(c, p)
|
||||
{
|
||||
m_file.open(aPath, std::ios_base::out | std::ios_base::trunc);
|
||||
logTimer.stamp();
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& message)
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
m_file << debugClassToString(c) << ":" << (int) p
|
||||
<< ":" << file << ":" << line << ":" << message << std::endl;
|
||||
|
||||
|
||||
// fprintf(stderr, "%7.2f [%.8s]:%-10s %s\n", logTimer.elapsedMSec() / 1000.0, debugPriorityToString(p), debugClassToString(c), aMessage.c_str());
|
||||
m_file
|
||||
<< std::fixed
|
||||
<< std::setprecision(2)
|
||||
<< std::setw(8)
|
||||
<< std::right
|
||||
<< (logTimer.elapsedMSec() / 1000.0)
|
||||
<< std::setw(8)
|
||||
<< std::left
|
||||
<< " ["+std::string(debugPriorityToString(p))+"]:"
|
||||
<< std::setw(10)
|
||||
<< std::left
|
||||
<< debugClassToString(c)
|
||||
<< " "
|
||||
<< file
|
||||
<< ":"
|
||||
<< line
|
||||
<< ":"
|
||||
<< message << std::endl;
|
||||
//m_file << debugClassToString(c) << ":" << (int)p
|
||||
// << ":" << file << ":" << line << ":" << message << std::endl;
|
||||
}
|
||||
private:
|
||||
sg_ofstream m_file;
|
||||
@@ -132,9 +174,12 @@ private:
|
||||
class StderrLogCallback : public simgear::LogCallback
|
||||
{
|
||||
public:
|
||||
SGTimeStamp logTimer;
|
||||
|
||||
StderrLogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
simgear::LogCallback(c, p)
|
||||
{
|
||||
logTimer.stamp();
|
||||
}
|
||||
|
||||
#if defined (SG_WINDOWS)
|
||||
@@ -148,8 +193,9 @@ public:
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
|
||||
fprintf(stderr, "%s\n", aMessage.c_str());
|
||||
//fprintf(stderr, "%s\n", aMessage.c_str());
|
||||
fprintf(stderr, "%8.2f [%.8s]:%-10s %s\n", logTimer.elapsedMSec()/1000.0, debugPriorityToString(p), debugClassToString(c), aMessage.c_str());
|
||||
// file, line, aMessage.c_str());
|
||||
//fprintf(stderr, "%s:%d:%s:%d:%s\n", debugClassToString(c), p,
|
||||
// file, line, aMessage.c_str());
|
||||
fflush(stderr);
|
||||
@@ -470,6 +516,10 @@ public:
|
||||
// Testing mode, so always log.
|
||||
if (m_testMode) return true;
|
||||
|
||||
// SG_OSG (OSG notify) - will always be displayed regardless of FG log settings as OSG log level is configured
|
||||
// separately and thus it makes more sense to allow these message through.
|
||||
if (p == SG_OSG) return true;
|
||||
|
||||
p = translatePriority(p);
|
||||
if (p >= SG_INFO) return true;
|
||||
return ((c & m_logClass) != 0 && p >= m_logPriority);
|
||||
|
||||
@@ -52,6 +52,7 @@ protected:
|
||||
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
|
||||
|
||||
static const char* debugClassToString(sgDebugClass c);
|
||||
static const char* debugPriorityToString(sgDebugPriority p);
|
||||
private:
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
|
||||
@@ -92,6 +92,12 @@ public:
|
||||
return p;
|
||||
}
|
||||
|
||||
static PropertyObject<T> create(SGPropertyNode_ptr aNode)
|
||||
{
|
||||
PropertyObject<T> p(aNode);
|
||||
return p;
|
||||
}
|
||||
|
||||
static PropertyObject<T> create(SGPropertyNode* aNode, T aValue)
|
||||
{
|
||||
PropertyObject<T> p(aNode);
|
||||
@@ -157,6 +163,8 @@ template <>
|
||||
class PropertyObject<std::string> : PropertyObjectBase
|
||||
{
|
||||
public:
|
||||
PropertyObject() = default;
|
||||
|
||||
explicit PropertyObject(const char* aChild) :
|
||||
PropertyObjectBase(aChild)
|
||||
{ }
|
||||
@@ -183,6 +191,12 @@ public:
|
||||
return p;
|
||||
}
|
||||
|
||||
static PropertyObject<std::string> create(SGPropertyNode_ptr aNode)
|
||||
{
|
||||
PropertyObject<std::string> p(aNode);
|
||||
return p;
|
||||
}
|
||||
|
||||
static PropertyObject<std::string> create(SGPropertyNode* aNode, const std::string& aValue)
|
||||
{
|
||||
PropertyObject<std::string> p(aNode);
|
||||
|
||||
@@ -205,6 +205,34 @@ void testCreate()
|
||||
|
||||
}
|
||||
|
||||
void testDeclare()
|
||||
{
|
||||
PropertyObject<bool> a;
|
||||
PropertyObject<int> b;
|
||||
PropertyObject<double> c;
|
||||
PropertyObject<std::string> d;
|
||||
}
|
||||
|
||||
void testDeclareThenDefine()
|
||||
{
|
||||
// Declare.
|
||||
PropertyObject<bool> a;
|
||||
PropertyObject<std::string> b;
|
||||
|
||||
// Property nodes.
|
||||
SGPropertyNode_ptr propsA = new SGPropertyNode;
|
||||
SGPropertyNode_ptr propsB = new SGPropertyNode;
|
||||
propsA->setValue(true);
|
||||
propsB->setValue("test");
|
||||
|
||||
// Now define.
|
||||
a = PropertyObject<bool>::create(propsA);
|
||||
b = PropertyObject<std::string>::create(propsB);
|
||||
|
||||
assert(a == true);
|
||||
assert(b == "test");
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
testRoot = new SGPropertyNode();
|
||||
@@ -227,6 +255,8 @@ int main(int argc, char* argv[])
|
||||
testAssignment();
|
||||
testSTLContainer();
|
||||
testCreate();
|
||||
testDeclare();
|
||||
testDeclareThenDefine();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -392,6 +392,8 @@ osg::Image* computeMipmap( osg::Image* image, MipMapTuple attrs )
|
||||
image->getInternalTextureFormat(), image->getPixelFormat(),
|
||||
image->getDataType(), data, osg::Image::USE_NEW_DELETE, image->getPacking() );
|
||||
mipmaps->setMipmapLevels( mipmapOffsets );
|
||||
mipmaps->setName(image->getName());
|
||||
mipmaps->setFileName(image->getFileName());
|
||||
|
||||
return mipmaps.release();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#endif
|
||||
|
||||
#include "ModelRegistry.hxx"
|
||||
#include <simgear/scene/util/SGImageUtils.hxx>
|
||||
#include "../material/mipmap.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
@@ -44,6 +46,7 @@
|
||||
#include <osgDB/Registry>
|
||||
#include <osgDB/SharedStateManager>
|
||||
#include <osgUtil/Optimizer>
|
||||
#include <osg/Texture>
|
||||
|
||||
#include <simgear/sg_inlines.h>
|
||||
|
||||
@@ -61,6 +64,10 @@
|
||||
#include "BoundingVolumeBuildVisitor.hxx"
|
||||
#include "model.hxx"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
#include <boost/tuple/tuple_comparison.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace osg;
|
||||
using namespace osgUtil;
|
||||
@@ -173,110 +180,270 @@ public:
|
||||
|
||||
} // namespace
|
||||
|
||||
Node* DefaultProcessPolicy::process(Node* node, const string& filename,
|
||||
const Options* opt)
|
||||
static bool isPowerOfTwo(int width, int height)
|
||||
{
|
||||
return (((width & (width - 1)) == 0) && ((height & (height - 1))) == 0);
|
||||
}
|
||||
osg::Node* DefaultProcessPolicy::process(osg::Node* node, const std::string& filename,
|
||||
const Options* opt)
|
||||
{
|
||||
TextureNameVisitor nameVisitor;
|
||||
node->accept(nameVisitor);
|
||||
return node;
|
||||
}
|
||||
|
||||
//#define LOCAL_IMAGE_CACHE
|
||||
#ifdef LOCAL_IMAGE_CACHE
|
||||
typedef std::map<std::string, osg::ref_ptr<Image>> ImageMap;
|
||||
ImageMap _imageMap;
|
||||
//typedef std::map<std::string, osg::ref_ptr<osg::Image> > ImageMap;
|
||||
//ImageMap _imageMap;
|
||||
osg::Image* getImageByName(const std::string& filename)
|
||||
{
|
||||
ImageMap::iterator itr = _imageMap.find(filename);
|
||||
if (itr != _imageMap.end()) return itr->second.get();
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
ReaderWriter::ReadResult
|
||||
ModelRegistry::readImage(const string& fileName,
|
||||
const Options* opt)
|
||||
const Options* opt)
|
||||
{
|
||||
CallbackMap::iterator iter
|
||||
= imageCallbackMap.find(getFileExtension(fileName));
|
||||
{
|
||||
if (iter != imageCallbackMap.end() && iter->second.valid())
|
||||
return iter->second->readImage(fileName, opt);
|
||||
string absFileName = SGModelLib::findDataFile(fileName, opt);
|
||||
if (!fileExists(absFileName)) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
|
||||
<< fileName << "\"");
|
||||
return ReaderWriter::ReadResult::FILE_NOT_FOUND;
|
||||
/*
|
||||
* processor is the interface to the osg_nvtt plugin
|
||||
*/
|
||||
static bool init = false;
|
||||
static osgDB::ImageProcessor *processor = 0;
|
||||
int max_texture_size = SGSceneFeatures::instance()->getMaxTextureSize();
|
||||
if (!init) {
|
||||
processor = osgDB::Registry::instance()->getImageProcessor();
|
||||
init = true;
|
||||
}
|
||||
|
||||
bool cache_active = SGSceneFeatures::instance()->getTextureCacheActive();
|
||||
bool compress_solid = SGSceneFeatures::instance()->getTextureCacheCompressionActive();
|
||||
bool compress_transparent = SGSceneFeatures::instance()->getTextureCacheCompressionActiveTransparent();
|
||||
|
||||
//
|
||||
// heuristically less than 2048 is more likely to be a badly reported size rather than
|
||||
// something that is valid so we'll have a minimum size of 2048.
|
||||
if (max_texture_size < 2048)
|
||||
max_texture_size = 2048;
|
||||
|
||||
std::string fileExtension = getFileExtension(fileName);
|
||||
CallbackMap::iterator iter = imageCallbackMap.find(fileExtension);
|
||||
|
||||
if (iter != imageCallbackMap.end() && iter->second.valid())
|
||||
return iter->second->readImage(fileName, opt);
|
||||
string absFileName = SGModelLib::findDataFile(fileName, opt);
|
||||
|
||||
if (!fileExists(absFileName)) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
|
||||
<< fileName << "\"");
|
||||
return ReaderWriter::ReadResult::FILE_NOT_FOUND;
|
||||
}
|
||||
Registry* registry = Registry::instance();
|
||||
ReaderWriter::ReadResult res;
|
||||
|
||||
if (cache_active) {
|
||||
if (fileExtension != "dds" && fileExtension != "gz") {
|
||||
std::string root = getPathRoot(absFileName);
|
||||
std::string prr = getPathRelative(root, absFileName);
|
||||
std::string cache_root = SGSceneFeatures::instance()->getTextureCompressionPath().c_str();
|
||||
std::string newName = cache_root + "/" + prr;
|
||||
|
||||
SGPath file(absFileName);
|
||||
std::stringstream tstream;
|
||||
tstream << std::hex << file.modTime();
|
||||
newName += "." + tstream.str();
|
||||
|
||||
newName += ".cache.dds";
|
||||
if (!fileExists(newName)) {
|
||||
res = registry->readImageImplementation(absFileName, opt);
|
||||
|
||||
osg::ref_ptr<osg::Image> srcImage = res.getImage();
|
||||
int width = srcImage->s();
|
||||
bool transparent = srcImage->isImageTranslucent();
|
||||
int height = srcImage->t();
|
||||
|
||||
if (height >= max_texture_size)
|
||||
{
|
||||
SG_LOG(SG_IO, SG_WARN, "Image texture too high " << width << "," << height << absFileName);
|
||||
osg::ref_ptr<osg::Image> resizedImage;
|
||||
int factor = height / max_texture_size;
|
||||
if (ImageUtils::resizeImage(srcImage, width / factor, height / factor, resizedImage))
|
||||
srcImage = resizedImage;
|
||||
width = srcImage->s();
|
||||
height = srcImage->t();
|
||||
}
|
||||
if (width >= max_texture_size)
|
||||
{
|
||||
SG_LOG(SG_IO, SG_WARN, "Image texture too wide " << width << "," << height << absFileName);
|
||||
osg::ref_ptr<osg::Image> resizedImage;
|
||||
int factor = width / max_texture_size;
|
||||
if (ImageUtils::resizeImage(srcImage, width / factor, height / factor, resizedImage))
|
||||
srcImage = resizedImage;
|
||||
width = srcImage->s();
|
||||
height = srcImage->t();
|
||||
}
|
||||
|
||||
//
|
||||
// only cache power of two textures that are of a reasonable size
|
||||
if (width >= 64 && height >= 64 && isPowerOfTwo(width, height)) {
|
||||
simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE);
|
||||
|
||||
SGPath filePath(newName);
|
||||
filePath.create_dir();
|
||||
|
||||
// setup the options string for saving the texture as we don't want OSG to auto flip the texture
|
||||
// as this complicates loading as it requires a flag to flip it back which will preclude the
|
||||
// image from being cached because we will have to clone the options to set the flag and thus lose
|
||||
// the link to the cache in the options from the caller.
|
||||
osg::ref_ptr<Options> nopt;
|
||||
nopt = opt->cloneOptions();
|
||||
std::string optionstring = nopt->getOptionString();
|
||||
|
||||
if (!optionstring.empty())
|
||||
optionstring += " ";
|
||||
|
||||
nopt->setOptionString(optionstring + "ddsNoAutoFlipWrite");
|
||||
|
||||
/*
|
||||
* decide if we need to compress this.
|
||||
*/
|
||||
bool compress = (transparent && compress_transparent) || (!transparent && compress_solid);
|
||||
|
||||
if (compress) {
|
||||
if (processor)
|
||||
{
|
||||
if (transparent)
|
||||
processor->compress(*srcImage, osg::Texture::USE_S3TC_DXT5_COMPRESSION, true, true, osgDB::ImageProcessor::USE_CPU, osgDB::ImageProcessor::PRODUCTION);
|
||||
else
|
||||
processor->compress(*srcImage, osg::Texture::USE_S3TC_DXT1_COMPRESSION, true, true, osgDB::ImageProcessor::USE_CPU, osgDB::ImageProcessor::PRODUCTION);
|
||||
//processor->generateMipMap(*srcImage, true, osgDB::ImageProcessor::USE_CPU);
|
||||
}
|
||||
else {
|
||||
simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE);
|
||||
SG_LOG(SG_IO, SG_WARN, "Texture compression plugin (osg_nvtt) not available; storing uncompressed image: " << newName);
|
||||
srcImage = simgear::effect::computeMipmap(srcImage, mipmapFunctions);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (processor) {
|
||||
processor->generateMipMap(*srcImage, true, osgDB::ImageProcessor::USE_CPU);
|
||||
}
|
||||
else {
|
||||
simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE);
|
||||
srcImage = simgear::effect::computeMipmap(srcImage, mipmapFunctions);
|
||||
}
|
||||
}
|
||||
registry->writeImage(*srcImage, newName, nopt);
|
||||
absFileName = newName;
|
||||
}
|
||||
}
|
||||
else
|
||||
absFileName = newName;
|
||||
}
|
||||
}
|
||||
res = registry->readImageImplementation(absFileName, opt);
|
||||
|
||||
Registry* registry = Registry::instance();
|
||||
ReaderWriter::ReadResult res;
|
||||
res = registry->readImageImplementation(absFileName, opt);
|
||||
if (!res.success()) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message());
|
||||
return res;
|
||||
}
|
||||
if (!res.success()) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message());
|
||||
return res;
|
||||
}
|
||||
|
||||
if (res.loadedFromCache())
|
||||
SG_LOG(SG_IO, SG_BULK, "Returning cached image \""
|
||||
<< res.getImage()->getFileName() << "\"");
|
||||
else
|
||||
SG_LOG(SG_IO, SG_BULK, "Reading image \""
|
||||
<< res.getImage()->getFileName() << "\"");
|
||||
osg::ref_ptr<osg::Image> srcImage1 = res.getImage();
|
||||
|
||||
//printf(" --> finished loading %s [%s] (%s) %d\n", absFileName.c_str(), srcImage1->getFileName().c_str(), res.loadedFromCache() ? "from cache" : "from disk", res.getImage()->getOrigin());
|
||||
/*
|
||||
* Fixup the filename - as when loading from eg. dds.gz the originating filename is lost in the conversion due to the way the OSG loader works
|
||||
*/
|
||||
if (srcImage1->getFileName().empty()) {
|
||||
srcImage1->setFileName(absFileName);
|
||||
}
|
||||
|
||||
if (srcImage1->getName().empty()) {
|
||||
srcImage1->setName(absFileName);
|
||||
}
|
||||
|
||||
if (res.loadedFromCache())
|
||||
SG_LOG(SG_IO, SG_BULK, "Returning cached image \""
|
||||
<< res.getImage()->getFileName() << "\"");
|
||||
else
|
||||
SG_LOG(SG_IO, SG_BULK, "Reading image \""
|
||||
<< res.getImage()->getFileName() << "\"");
|
||||
|
||||
// as of March 2018 all patents have expired, https://en.wikipedia.org/wiki/S3_Texture_Compression#Patent
|
||||
// there is support for S3TC DXT1..5 in MESA https://www.phoronix.com/scan.php?page=news_item&px=S3TC-Lands-In-Mesa
|
||||
// so it seems that there isn't a valid reason to warn any longer; and beside this is one of those cases where it should
|
||||
// really only be a developer message
|
||||
#ifdef WARN_DDS_TEXTURES
|
||||
// Check for precompressed textures that depend on an extension
|
||||
switch (res.getImage()->getPixelFormat()) {
|
||||
switch (res.getImage()->getPixelFormat()) {
|
||||
|
||||
// GL_EXT_texture_compression_s3tc
|
||||
// patented, no way to decompress these
|
||||
// GL_EXT_texture_compression_s3tc
|
||||
// patented, no way to decompress these
|
||||
#ifndef GL_EXT_texture_compression_s3tc
|
||||
#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
|
||||
#endif
|
||||
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
||||
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
||||
|
||||
// GL_EXT_texture_sRGB
|
||||
// patented, no way to decompress these
|
||||
// GL_EXT_texture_sRGB
|
||||
// patented, no way to decompress these
|
||||
#ifndef GL_EXT_texture_sRGB
|
||||
#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C
|
||||
#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D
|
||||
#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E
|
||||
#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F
|
||||
#endif
|
||||
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
|
||||
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
|
||||
|
||||
// GL_TDFX_texture_compression_FXT1
|
||||
// can decompress these in software but
|
||||
// no code present in simgear.
|
||||
// GL_TDFX_texture_compression_FXT1
|
||||
// can decompress these in software but
|
||||
// no code present in simgear.
|
||||
#ifndef GL_3DFX_texture_compression_FXT1
|
||||
#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0
|
||||
#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1
|
||||
#endif
|
||||
case GL_COMPRESSED_RGB_FXT1_3DFX:
|
||||
case GL_COMPRESSED_RGBA_FXT1_3DFX:
|
||||
case GL_COMPRESSED_RGB_FXT1_3DFX:
|
||||
case GL_COMPRESSED_RGBA_FXT1_3DFX:
|
||||
|
||||
// GL_EXT_texture_compression_rgtc
|
||||
// can decompress these in software but
|
||||
// no code present in simgear.
|
||||
// GL_EXT_texture_compression_rgtc
|
||||
// can decompress these in software but
|
||||
// no code present in simgear.
|
||||
#ifndef GL_EXT_texture_compression_rgtc
|
||||
#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB
|
||||
#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC
|
||||
#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD
|
||||
#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE
|
||||
#endif
|
||||
case GL_COMPRESSED_RED_RGTC1_EXT:
|
||||
case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT:
|
||||
case GL_COMPRESSED_RED_GREEN_RGTC2_EXT:
|
||||
case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
|
||||
case GL_COMPRESSED_RED_RGTC1_EXT:
|
||||
case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT:
|
||||
case GL_COMPRESSED_RED_GREEN_RGTC2_EXT:
|
||||
case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
|
||||
|
||||
SG_LOG(SG_IO, SG_WARN, "Image \"" << fileName << "\"\n"
|
||||
"uses compressed textures which cannot be supported on "
|
||||
"some systems.\n"
|
||||
"Please decompress this texture for improved portability.");
|
||||
break;
|
||||
SG_LOG(SG_IO, SG_WARN, "Image \"" << fileName << "\"\n"
|
||||
"uses compressed textures which cannot be supported on "
|
||||
"some systems.\n"
|
||||
"Please decompress this texture for improved portability.");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -430,7 +597,7 @@ public:
|
||||
setObjectCacheHint((Options::CacheHintOptions)cacheOptions);
|
||||
registry->setOptions(options);
|
||||
registry->getOrCreateSharedStateManager()->
|
||||
setShareMode(SharedStateManager::SHARE_STATESETS);
|
||||
setShareMode(SharedStateManager::SHARE_ALL);
|
||||
registry->setReadFileCallback(ModelRegistry::instance());
|
||||
}
|
||||
};
|
||||
@@ -520,9 +687,79 @@ typedef ModelRegistryCallback<OBJProcessPolicy,
|
||||
OSGSubstitutePolicy, BuildLeafBVHPolicy>
|
||||
OBJCallback;
|
||||
|
||||
|
||||
// we get optimal geometry from the loader (Hah!).
|
||||
struct IVEOptimizePolicy : public OptimizeModelPolicy {
|
||||
IVEOptimizePolicy(const string& extension) :
|
||||
OptimizeModelPolicy(extension)
|
||||
{
|
||||
_osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
|
||||
}
|
||||
Node* optimize(Node* node, const string& fileName,
|
||||
const Options* opt)
|
||||
{
|
||||
ref_ptr<Node> optimized
|
||||
= OptimizeModelPolicy::optimize(node, fileName, opt);
|
||||
Group* group = dynamic_cast<Group*>(optimized.get());
|
||||
MatrixTransform* transform
|
||||
= dynamic_cast<MatrixTransform*>(optimized.get());
|
||||
if (((transform && transform->getMatrix().isIdentity()) || group)
|
||||
&& group->getName().empty()
|
||||
&& group->getNumChildren() == 1) {
|
||||
optimized = static_cast<Node*>(group->getChild(0));
|
||||
group = dynamic_cast<Group*>(optimized.get());
|
||||
if (group && group->getName().empty()
|
||||
&& group->getNumChildren() == 1)
|
||||
optimized = static_cast<Node*>(group->getChild(0));
|
||||
}
|
||||
const SGReaderWriterOptions* sgopt
|
||||
= dynamic_cast<const SGReaderWriterOptions*>(opt);
|
||||
|
||||
if (sgopt && sgopt->getInstantiateMaterialEffects()) {
|
||||
optimized = instantiateMaterialEffects(optimized.get(), sgopt);
|
||||
}
|
||||
else if (sgopt && sgopt->getInstantiateEffects()) {
|
||||
optimized = instantiateEffects(optimized.get(), sgopt);
|
||||
}
|
||||
|
||||
return optimized.release();
|
||||
}
|
||||
};
|
||||
|
||||
struct IVEProcessPolicy {
|
||||
IVEProcessPolicy(const string& extension) {}
|
||||
Node* process(Node* node, const string& filename,
|
||||
const Options* opt)
|
||||
{
|
||||
Matrix m(1, 0, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, -1, 0, 0,
|
||||
0, 0, 0, 1);
|
||||
// XXX Does there need to be a Group node here to trick the
|
||||
// optimizer into optimizing the static transform?
|
||||
osg::Group* root = new Group;
|
||||
MatrixTransform* transform = new MatrixTransform;
|
||||
root->addChild(transform);
|
||||
|
||||
transform->setDataVariance(Object::STATIC);
|
||||
transform->setMatrix(m);
|
||||
transform->addChild(node);
|
||||
|
||||
return root;
|
||||
}
|
||||
};
|
||||
|
||||
typedef ModelRegistryCallback<IVEProcessPolicy, DefaultCachePolicy,
|
||||
IVEOptimizePolicy,
|
||||
OSGSubstitutePolicy, BuildLeafBVHPolicy>
|
||||
IVECallback;
|
||||
|
||||
namespace
|
||||
{
|
||||
ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");
|
||||
ModelRegistryCallbackProxy<OBJCallback> g_objRegister("obj");
|
||||
ModelRegistryCallbackProxy<IVECallback> g_iveRegister("ive");
|
||||
ModelRegistryCallbackProxy<IVECallback> g_osgtRegister("osgt");
|
||||
ModelRegistryCallbackProxy<IVECallback> g_osgbRegister("osgb");
|
||||
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ SGModelLib::loadDeferredModel(const string &path, SGPropertyNode *prop_root,
|
||||
osg::PagedLOD*
|
||||
SGModelLib::loadPagedModel(SGPropertyNode *prop_root, SGModelData *data, SGModelLOD model_lods)
|
||||
{
|
||||
unsigned int simple_models = 0;
|
||||
osg::PagedLOD *plod = new osg::PagedLOD;
|
||||
|
||||
osg::ref_ptr<SGReaderWriterOptions> opt;
|
||||
@@ -189,15 +190,15 @@ SGModelLib::loadPagedModel(SGPropertyNode *prop_root, SGModelData *data, SGModel
|
||||
plod->setMinimumExpiryTime(i, prop_root->getDoubleValue("/sim/rendering/plod-minimum-expiry-time-secs", 180.0 ) );
|
||||
|
||||
std::string lext = SGPath(lod.path).lower_extension();
|
||||
|
||||
// We can only have one set of ReaderWriterOptions for the PagedLOD, so
|
||||
// we will just have to assume that if one of the defined models can
|
||||
// handle instantiated effects, then the other can as well.
|
||||
if ((lext == "ac") || (lext == "obj")) {
|
||||
opt->setInstantiateEffects(true);
|
||||
simple_models++;
|
||||
}
|
||||
}
|
||||
|
||||
// If all we have are simple models, then we can instantiate effects in
|
||||
// the loader.
|
||||
if (simple_models == model_lods.getNumLODs()) opt->setInstantiateEffects(true);
|
||||
|
||||
plod->setDatabaseOptions(opt.get());
|
||||
|
||||
return plod;
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
#include <simgear/scene/tgdb/apt_signs.hxx>
|
||||
#include <simgear/scene/tgdb/obj.hxx>
|
||||
#include <simgear/scene/material/matlib.hxx>
|
||||
#include <simgear/scene/tgdb/SGBuildingBin.hxx>
|
||||
|
||||
#include "SGOceanTile.hxx"
|
||||
|
||||
@@ -59,6 +61,7 @@
|
||||
#define ROAD_DETAILED "OBJECT_ROAD_DETAILED"
|
||||
#define RAILWAY_ROUGH "OBJECT_RAILWAY_ROUGH"
|
||||
#define RAILWAY_DETAILED "OBJECT_RAILWAY_DETAILED"
|
||||
#define BUILDING_LIST "BUILDING_LIST"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
@@ -121,6 +124,12 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
double _hdg;
|
||||
int _size;
|
||||
};
|
||||
struct _BuildingList {
|
||||
_BuildingList() : _lon(0), _lat(0), _elev(0) { }
|
||||
std::string _filename;
|
||||
std::string _material_name;
|
||||
double _lon, _lat, _elev;
|
||||
};
|
||||
|
||||
class DelayLoadReadFileCallback : public OptionsReadFileCallback {
|
||||
|
||||
@@ -185,8 +194,6 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
};
|
||||
typedef QuadTreeBuilder<osg::LOD*, _ObjectStatic, MakeQuadLeaf, AddModelLOD,
|
||||
GetModelLODCoord> STGObjectsQuadtree;
|
||||
|
||||
|
||||
public:
|
||||
virtual osgDB::ReaderWriter::ReadResult
|
||||
readNode(const std::string&, const osgDB::Options*)
|
||||
@@ -203,12 +210,40 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
if (signBuilder.getSignsGroup())
|
||||
group->addChild(signBuilder.getSignsGroup());
|
||||
|
||||
if (_buildingList.size() > 0) {
|
||||
SGMaterialLibPtr matlib = _options->getMaterialLib();
|
||||
bool useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
|
||||
|
||||
if (!matlib) {
|
||||
SG_LOG( SG_TERRAIN, SG_ALERT, "Unable to get materials definition for buildings");
|
||||
} else {
|
||||
for (std::list<_BuildingList>::iterator i = _buildingList.begin(); i != _buildingList.end(); ++i) {
|
||||
// Build buildings for each list of buildings
|
||||
SGGeod geodPos = SGGeod::fromDegM(i->_lon, i->_lat, 0.0);
|
||||
SGMaterial* mat = matlib->find(i->_material_name, geodPos);
|
||||
SGPath path = SGPath(i->_filename);
|
||||
SGBuildingBin* buildingBin = new SGBuildingBin(path, mat, useVBOs);
|
||||
|
||||
SGBuildingBinList buildingBinList;
|
||||
buildingBinList.push_back(buildingBin);
|
||||
|
||||
osg::MatrixTransform* matrixTransform;
|
||||
matrixTransform = new osg::MatrixTransform(makeZUpFrame(SGGeod::fromDegM(i->_lon, i->_lat, i->_elev)));
|
||||
matrixTransform->setName("rotateBuildings");
|
||||
matrixTransform->setDataVariance(osg::Object::STATIC);
|
||||
matrixTransform->addChild(createRandomBuildings(buildingBinList, osg::Matrix::identity(), _options));
|
||||
group->addChild(matrixTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return group.release();
|
||||
}
|
||||
|
||||
mt _seed;
|
||||
std::list<_ObjectStatic> _objectStaticList;
|
||||
std::list<_Sign> _signList;
|
||||
std::list<_BuildingList> _buildingList;
|
||||
|
||||
/// The original options to use for this bunch of models
|
||||
osg::ref_ptr<SGReaderWriterOptions> _options;
|
||||
@@ -315,7 +350,6 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
|
||||
// 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 detailedRange = atof(options->getPluginStringData("SimGear::LOD_RANGE_DETAILED").c_str());
|
||||
double bareRangeDelta = atof(options->getPluginStringData("SimGear::LOD_RANGE_BARE").c_str());
|
||||
double roughRangeDelta = atof(options->getPluginStringData("SimGear::LOD_RANGE_ROUGH").c_str());
|
||||
@@ -490,6 +524,13 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
obj._options = opt;
|
||||
checkInsideBucket(absoluteFileName, obj._lon, obj._lat);
|
||||
_objectStaticList.push_back(obj);
|
||||
} else if (token == BUILDING_LIST) {
|
||||
_BuildingList buildinglist;
|
||||
buildinglist._filename = path.local8BitStr();
|
||||
in >> buildinglist._material_name >> buildinglist._lon >> buildinglist._lat >> buildinglist._elev;
|
||||
checkInsideBucket(absoluteFileName, buildinglist._lon, buildinglist._lat);
|
||||
_buildingListList.push_back(buildinglist);
|
||||
//SG_LOG(SG_TERRAIN, SG_ALERT, "Building list: " << buildinglist._filename << " " << buildinglist._material_name << " " << buildinglist._lon << " " << buildinglist._lat);
|
||||
} else {
|
||||
SG_LOG( SG_TERRAIN, SG_ALERT, absoluteFileName
|
||||
<< ": Unknown token '" << token << "'" );
|
||||
@@ -560,6 +601,7 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
// we just need to know about the read file callback that itself holds the data
|
||||
osg::ref_ptr<DelayLoadReadFileCallback> readFileCallback = new DelayLoadReadFileCallback;
|
||||
readFileCallback->_objectStaticList = _objectStaticList;
|
||||
readFileCallback->_buildingList = _buildingListList;
|
||||
readFileCallback->_signList = _signList;
|
||||
readFileCallback->_options = options;
|
||||
readFileCallback->_bucket = bucket;
|
||||
@@ -584,6 +626,7 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
std::list<_Object> _objectList;
|
||||
std::list<_ObjectStatic> _objectStaticList;
|
||||
std::list<_Sign> _signList;
|
||||
std::list<_BuildingList> _buildingListList;
|
||||
};
|
||||
|
||||
ReaderWriterSTG::ReaderWriterSTG()
|
||||
|
||||
@@ -44,12 +44,11 @@
|
||||
#include <osgDB/FileUtils>
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
#include <simgear/math/SGLimits.hxx>
|
||||
#include <simgear/math/SGMisc.hxx>
|
||||
#include <simgear/math/sg_random.h>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/scene/material/Effect.hxx>
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/model/model.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
@@ -107,6 +106,59 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const
|
||||
return bb;
|
||||
}
|
||||
|
||||
// Set up a BuildingBin from a file containing a list of individual building
|
||||
// positions.
|
||||
SGBuildingBin::SGBuildingBin(const SGPath& absoluteFileName, const SGMaterial *mat, bool useVBOs) :
|
||||
SGBuildingBin::SGBuildingBin(mat, useVBOs)
|
||||
{
|
||||
if (!absoluteFileName.exists()) {
|
||||
SG_LOG(SG_TERRAIN, SG_ALERT, "Building list file " << absoluteFileName << " does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
sg_gzifstream stream(absoluteFileName);
|
||||
if (!stream.is_open()) {
|
||||
SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to open " << absoluteFileName << " does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
while (!stream.eof()) {
|
||||
// read a line. Each line defines a single builing position, and may have
|
||||
// a comment, starting with #
|
||||
std::string line;
|
||||
std::getline(stream, line);
|
||||
|
||||
// strip comments
|
||||
std::string::size_type hash_pos = line.find('#');
|
||||
if (hash_pos != std::string::npos)
|
||||
line.resize(hash_pos);
|
||||
|
||||
// and process further
|
||||
std::stringstream in(line);
|
||||
|
||||
// Line format is X Y Z R T
|
||||
// where:
|
||||
// X,Y,Z are the cartesian coordinates of the bottom SW corner of the building. +X is East, +Y is North
|
||||
// R is the building rotation in degrees centered on the SW corner
|
||||
// T is the building type [0, 1, 2] for SMALL, MEDIUM, LARGE
|
||||
float x, y, z, r;
|
||||
int t;
|
||||
in >> x >> y >> z >> r >> t;
|
||||
|
||||
//SG_LOG(SG_TERRAIN, SG_ALERT, "Building entry " << x << " " << y << " " << z << " " << t );
|
||||
SGVec3f p = SGVec3f(x,y,z);
|
||||
BuildingType type = BuildingType::SMALL;
|
||||
if (t == 1) type = BuildingType::MEDIUM;
|
||||
if (t == 2) type = BuildingType::LARGE;
|
||||
|
||||
// Rotation is in the file as degrees, but in the datastructure normalized
|
||||
// to 0.0 - 1.0
|
||||
insert(p, (float) (r / 360.0f), type);
|
||||
}
|
||||
|
||||
stream.close();
|
||||
};
|
||||
|
||||
// Set up the building set based on the material definitions
|
||||
SGBuildingBin::SGBuildingBin(const SGMaterial *mat, bool useVBOs) {
|
||||
|
||||
@@ -165,7 +217,7 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const
|
||||
if (useVBOs) {
|
||||
sharedGeometry->setUseVertexBufferObjects(true);
|
||||
}
|
||||
|
||||
|
||||
for (unsigned int j = 0; j < BUILDING_SET_SIZE; j++) {
|
||||
float width;
|
||||
float depth;
|
||||
@@ -820,8 +872,8 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const
|
||||
};
|
||||
|
||||
// This actually returns a MatrixTransform node. If we rotate the whole
|
||||
// forest into the local Z-up coordinate system we can reuse the
|
||||
// primitive building geometry for all the forests of the same type.
|
||||
// set of buildings into the local Z-up coordinate system we can reuse the
|
||||
// primitive building geometry for all the buildings of the same type.
|
||||
osg::Group* createRandomBuildings(SGBuildingBinList& buildings, const osg::Matrix& transform,
|
||||
const SGReaderWriterOptions* options)
|
||||
{
|
||||
|
||||
@@ -36,10 +36,12 @@
|
||||
#include <osg/Material>
|
||||
#include <osg/CullFace>
|
||||
|
||||
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/scene/material/mat.hxx>
|
||||
|
||||
#include <simgear/scene/material/Effect.hxx>
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
|
||||
#include <simgear/scene/util/QuadTreeBuilder.hxx>
|
||||
#include <simgear/scene/util/RenderConstants.hxx>
|
||||
#include <simgear/scene/util/StateAttributeFactory.hxx>
|
||||
@@ -169,6 +171,7 @@ private:
|
||||
public:
|
||||
|
||||
SGBuildingBin(const SGMaterial *mat, bool useVBOs);
|
||||
SGBuildingBin(const SGPath& absoluteFileName, const SGMaterial *mat, bool useVBOs);
|
||||
|
||||
~SGBuildingBin() {
|
||||
smallBuildings.clear();
|
||||
|
||||
@@ -230,9 +230,9 @@ void addTreeToLeafGeode(Geode* geode, const SGVec3f& p, const SGVec3f& t)
|
||||
posArray->insert(posArray->end(), 4, pos);
|
||||
|
||||
size_t numVerts = posArray->size();
|
||||
int imax = 2;
|
||||
unsigned int imax = 2;
|
||||
if (use_tree_shadows) { imax = 3; }
|
||||
for (int i = 0; i < imax; ++i) {
|
||||
for (unsigned int i = 0; i < imax; ++i) {
|
||||
if (i < geom->getNumPrimitiveSets()) {
|
||||
DrawArrays* primSet = static_cast<DrawArrays*>(geom->getPrimitiveSet(i));
|
||||
if (primSet != nullptr)
|
||||
|
||||
@@ -16,6 +16,7 @@ set(HEADERS
|
||||
RenderConstants.hxx
|
||||
SGDebugDrawCallback.hxx
|
||||
SGEnlargeBoundingBox.hxx
|
||||
SGImageUtils.hxx
|
||||
SGNodeMasks.hxx
|
||||
SGPickCallback.hxx
|
||||
SGReaderWriterOptions.hxx
|
||||
@@ -44,6 +45,7 @@ set(SOURCES
|
||||
PrimitiveUtils.cxx
|
||||
QuadTreeBuilder.cxx
|
||||
SGEnlargeBoundingBox.cxx
|
||||
SGImageUtils.cxx
|
||||
SGReaderWriterOptions.cxx
|
||||
SGSceneFeatures.cxx
|
||||
SGSceneUserData.cxx
|
||||
|
||||
2167
simgear/scene/util/SGImageUtils.cxx
Normal file
2167
simgear/scene/util/SGImageUtils.cxx
Normal file
File diff suppressed because it is too large
Load Diff
542
simgear/scene/util/SGImageUtils.hxx
Normal file
542
simgear/scene/util/SGImageUtils.hxx
Normal file
@@ -0,0 +1,542 @@
|
||||
/* -*-c++-*- */
|
||||
/* ImageUtils: copied from osgEarth - Geospatial SDK for OpenSceneGraph
|
||||
* Copyright 2018 Pelican Mapping
|
||||
* http://osgearth.org
|
||||
*
|
||||
* osgEarth is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef SIMGEAR_IMAGEUTILS_H
|
||||
#define SIMGEAR_IMAGEUTILS_H
|
||||
|
||||
#include <osg/Image>
|
||||
#include <osg/Texture>
|
||||
#include <osg/GL>
|
||||
#include <osg/NodeVisitor>
|
||||
#include <osgDB/ReaderWriter>
|
||||
#include <vector>
|
||||
|
||||
//These formats were not added to OSG until after 2.8.3 so we need to define them to use them.
|
||||
#ifndef GL_EXT_texture_compression_rgtc
|
||||
#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB
|
||||
#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC
|
||||
#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD
|
||||
#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE
|
||||
#endif
|
||||
|
||||
#ifndef GL_IMG_texture_compression_pvrtc
|
||||
#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
|
||||
#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
|
||||
#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
|
||||
#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03
|
||||
#endif
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
class ImageUtils
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Clones an image.
|
||||
*
|
||||
* Use this instead of the osg::Image copy construtor, which keeps the referenced to
|
||||
* its underlying BufferObject around. Calling dirty() on the new clone appears to
|
||||
* help, but just call this method instead to be sure.
|
||||
*/
|
||||
static osg::Image* cloneImage(const osg::Image* image);
|
||||
|
||||
/**
|
||||
* Tweaks an image for consistency. OpenGL allows enums like "GL_RGBA" et.al. to be
|
||||
* used in the internal texture format, when really "GL_RGBA8" is the proper things
|
||||
* to use. This method accounts for that. Some parts of osgEarth (like the texture-
|
||||
* array compositor) rely on the internal texture format being correct.
|
||||
* (http://http.download.nvidia.com/developer/Papers/2005/Fast_Texture_Transfers/Fast_Texture_Transfers.pdf)
|
||||
*/
|
||||
static void fixInternalFormat(osg::Image* image);
|
||||
|
||||
/**
|
||||
* Marks an image as containing un-normalized data values.
|
||||
*
|
||||
* Normally the values in an image are "normalized", i.e. scaled so they are in the
|
||||
* range [0..1]. This is normal for color values. But when the image is being used
|
||||
* for coverage data (a value lookup table) it is desireable to store the raw
|
||||
* values instead.
|
||||
*/
|
||||
static void markAsUnNormalized(osg::Image* image, bool value);
|
||||
|
||||
/** Inverse of above. */
|
||||
static void markAsNormalized(osg::Image* image, bool value) { markAsUnNormalized(image, !value); }
|
||||
|
||||
/**
|
||||
* Whether the image has been marked as containing un-normalized values.
|
||||
*/
|
||||
static bool isUnNormalized(const osg::Image* image);
|
||||
|
||||
/**
|
||||
* Whether the image has been marked as containing normalized values.
|
||||
*/
|
||||
static bool isNormalized(const osg::Image* image) { return !isUnNormalized(image); }
|
||||
|
||||
/**
|
||||
* Copys a portion of one image into another.
|
||||
*/
|
||||
static bool copyAsSubImage(
|
||||
const osg::Image* src,
|
||||
osg::Image* dst,
|
||||
int dst_start_col, int dst_start_row);
|
||||
|
||||
/**
|
||||
* Resizes an image. Returns a new image, leaving the input image unaltered.
|
||||
*
|
||||
* Note. If the output parameter is NULL, this method will allocate a new image and
|
||||
* resize into that new image. If the output parameter is non-NULL, this method will
|
||||
* assume that the output image is already allocated to the proper size, and will
|
||||
* do a resize+copy into that image. In the latter case, it is your responsibility
|
||||
* to make sure the output image is allocated to the proper size.
|
||||
*
|
||||
* If the output parameter is non-NULL, then the mipmapLevel is also considered.
|
||||
* This lets you resize directly into a particular mipmap level of the output image.
|
||||
*/
|
||||
static bool resizeImage(
|
||||
const osg::Image* input,
|
||||
unsigned int new_s, unsigned int new_t,
|
||||
osg::ref_ptr<osg::Image>& output,
|
||||
unsigned int mipmapLevel = 0, bool bilinear = true);
|
||||
|
||||
/**
|
||||
* Crops the input image to the dimensions provided and returns a
|
||||
* new image. Returns a new image, leaving the input image unaltered.
|
||||
* Note: The input destination bounds are modified to reflect the bounds of the
|
||||
* actual output image. Due to the fact that you cannot crop in the middle of a pixel
|
||||
* The specified destination extents and the output extents may vary slightly.
|
||||
*@param src_minx
|
||||
* The minimum x coordinate of the input image.
|
||||
*@param src_miny
|
||||
* The minimum y coordinate of the input image.
|
||||
*@param src_maxx
|
||||
* The maximum x coordinate of the input image.
|
||||
*@param src_maxy
|
||||
* The maximum y coordinate of the input image.
|
||||
*@param dst_minx
|
||||
* The desired minimum x coordinate of the cropped image.
|
||||
*@param dst_miny
|
||||
* The desired minimum y coordinate of the cropped image.
|
||||
*@param dst_maxx
|
||||
* The desired maximum x coordinate of the cropped image.
|
||||
*@param dst_maxy
|
||||
* The desired maximum y coordinate of the cropped image.
|
||||
*/
|
||||
static osg::Image* cropImage(
|
||||
const osg::Image* image,
|
||||
double src_minx, double src_miny, double src_maxx, double src_maxy,
|
||||
double &dst_minx, double &dst_miny, double &dst_maxx, double &dst_maxy);
|
||||
|
||||
/**
|
||||
* Creates an Image that "blends" two images into a new image in which "primary"
|
||||
* occupies mipmap level 0, and "secondary" occupies all the other mipmap levels.
|
||||
*
|
||||
* WARNING: this method assumes that primary and seconday are the same exact size
|
||||
* and the same exact format.
|
||||
*/
|
||||
static osg::Image* createMipmapBlendedImage(
|
||||
const osg::Image* primary,
|
||||
const osg::Image* secondary);
|
||||
|
||||
/**
|
||||
* Creates a new image containing mipmaps built with nearest-neighbor
|
||||
* sampling.
|
||||
*/
|
||||
static osg::Image* buildNearestNeighborMipmaps(
|
||||
const osg::Image* image);
|
||||
|
||||
/**
|
||||
* Blends the "src" image into the "dest" image, based on the "a" value.
|
||||
* The two images must be the same.
|
||||
*/
|
||||
static bool mix(osg::Image* dest, const osg::Image* src, float a);
|
||||
|
||||
/**
|
||||
* Creates and returns a copy of the input image after applying a
|
||||
* sharpening filter. Returns a new image, leaving the input image unaltered.
|
||||
*/
|
||||
static osg::Image* createSharpenedImage(const osg::Image* image);
|
||||
|
||||
/**
|
||||
* For each "layer" in the input image (each bitmap in the "r" dimension),
|
||||
* create a new, separate image with r=1. If the input image is r=1, it is
|
||||
* simply placed onto the output vector (no copy).
|
||||
* Returns true upon sucess, false upon failure
|
||||
*/
|
||||
static bool flattenImage(osg::Image* image, std::vector<osg::ref_ptr<osg::Image> >& output);
|
||||
|
||||
/**
|
||||
* Gets whether the input image's dimensions are powers of 2.
|
||||
*/
|
||||
static bool isPowerOfTwo(const osg::Image* image);
|
||||
|
||||
/**
|
||||
* Gets a transparent, single pixel image used for a placeholder
|
||||
*/
|
||||
static osg::Image* createEmptyImage();
|
||||
|
||||
/**
|
||||
* Gets a transparent image used for a placeholder with the specified dimensions
|
||||
*/
|
||||
static osg::Image* createEmptyImage(unsigned int s, unsigned int t);
|
||||
|
||||
/**
|
||||
* Creates a one-pixel image.
|
||||
*/
|
||||
static osg::Image* createOnePixelImage(const osg::Vec4& color);
|
||||
|
||||
/**
|
||||
* Tests an image to see whether it's "empty", i.e. completely transparent,
|
||||
* within an alpha threshold.
|
||||
*/
|
||||
static bool isEmptyImage(const osg::Image* image, float alphaThreshold = 0.01);
|
||||
|
||||
/**
|
||||
* Tests an image to see whether it's "single color", i.e. completely filled with a single color,
|
||||
* within an threshold (threshold is tested on each channel).
|
||||
*/
|
||||
static bool isSingleColorImage(const osg::Image* image, float threshold = 0.01);
|
||||
|
||||
/**
|
||||
* Returns true if it is possible to convert the image to the specified
|
||||
* format/datatype specification.
|
||||
*/
|
||||
static bool canConvert(const osg::Image* image, GLenum pixelFormat, GLenum dataType);
|
||||
|
||||
/**
|
||||
* Converts an image to the specified format.
|
||||
*/
|
||||
static osg::Image* convert(const osg::Image* image, GLenum pixelFormat, GLenum dataType);
|
||||
|
||||
/**
|
||||
*Converts the given image to RGB8
|
||||
*/
|
||||
static osg::Image* convertToRGB8(const osg::Image* image);
|
||||
|
||||
/**
|
||||
*Converts the given image to RGBA8
|
||||
*/
|
||||
static osg::Image* convertToRGBA8(const osg::Image* image);
|
||||
|
||||
/**
|
||||
* True if the two images are of the same format (pixel format, data type, etc.)
|
||||
* though not necessarily the same size, depth, etc.
|
||||
*/
|
||||
static bool sameFormat(const osg::Image* lhs, const osg::Image* rhs);
|
||||
|
||||
/**
|
||||
* True if the two images have the same format AND size, and can therefore
|
||||
* be used together in a texture array.
|
||||
*/
|
||||
static bool textureArrayCompatible(const osg::Image* lhs, const osg::Image* rhs);
|
||||
|
||||
/**
|
||||
*Compares the image data of two images and determines if they are equivalent
|
||||
*/
|
||||
static bool areEquivalent(const osg::Image *lhs, const osg::Image *rhs);
|
||||
|
||||
/**
|
||||
* Whether two colors are roughly equivalent.
|
||||
*/
|
||||
static bool areRGBEquivalent(const osg::Vec4& lhs, const osg::Vec4& rhs, float epsilon = 0.01f) {
|
||||
return
|
||||
fabs(lhs.r() - rhs.r()) < epsilon &&
|
||||
fabs(lhs.g() - rhs.g()) < epsilon &&
|
||||
fabs(lhs.b() - rhs.b()) < epsilon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the image has an alpha component
|
||||
*/
|
||||
static bool hasAlphaChannel(const osg::Image* image);
|
||||
|
||||
/**
|
||||
* Checks whether an image has transparency; i.e. whether
|
||||
* there are any pixels with an alpha component whole value
|
||||
* falls below the specified threshold.
|
||||
*/
|
||||
static bool hasTransparency(const osg::Image* image, float alphaThreshold = 1.0f);
|
||||
|
||||
/**
|
||||
* Finds pixels with alpha less than [maxAlpha] and sets their color
|
||||
* to match that or neighboring non-alpha pixels. This facilitates multipass
|
||||
* blending or abutting tiles by overlapping them slightly. Specify "maxAlpha"
|
||||
* as the maximum value to consider when searching for fully-transparent pixels.
|
||||
*
|
||||
* Returns false if there is no reader or writer for the image's format.
|
||||
*/
|
||||
static bool featherAlphaRegions(osg::Image* image, float maxAlpha = 0.0f);
|
||||
|
||||
/**
|
||||
* Converts an image (in place) to premultiplied-alpha format.
|
||||
* Returns False is the conversion fails, e.g., if there is no reader
|
||||
* or writer for the image format.
|
||||
*/
|
||||
static bool convertToPremultipliedAlpha(osg::Image* image);
|
||||
|
||||
/**
|
||||
* Checks whether the given image is compressed
|
||||
*/
|
||||
static bool isCompressed(const osg::Image* image);
|
||||
|
||||
/**
|
||||
* Generated a bump map image for the input image
|
||||
*/
|
||||
static osg::Image* createBumpMap(const osg::Image* input);
|
||||
|
||||
/**
|
||||
* Is it a floating-point texture format?
|
||||
*/
|
||||
static bool isFloatingPointInternalFormat(GLint internalFormat);
|
||||
|
||||
/**
|
||||
* Compute a texture compression format suitable for the image.
|
||||
*/
|
||||
static bool computeTextureCompressionMode(
|
||||
const osg::Image* image,
|
||||
osg::Texture::InternalFormatMode& out_mode);
|
||||
|
||||
|
||||
/**
|
||||
* Bicubic upsampling in a quadrant. Target image is already allocated.
|
||||
*/
|
||||
static bool bicubicUpsample(
|
||||
const osg::Image* source,
|
||||
osg::Image* target,
|
||||
unsigned quadrant,
|
||||
unsigned stride);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static osg::Image* upSampleNN(const osg::Image* src, int quadrant);
|
||||
|
||||
/**
|
||||
* Activates mipmapping for a texture image if the correct filters exist.
|
||||
*
|
||||
* If OSG has an ImageProcessor service installed, this method will use that
|
||||
* to generate mipmaps. If not, the method will be a NOP and the GPU wil
|
||||
* generate mipmaps (if necessary) upon GPU transfer.
|
||||
*/
|
||||
static void activateMipMaps(osg::Texture* texture);
|
||||
|
||||
/**
|
||||
* Gets an osgDB::ReaderWriter for the given input stream.
|
||||
* Returns NULL if no ReaderWriter can be found.
|
||||
*/
|
||||
static osgDB::ReaderWriter* getReaderWriterForStream(std::istream& stream);
|
||||
|
||||
/**
|
||||
* Reads an osg::Image from the given input stream.
|
||||
* Returns NULL if the image could not be read.
|
||||
*/
|
||||
static osg::Image* readStream(std::istream& stream, const osgDB::Options* options);
|
||||
|
||||
/**
|
||||
* Reads color data out of an image, regardles of its internal pixel format.
|
||||
*/
|
||||
class PixelReader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructs a pixel reader. "Normalized" means that the values in the source
|
||||
* image have been scaled to [0..1] and should be denormalized upon reading.
|
||||
*/
|
||||
PixelReader(const osg::Image* image);
|
||||
|
||||
/** Sets an image to read. */
|
||||
void setImage(const osg::Image* image);
|
||||
|
||||
/** Whether to use bilinear interpolation when reading with u,v coords (default=true) */
|
||||
void setBilinear(bool value) { _bilinear = value; }
|
||||
|
||||
/** Whether PixelReader supports a given format/datatype combiniation. */
|
||||
static bool supports(GLenum pixelFormat, GLenum dataType);
|
||||
|
||||
/** Whether PixelReader can read from the specified image. */
|
||||
static bool supports(const osg::Image* image) {
|
||||
return image && supports(image->getPixelFormat(), image->getDataType());
|
||||
}
|
||||
|
||||
/** Reads a color from the image */
|
||||
osg::Vec4 operator()(int s, int t, int r = 0, int m = 0) const {
|
||||
return (*_reader)(this, s, t, r, m);
|
||||
}
|
||||
|
||||
/** Reads a color from the image */
|
||||
osg::Vec4 operator()(unsigned s, unsigned t, unsigned r = 0, int m = 0) const {
|
||||
return (*_reader)(this, s, t, r, m);
|
||||
}
|
||||
|
||||
/** Reads a color from the image by unit coords [0..1] */
|
||||
osg::Vec4 operator()(float u, float v, int r = 0, int m = 0) const;
|
||||
osg::Vec4 operator()(double u, double v, int r = 0, int m = 0) const;
|
||||
|
||||
// internals:
|
||||
const unsigned char* data(int s = 0, int t = 0, int r = 0, int m = 0) const {
|
||||
return m == 0 ?
|
||||
_image->data() + s*_colMult + t*_rowMult + r*_imageSize :
|
||||
_image->getMipmapData(m) + s*_colMult + t*(_rowMult >> m) + r*(_imageSize >> m);
|
||||
}
|
||||
|
||||
typedef osg::Vec4(*ReaderFunc)(const PixelReader* ia, int s, int t, int r, int m);
|
||||
ReaderFunc _reader;
|
||||
const osg::Image* _image;
|
||||
unsigned _colMult;
|
||||
unsigned _rowMult;
|
||||
unsigned _imageSize;
|
||||
bool _normalized;
|
||||
bool _bilinear;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes color data to an image, regardles of its internal pixel format.
|
||||
*/
|
||||
class PixelWriter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructs a pixel writer. "Normalized" means the values are scaled to [0..1]
|
||||
* before writing.
|
||||
*/
|
||||
PixelWriter(osg::Image* image);
|
||||
|
||||
/** Whether PixelWriter can write to an image with the given format/datatype combo. */
|
||||
static bool supports(GLenum pixelFormat, GLenum dataType);
|
||||
|
||||
/** Whether PixelWriter can write to non-const version of an image. */
|
||||
static bool supports(const osg::Image* image) {
|
||||
return image && supports(image->getPixelFormat(), image->getDataType());
|
||||
}
|
||||
|
||||
/** Writes a color to a pixel. */
|
||||
void operator()(const osg::Vec4& c, int s, int t, int r = 0, int m = 0) {
|
||||
(*_writer)(this, c, s, t, r, m);
|
||||
}
|
||||
|
||||
void f(const osg::Vec4& c, float s, float t, int r = 0, int m = 0) {
|
||||
this->operator()(c,
|
||||
(int)(s * (float)(_image->s() - 1)),
|
||||
(int)(t * (float)(_image->t() - 1)),
|
||||
r, m);
|
||||
}
|
||||
|
||||
// internals:
|
||||
osg::Image* _image;
|
||||
unsigned _colMult;
|
||||
unsigned _rowMult;
|
||||
unsigned _imageSize;
|
||||
bool _normalized;
|
||||
|
||||
unsigned char* data(int s = 0, int t = 0, int r = 0, int m = 0) const {
|
||||
return m == 0 ?
|
||||
_image->data() + s*_colMult + t*_rowMult + r*_imageSize :
|
||||
_image->getMipmapData(m) + s*_colMult + t*(_rowMult >> m) + r*(_imageSize >> m);
|
||||
}
|
||||
|
||||
typedef void(*WriterFunc)(const PixelWriter* iw, const osg::Vec4& c, int s, int t, int r, int m);
|
||||
WriterFunc _writer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Functor that visits every pixel in an image
|
||||
*/
|
||||
template<typename T>
|
||||
struct PixelVisitor : public T
|
||||
{
|
||||
/**
|
||||
* Traverse an image, and call this method on the superclass:
|
||||
*
|
||||
* bool operator(osg::Vec4& pixel);
|
||||
*
|
||||
* If that method returns true, write the value back at the same location.
|
||||
*/
|
||||
void accept(osg::Image* image) {
|
||||
PixelReader _reader(image);
|
||||
PixelWriter _writer(image);
|
||||
for (int r = 0; r<image->r(); ++r) {
|
||||
for (int t = 0; t<image->t(); ++t) {
|
||||
for (int s = 0; s<image->s(); ++s) {
|
||||
osg::Vec4f pixel = _reader(s, t, r);
|
||||
if ((*this)(pixel))
|
||||
_writer(pixel, s, t, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse an image, and call this method on the superclass:
|
||||
*
|
||||
* bool operator(const osg::Vec4& srcPixel, osg::Vec4& destPixel);
|
||||
*
|
||||
* If that method returns true, write destPixel back at the same location
|
||||
* in the destination image.
|
||||
*/
|
||||
void accept(const osg::Image* src, osg::Image* dest) {
|
||||
PixelReader _readerSrc(src);
|
||||
PixelReader _readerDest(dest);
|
||||
PixelWriter _writerDest(dest);
|
||||
for (int r = 0; r<src->r(); ++r) {
|
||||
for (int t = 0; t<src->t(); ++t) {
|
||||
for (int s = 0; s<src->s(); ++s) {
|
||||
const osg::Vec4f pixelSrc = _readerSrc(s, t, r);
|
||||
osg::Vec4f pixelDest = _readerDest(s, t, r);
|
||||
if ((*this)(pixelSrc, pixelDest))
|
||||
_writerDest(pixelDest, s, t, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple functor to copy pixels from one image to another.
|
||||
*
|
||||
* Usage:
|
||||
* PixelVisitor<CopyImage>().accept( fromImage, toImage );
|
||||
*/
|
||||
struct CopyImage {
|
||||
bool operator()(const osg::Vec4f& src, osg::Vec4f& dest) {
|
||||
dest = src;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/** Visitor that finds and operates on textures and images */
|
||||
class TextureAndImageVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
TextureAndImageVisitor();
|
||||
virtual ~TextureAndImageVisitor() { }
|
||||
|
||||
public:
|
||||
/** Visits a texture and, by default, all its components images */
|
||||
virtual void apply(osg::Texture& texture);
|
||||
|
||||
/** Visits an image inside a texture */
|
||||
virtual void apply(osg::Image& image) { }
|
||||
|
||||
public: // osg::NodeVisitor
|
||||
virtual void apply(osg::Node& node);
|
||||
virtual void apply(osg::StateSet& stateSet);
|
||||
};
|
||||
}
|
||||
|
||||
#endif //SIMGEAR_IMAGEUTILS_H
|
||||
@@ -43,7 +43,11 @@ SGSceneFeatures::SGSceneFeatures() :
|
||||
_shaderLights(true),
|
||||
_pointSpriteLights(true),
|
||||
_distanceAttenuationLights(true),
|
||||
_textureFilter(1)
|
||||
_textureFilter(1),
|
||||
_MaxTextureSize(4096),
|
||||
_TextureCacheCompressionActive(true),
|
||||
_TextureCacheCompressionActiveTransparent(true),
|
||||
_TextureCacheActive(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1,102 +1,133 @@
|
||||
/* -*-c++-*-
|
||||
*
|
||||
* Copyright (C) 2006-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.
|
||||
*
|
||||
*/
|
||||
*
|
||||
* Copyright (C) 2006-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 SG_SCENE_FEATURES_HXX
|
||||
#define SG_SCENE_FEATURES_HXX
|
||||
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
#include <string>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
namespace osg { class Texture; }
|
||||
|
||||
class SGSceneFeatures : public SGReferenced {
|
||||
public:
|
||||
static SGSceneFeatures* instance();
|
||||
static SGSceneFeatures* instance();
|
||||
|
||||
enum TextureCompression {
|
||||
DoNotUseCompression,
|
||||
UseARBCompression,
|
||||
UseDXT1Compression,
|
||||
UseDXT3Compression,
|
||||
UseDXT5Compression
|
||||
};
|
||||
enum TextureCompression {
|
||||
DoNotUseCompression,
|
||||
UseARBCompression,
|
||||
UseDXT1Compression,
|
||||
UseDXT3Compression,
|
||||
UseDXT5Compression
|
||||
};
|
||||
int getMaxTextureSize() const { return _MaxTextureSize; }
|
||||
void setMaxTextureSize(const int maxTextureSize) { _MaxTextureSize = maxTextureSize; }
|
||||
|
||||
void setTextureCompression(TextureCompression textureCompression)
|
||||
{ _textureCompression = textureCompression; }
|
||||
TextureCompression getTextureCompression() const
|
||||
{ return _textureCompression; }
|
||||
void setTextureCompression(osg::Texture* texture) const;
|
||||
SGPath getTextureCompressionPath() const { return _TextureCompressionPath; }
|
||||
void setTextureCompressionPath(const SGPath path) { _TextureCompressionPath = path; }
|
||||
|
||||
void setEnablePointSpriteLights(bool enable)
|
||||
{ _pointSpriteLights = enable; }
|
||||
bool getEnablePointSpriteLights() const
|
||||
{
|
||||
return _pointSpriteLights;
|
||||
}
|
||||
bool getEnablePointSpriteLights(unsigned contextId) const
|
||||
{
|
||||
if (!_pointSpriteLights)
|
||||
return false;
|
||||
return getHavePointSprites(contextId);
|
||||
}
|
||||
bool getTextureCacheActive() const { return _TextureCacheActive; }
|
||||
void setTextureCacheActive(const bool val) { _TextureCacheActive = val; }
|
||||
|
||||
void setEnableDistanceAttenuationLights(bool enable)
|
||||
{ _distanceAttenuationLights = enable; }
|
||||
bool getEnableDistanceAttenuationLights(unsigned contextId) const
|
||||
{
|
||||
if (!_distanceAttenuationLights)
|
||||
return false;
|
||||
return getHavePointParameters(contextId);
|
||||
}
|
||||
bool getTextureCacheCompressionActive() const { return _TextureCacheCompressionActive; }
|
||||
void setTextureCacheCompressionActive(const bool val) { _TextureCacheCompressionActive = val; }
|
||||
|
||||
void setEnableShaderLights(bool enable)
|
||||
{ _shaderLights = enable; }
|
||||
bool getEnableShaderLights(unsigned contextId) const
|
||||
{
|
||||
if (!_shaderLights)
|
||||
return false;
|
||||
return getHaveShaderPrograms(contextId);
|
||||
}
|
||||
|
||||
void setTextureFilter(int max)
|
||||
{ _textureFilter = max; }
|
||||
int getTextureFilter() const
|
||||
{ return _textureFilter; }
|
||||
bool getTextureCacheCompressionActiveTransparent() const { return _TextureCacheCompressionActiveTransparent; }
|
||||
void setTextureCacheCompressionActiveTransparent(const bool val) { _TextureCacheCompressionActiveTransparent = val; }
|
||||
|
||||
void setTextureCompression(TextureCompression textureCompression) { _textureCompression = textureCompression; }
|
||||
TextureCompression getTextureCompression() const { return _textureCompression; }
|
||||
|
||||
// modify the texture compression on the texture parameter
|
||||
void setTextureCompression(osg::Texture* texture) const;
|
||||
|
||||
void setEnablePointSpriteLights(bool enable)
|
||||
{
|
||||
_pointSpriteLights = enable;
|
||||
}
|
||||
bool getEnablePointSpriteLights() const
|
||||
{
|
||||
return _pointSpriteLights;
|
||||
}
|
||||
bool getEnablePointSpriteLights(unsigned contextId) const
|
||||
{
|
||||
if (!_pointSpriteLights)
|
||||
return false;
|
||||
return getHavePointSprites(contextId);
|
||||
}
|
||||
|
||||
void setEnableDistanceAttenuationLights(bool enable)
|
||||
{
|
||||
_distanceAttenuationLights = enable;
|
||||
}
|
||||
bool getEnableDistanceAttenuationLights(unsigned contextId) const
|
||||
{
|
||||
if (!_distanceAttenuationLights)
|
||||
return false;
|
||||
return getHavePointParameters(contextId);
|
||||
}
|
||||
|
||||
void setEnableShaderLights(bool enable)
|
||||
{
|
||||
_shaderLights = enable;
|
||||
}
|
||||
bool getEnableShaderLights(unsigned contextId) const
|
||||
{
|
||||
if (!_shaderLights)
|
||||
return false;
|
||||
return getHaveShaderPrograms(contextId);
|
||||
}
|
||||
|
||||
void setTextureFilter(int max)
|
||||
{
|
||||
_textureFilter = max;
|
||||
}
|
||||
int getTextureFilter() const
|
||||
{
|
||||
return _textureFilter;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool getHavePointSprites(unsigned contextId) const;
|
||||
bool getHaveFragmentPrograms(unsigned contextId) const;
|
||||
bool getHaveVertexPrograms(unsigned contextId) const;
|
||||
bool getHaveShaderPrograms(unsigned contextId) const;
|
||||
bool getHavePointParameters(unsigned contextId) const;
|
||||
bool getHavePointSprites(unsigned contextId) const;
|
||||
bool getHaveFragmentPrograms(unsigned contextId) const;
|
||||
bool getHaveVertexPrograms(unsigned contextId) const;
|
||||
bool getHaveShaderPrograms(unsigned contextId) const;
|
||||
bool getHavePointParameters(unsigned contextId) const;
|
||||
|
||||
private:
|
||||
SGSceneFeatures();
|
||||
SGSceneFeatures(const SGSceneFeatures&);
|
||||
SGSceneFeatures& operator=(const SGSceneFeatures&);
|
||||
SGSceneFeatures();
|
||||
SGSceneFeatures(const SGSceneFeatures&);
|
||||
SGSceneFeatures& operator=(const SGSceneFeatures&);
|
||||
|
||||
TextureCompression _textureCompression;
|
||||
bool _shaderLights;
|
||||
bool _pointSpriteLights;
|
||||
bool _distanceAttenuationLights;
|
||||
int _textureFilter;
|
||||
TextureCompression _textureCompression;
|
||||
int _MaxTextureSize;
|
||||
SGPath _TextureCompressionPath;
|
||||
bool _TextureCacheCompressionActive;
|
||||
bool _TextureCacheCompressionActiveTransparent;
|
||||
bool _TextureCacheActive;
|
||||
bool _shaderLights;
|
||||
bool _pointSpriteLights;
|
||||
bool _distanceAttenuationLights;
|
||||
int _textureFilter;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user