Files
OpenSceneGraph/src/osgPlugins/osgjs/WriteVisitor
Marc Helbling c84b667fa5 Updates osgjs from sketchfab
* updates uservalue serialization (avoid creating multie UserDataContainer for a same object)
* removes vec4ubarray specific serialization (serialization should not enforce the previous color transformation)
2017-01-20 15:17:16 +01:00

719 lines
26 KiB
C++

/* -*-c++-*- OpenSceneGraph - Copyright (C) 2012 Cedric Pinson */
#ifndef WRITE_VISITOR_H
#define WRITE_VISITOR_H
#include <osg/Image>
#include <osg/Notify>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/GL>
#include <osg/Version>
#include <osg/Endian>
#include <osg/Projection>
#include <osg/MatrixTransform>
#include <osg/PagedLOD>
#include <osg/PositionAttitudeTransform>
#include <osgAnimation/BasicAnimationManager>
#include <osgAnimation/Skeleton>
#include <osg/LightSource>
#include <osg/CullFace>
#include <osg/Material>
#include <osg/BlendColor>
#include <osg/BlendFunc>
#include <osg/ValueObject>
#include <osg/Array>
#include <osgDB/FileNameUtils>
#include <osgAnimation/RigGeometry>
#include <osgAnimation/MorphGeometry>
#include <osgAnimation/Bone>
#include <osgAnimation/UpdateBone>
#include <osgSim/ShapeAttribute>
#include <osgText/Text>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <string>
#include <algorithm>
#include <sstream>
#include "JSON_Objects"
#include "Animation"
#include "json_stream"
#define WRITER_VERSION 9
class WriteVisitor;
osg::Array* getTangentSpaceArray(osg::Geometry& geometry);
void getStringifiedUserValue(osg::Object* o, std::string& name, std::string& value);
template<typename T>
bool getStringifiedUserValue(osg::Object* o, std::string& name, std::string& value);
class WriteVisitor : public osg::NodeVisitor
{
public:
typedef std::vector<osg::ref_ptr<osg::StateSet> > StateSetStack;
typedef std::pair<std::string, std::string> KeyValue;
typedef std::map<osg::ref_ptr<osg::Object>, osg::ref_ptr<JSONObject> > OsgObjectToJSONObject;
OsgObjectToJSONObject _maps;
std::vector<osg::ref_ptr<JSONObject> > _parents;
osg::ref_ptr<JSONObject> _root;
StateSetStack _stateset;
std::string _baseName;
std::string _baseLodURL;
bool _useExternalBinaryArray;
bool _mergeAllBinaryFiles;
bool _inlineImages;
int _maxTextureDimension;
bool _varint;
std::map<KeyValue, std::string> _specificBuffers;
std::map<std::string, std::ofstream*> _buffers;
JSONObject* getJSON(osg::Object* object) const {
OsgObjectToJSONObject::const_iterator lookup = _maps.find(object);
if(lookup != _maps.end()) {
return lookup->second->getShadowObject();
}
return 0;
}
void setJSON(osg::Object* object, JSONObject* json) {
if(json) {
_maps[object] = json;
}
}
std::ofstream& getBufferFile(const std::string& name) {
if(_buffers.find(name) == _buffers.end()) {
_buffers[name] = new std::ofstream(name.c_str(), std::ios::binary);
}
return *_buffers[name];
}
WriteVisitor() :
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
_useExternalBinaryArray(false),
_mergeAllBinaryFiles(false),
_inlineImages(false),
_maxTextureDimension(0),
_varint(false)
{}
~WriteVisitor() {
for(std::map<std::string, std::ofstream*>::iterator buffer = _buffers.begin() ;
buffer != _buffers.end() ; ++ buffer) {
delete buffer->second;
}
}
std::string getBinaryFilename(KeyValue flag=KeyValue()) const {
std::string suffix,
prefix(_baseName);
std::map<KeyValue, std::string>::const_iterator it_buffer = _specificBuffers.find(flag);
if(it_buffer != _specificBuffers.end()) {
if(osgDB::isAbsolutePath(it_buffer->second)) {
// if output is explicit, do not change it
return it_buffer->second;
}
else {
suffix = "_" + it_buffer->second;
}
}
return prefix + suffix + ".bin";
}
void closeBuffers() {
for(std::map<std::string, std::ofstream*>::iterator buffer = _buffers.begin() ;
buffer != _buffers.end() ; ++ buffer) {
buffer->second->close();
}
}
unsigned int getBuffersSize() const {
unsigned int size = 0;
for(std::map<std::string, std::ofstream*>::const_iterator buffer = _buffers.begin() ;
buffer != _buffers.end() ; ++ buffer) {
size += buffer->second->tellp();
}
return size;
}
void write(json_stream& str) {
osg::ref_ptr<JSONObject> o = new JSONObject();
o->getMaps()["Version"] = new JSONValue<int>(WRITER_VERSION);
o->getMaps()["Generator"] = new JSONValue<std::string>("OpenSceneGraph " + std::string(osgGetVersion()) );
o->getMaps()["osg.Node"] = _root.get();
o->write(str, *this);
if (_mergeAllBinaryFiles) {
closeBuffers();
unsigned int size = getBuffersSize();
osg::notify(osg::NOTICE) << "Use a merged binary file ";
if (size/1024.0 < 1.0) {
osg::notify(osg::NOTICE) << size << " bytes" << std::endl;
} else if (size/(1024.0*1024.0) < 1.0) {
osg::notify(osg::NOTICE) << size/1024.0 << " kb" << std::endl;
} else {
osg::notify(osg::NOTICE) << size/(1024.0*1024.0) << " mb" << std::endl;
}
}
}
void error() {
throw "Error occur";
}
void setBufferName(JSONObject *json, osg::Object* parent=0, osg::Object* object=0) const {
if(!_mergeAllBinaryFiles || _specificBuffers.empty())
return;
// try to fetch buffer name for object
std::string bufferName = getBufferName(object);
std::string defaultBufferName = getBinaryFilename();
std::string jsonBufferName = json->getBufferName();
if(bufferName == defaultBufferName) {
// in case none is set, fallback to parent buffer name
bufferName = getBufferName(parent);
}
// if the buffer is shared we will always favor dumping it in the default
// buffer and otherwise we keep the first buffer name set.
if(!jsonBufferName.empty()) {
if(jsonBufferName != defaultBufferName && bufferName == defaultBufferName) {
json->setBufferName(defaultBufferName);
}
}
else {
json->setBufferName(bufferName);
}
}
std::string getBufferName(osg::Object* object) const {
KeyValue flag;
if(object && object->getUserDataContainer() && object->getUserDataContainer()->getNumUserObjects()) {
for(std::map<KeyValue, std::string>::const_iterator it_flag = _specificBuffers.begin() ; it_flag != _specificBuffers.end() ; ++ it_flag) {
std::string key = it_flag->first.first,
value = it_flag->first.second;
// only looking for existence of user value
if(value.empty()) {
if(object->getUserDataContainer()->getUserObject(key)) {
flag = it_flag->first;
break;
}
}
else {
std::set<std::string> uservalues;
bool boolValue;
int numberValue;
unsigned int uNumberValue;
std::string stringValue;
if(object->getUserValue(key, boolValue)) {
std::ostringstream oss;
if(boolValue) {
uservalues.insert("1");
uservalues.insert("true");
}
else {
uservalues.insert("0");
uservalues.insert("false");
}
}
if(object->getUserValue(key, numberValue)) {
std::ostringstream oss;
oss << numberValue;
uservalues.insert(oss.str());
}
if(object->getUserValue(key, uNumberValue)) {
std::ostringstream oss;
oss << uNumberValue;
uservalues.insert(oss.str());
}
if(object->getUserValue(key, stringValue)) {
uservalues.insert(stringValue);
}
if(uservalues.find(value) != uservalues.end()) {
flag = it_flag->first;
break;
}
}
}
}
return getBinaryFilename(flag);
}
void translateObject(JSONObject* json, osg::Object* osg);
JSONObject* createJSONOsgSimUserData(osgSim::ShapeAttributeList*);
JSONObject* createJSONUserDataContainer(osg::UserDataContainer*);
JSONObject* createJSONPagedLOD(osg::PagedLOD* plod);
JSONObject* createJSONStateSet(osg::StateSet* ss);
JSONObject* createJSONTexture(osg::Texture* sa);
JSONObject* createJSONText(osgText::Text* text);
JSONObject* createJSONMaterial(osg::Material* sa);
JSONObject* createJSONLight(osg::Light* sa);
JSONObject* createJSONCullFace(osg::CullFace* sa);
JSONObject* createJSONBlendColor(osg::BlendColor* sa);
JSONObject* createJSONBlendFunc(osg::BlendFunc* sa);
JSONObject* createJSONBufferArray(osg::Array* array, osg::Object* parent = 0);
JSONObject* createJSONDrawElements(osg::DrawArrays* drawArray, osg::Object* parent = 0);
JSONObject* createJSONDrawElementsUInt(osg::DrawElementsUInt* de, osg::Object* parent = 0);
JSONObject* createJSONDrawElementsUShort(osg::DrawElementsUShort* de, osg::Object* parent = 0);
JSONObject* createJSONDrawElementsUByte(osg::DrawElementsUByte* de, osg::Object* parent = 0);
JSONObject* createJSONDrawArray(osg::DrawArrays* drawArray, osg::Object* parent = 0);
JSONObject* createJSONDrawArrayLengths(osg::DrawArrayLengths* drawArray, osg::Object* parent = 0);
JSONObject* createJSONGeometry(osg::Geometry* geometry, osg::Object* parent=0);
JSONObject* createJSONRigGeometry(osgAnimation::RigGeometry* rigGeometry);
JSONObject* createJSONMorphGeometry(osgAnimation::MorphGeometry* morphGeom, osg::Object* parent=0);
JSONObject* getParent() {
if (_parents.empty()) {
_root = new JSONObject;
_parents.push_back(_root.get());
}
return _parents.back().get();
}
void initJsonObjectFromNode(osg::Node& node, JSONObject& json) {
translateObject(&json, &node);
}
void createJSONStateSet(JSONObject* json, osg::StateSet* ss) {
JSONObject* json_stateset = createJSONStateSet(ss);
if (json_stateset) {
JSONObject* obj = new JSONObject;
obj->getMaps()["osg.StateSet"] = json_stateset;
json->getMaps()["StateSet"] = obj;
}
}
void createJSONStateSet(osg::Node& node, JSONObject* json) {
if (node.getStateSet()) {
createJSONStateSet(json, node.getStateSet());
}
}
void applyCallback(osg::Node& node, JSONObject* in_json) {
osg::ref_ptr<JSONArray> updateCallbacks = new JSONArray;
osg::ref_ptr<osg::Callback> nc = node.getUpdateCallback();
while (nc) {
if(osgAnimation::BasicAnimationManager* am = dynamic_cast<osgAnimation::BasicAnimationManager*>(nc.get())) {
osg::ref_ptr<JSONArray> array = new JSONArray;
osg::ref_ptr<JSONObject> bam = new JSONObject;
translateObject(bam.get(), am);
bam->getMaps()["Animations"] = array;
osg::ref_ptr<JSONObject> nodeCallbackObject = new JSONObject;
nodeCallbackObject->getMaps()["osgAnimation.BasicAnimationManager"] = bam;
updateCallbacks->getArray().push_back(nodeCallbackObject);
for ( unsigned int i = 0; i < am->getAnimationList().size(); i++) {
osgAnimation::Animation* animation = am->getAnimationList()[i].get();
osg::ref_ptr<JSONObject> jsonAnim = createJSONAnimation(animation, this);
if (jsonAnim) {
translateObject(jsonAnim.get(), animation);
osg::ref_ptr<JSONObject> obj = new JSONObject;
obj->getMaps()["osgAnimation.Animation"] = jsonAnim;
array->getArray().push_back(obj);
}
}
}
else if(osgAnimation::UpdateBone* upBone = dynamic_cast<osgAnimation::UpdateBone*>(nc.get())) {
osg::ref_ptr<JSONObject> jsonCallback = createJSONUpdateMatrixTransform(*upBone, this);
if (jsonCallback.valid()) {
osg::ref_ptr<JSONObject> jsonObject = new JSONObject;
jsonObject->getMaps()["osgAnimation.UpdateBone"] = jsonCallback;
updateCallbacks->getArray().push_back(jsonObject);
}
}
else if(osgAnimation::UpdateMatrixTransform* updateMT = dynamic_cast<osgAnimation::UpdateMatrixTransform*>(nc.get())) {
osg::ref_ptr<JSONObject> jsonCallback = createJSONUpdateMatrixTransform(*updateMT, this);
if (jsonCallback.valid()) {
osg::ref_ptr<JSONObject> jsonObject = new JSONObject;
jsonObject->getMaps()["osgAnimation.UpdateMatrixTransform"] = jsonCallback;
updateCallbacks->getArray().push_back(jsonObject);
}
}
else if(dynamic_cast<osgAnimation::Skeleton::UpdateSkeleton*>(nc.get())) {
osg::ref_ptr<JSONObject> json = new JSONNode;
osg::ref_ptr<JSONObject> jsonObject = new JSONObject;
_maps[&node] = json;
jsonObject->getMaps()["osgAnimation.UpdateSkeleton"] = json;
updateCallbacks->getArray().push_back(jsonObject);
}
else if(osgAnimation::UpdateMorph* updateMorph = dynamic_cast<osgAnimation::UpdateMorph*>(nc.get())) {
osg::ref_ptr<JSONObject> json = new JSONNode;
osg::ref_ptr<JSONObject> jsonObject = new JSONObject;
osg::ref_ptr<JSONObject> jsonTargetName = new JSONObject;
json->getMaps()["Name"] = new JSONValue<std::string>(updateMorph->getName());
unsigned int numTarget = updateMorph->getNumTarget();
for(unsigned int i = 0; i < numTarget; ++i) {
std::ostringstream oss;
oss << i;
jsonTargetName->getMaps()[ oss.str() ] = new JSONValue<std::string>(updateMorph->getTargetName(i));
}
json->getMaps()["TargetMap"] = jsonTargetName;
json->getMaps()["TargetMap"] = jsonTargetName;
_maps[&node] = json;
jsonObject->getMaps()["osgAnimation.UpdateMorph"] = json;
updateCallbacks->getArray().push_back(jsonObject);
}
nc = nc->getNestedCallback();
}
if (!updateCallbacks->getArray().empty()) {
in_json->getMaps()["UpdateCallbacks"] = updateCallbacks;
}
}
void apply(osg::Drawable& drawable) {
if(osgAnimation::RigGeometry * rigGeometry = dynamic_cast<osgAnimation::RigGeometry*>(&drawable)) {
JSONObject* json = createJSONRigGeometry(rigGeometry);
translateObject(json, rigGeometry);
JSONObject* parent = getParent();
parent->addChild("osgAnimation.RigGeometry", json);
}
else if(osgAnimation::MorphGeometry * morphGeometry = dynamic_cast<osgAnimation::MorphGeometry*>(&drawable)) {
JSONObject* json = createJSONMorphGeometry(morphGeometry);
JSONObject* parent = getParent();
parent->addChild("osgAnimation.MorphGeometry", json);
}
else if(osg::Geometry* geometry = dynamic_cast<osg::Geometry*>(&drawable)) {
JSONObject* json = createJSONGeometry(geometry);
JSONObject* parent = getParent();
parent->addChild("osg.Geometry", json);
}
else if(osgText::Text* text = dynamic_cast<osgText::Text*>(&drawable)) {
JSONObject* json = createJSONText(text);
JSONObject* parent = getParent();
parent->addChild("osgText.Text", json);
}
}
void apply(osg::Geode& node) {
JSONObject* parent = getParent();
if (_maps.find(&node) != _maps.end()) {
parent->addChild("osg.Node", _maps[&node]->getShadowObject());
return;
}
osg::ref_ptr<JSONObject> json = new JSONNode;
_maps[&node] = json;
applyCallback(node, json.get());
createJSONStateSet(node, json.get());
parent->addChild("osg.Node", json.get());
initJsonObjectFromNode(node, *json);
_parents.push_back(json);
for (unsigned int i = 0; i < node.getNumDrawables(); ++i) {
if (node.getDrawable(i))
apply(*node.getDrawable(i));
}
_parents.pop_back();
}
void apply(osg::Group& node) {
JSONObject* parent = getParent();
if (_maps.find(&node) != _maps.end()) {
parent->addChild("osg.Node", _maps[&node]->getShadowObject());
return;
}
osg::ref_ptr<JSONObject> json = new JSONNode;
_maps[&node] = json;
parent->addChild("osg.Node", json.get());
applyCallback(node, json.get());
createJSONStateSet(node, json.get());
initJsonObjectFromNode(node, *json);
_parents.push_back(json);
traverse(node);
_parents.pop_back();
}
void apply(osg::PagedLOD& node)
{
JSONObject* parent = getParent();
if (_maps.find(&node) != _maps.end()) {
parent->addChild("osg.PagedLOD", _maps[&node]->getShadowObject());
return;
}
osg::ref_ptr<JSONObject> json = createJSONPagedLOD(&node);
json->addUniqueID();
_maps[&node] = json;
parent->addChild("osg.PagedLOD", json.get());
applyCallback(node, json.get());
createJSONStateSet(node, json.get());
initJsonObjectFromNode(node, *json);
_parents.push_back(json);
traverse(node);
_parents.pop_back();
}
void apply(osg::LightSource& node) {
JSONObject* parent = getParent();
if (_maps.find(&node) != _maps.end()) {
parent->addChild("osg.LightSource", _maps[&node]->getShadowObject());
return;
}
osg::ref_ptr<JSONObject> json = new JSONNode;
_maps[&node] = json;
applyCallback(node, json.get());
createJSONStateSet(node, json.get());
parent->addChild("osg.LightSource", json.get());
initJsonObjectFromNode(node, *json);
if (node.getLight()) {
JSONObject* obj = new JSONObject;
obj->getMaps()["osg.Light"] = createJSONLight(node.getLight());
json->getMaps()["Light"] = obj;
}
_parents.push_back(json);
traverse(node);
_parents.pop_back();
}
void apply(osg::Projection& node) {
JSONObject* parent = getParent();
if (_maps.find(&node) != _maps.end()) {
parent->addChild("osg.Projection", _maps[&node]->getShadowObject());
return;
}
osg::ref_ptr<JSONObject> json = new JSONNode;
_maps[&node] = json;
applyCallback(node, json.get());
createJSONStateSet(node, json.get());
parent->addChild("osg.Projection", json.get());
initJsonObjectFromNode(node, *json);
json->getMaps()["Matrix"] = new JSONMatrix(node.getMatrix());
_parents.push_back(json);
traverse(node);
_parents.pop_back();
}
void applyCommonMatrixTransform(const char * jsClassName, osg::ref_ptr<JSONObject> &json, osg::MatrixTransform &node, JSONObject* parent) {
json->addUniqueID();
_maps[&node] = json;
applyCallback(node, json.get());
createJSONStateSet(node, json.get());
parent->addChild(jsClassName, json.get());
initJsonObjectFromNode(node, *json);
json->getMaps()["Matrix"] = new JSONMatrix(node.getMatrix());
}
void apply(osgText::Text& node) {
JSONObject* parent = getParent();
if (_maps.find(&node) != _maps.end()) {
parent->addChild("osgText.Text", _maps[&node]->getShadowObject());
return;
}
osg::ref_ptr<JSONObject> json = createJSONText(&node);
json->addUniqueID();
_maps[&node] = json.get();
parent->addChild("osgText.Text", json.get());
applyCallback(node, json.get());
createJSONStateSet(node, json.get());
initJsonObjectFromNode(node, *json);
_parents.push_back(json);
traverse(node);
_parents.pop_back();
}
void apply(osg::MatrixTransform& node) {
if (dynamic_cast<osgAnimation::Skeleton*>(&node)) {
apply(static_cast<osgAnimation::Skeleton&>(node));
return;
}
if (dynamic_cast<osgAnimation::Bone*>(&node)) {
apply(static_cast<osgAnimation::Bone&>(node));
return;
}
JSONObject* parent = getParent();
if (_maps.find(&node) != _maps.end()) {
parent->addChild("osg.MatrixTransform", _maps[&node]->getShadowObject());
return;
}
osg::ref_ptr<JSONObject> json = new JSONNode;
_maps[&node] = json;
applyCallback(node, json.get());
createJSONStateSet(node, json.get());
parent->addChild("osg.MatrixTransform", json.get());
initJsonObjectFromNode(node, *json);
json->getMaps()["Matrix"] = new JSONMatrix(node.getMatrix());
_parents.push_back(json);
traverse(node);
_parents.pop_back();
}
void apply(osgAnimation::Skeleton& node) {
JSONObject* parent = getParent();
if (_maps.find(&node) != _maps.end()) {
parent->addChild("osgAnimation.Skeleton", _maps[&node]->getShadowObject());
return;
}
osg::ref_ptr<JSONObject> json = new JSONNode;
applyCommonMatrixTransform("osgAnimation.Skeleton", json, node, parent);
_parents.push_back(json);
traverse(node);
_parents.pop_back();
}
void apply(osgAnimation::Bone &node) {
JSONObject* parent = getParent();
if (_maps.find(&node) != _maps.end()) {
parent->addChild("osgAnimation.Bone", _maps[&node]->getShadowObject());
return;
}
osg::ref_ptr<JSONObject> json = new JSONNode;
osg::Vec3 min(0,0,0), max(0,0,0);
osg::ref_ptr<JSONObject> bboxData = new JSONObject;
bool hasBoundindBox = ( node.getUserValue("AABBonBone_min", min) && node.getUserValue("AABBonBone_max", max) );
if (hasBoundindBox) {
unsigned int idxmin = node.getUserDataContainer()->getUserObjectIndex("AABBonBone_min");
node.getUserDataContainer()->removeUserObject(idxmin);
unsigned int idxmax = node.getUserDataContainer()->getUserObjectIndex("AABBonBone_max");
node.getUserDataContainer()->removeUserObject(idxmax);
if(node.getUserDataContainer() && node.getUserDataContainer()->getNumUserObjects() == 0)
node.setUserDataContainer(NULL);
bboxData->getMaps()["min"] = new JSONVec3Array(min);
bboxData->getMaps()["max"] = new JSONVec3Array(max);
json->getMaps()["BoundingBox"] = bboxData;
}
json->getMaps()["InvBindMatrixInSkeletonSpace"] = new JSONMatrix(node.getInvBindMatrixInSkeletonSpace());
applyCommonMatrixTransform("osgAnimation.Bone", json, node, parent);
_parents.push_back(json);
traverse(node);
_parents.pop_back();
}
void apply(osg::PositionAttitudeTransform& node)
{
JSONObject* parent = getParent();
if (_maps.find(&node) != _maps.end()) {
parent->addChild("osg.MatrixTransform", _maps[&node]->getShadowObject());
return;
}
osg::ref_ptr<JSONObject> json = new JSONNode;
_maps[&node] = json;
applyCallback(node, json.get());
createJSONStateSet(node, json.get());
parent->addChild("osg.MatrixTransform", json.get());
initJsonObjectFromNode(node, *json);
osg::Matrix matrix = osg::Matrix::identity();
node.computeLocalToWorldMatrix(matrix,0);
json->getMaps()["Matrix"] = new JSONMatrix(matrix);
_parents.push_back(json);
traverse(node);
_parents.pop_back();
}
std::string getBaseName() const { return _baseName; }
bool getInlineImages() const { return _inlineImages; }
int getMaxTextureDimension() const { return _maxTextureDimension; }
void setBaseName(const std::string& basename) { _baseName = basename; }
void useExternalBinaryArray(bool use) { _useExternalBinaryArray = use; }
void mergeAllBinaryFiles(bool use) { _mergeAllBinaryFiles = use; }
void setInlineImages(bool use) { _inlineImages = use; }
void setVarint(bool use) { _varint = use; }
void setMaxTextureDimension(int use) { _maxTextureDimension = use; }
void addSpecificBuffer(const std::string& bufferFlag) {
if(bufferFlag.empty()) {
return;
}
std::string key, value, buffer;
size_t equal = bufferFlag.find("="),
colon = bufferFlag.find(":");
key = bufferFlag.substr(0, std::min(equal, colon));
if(equal != std::string::npos) {
if(colon == std::string::npos) {
value = bufferFlag.substr(equal + 1, std::string::npos);
}
else {
value = bufferFlag.substr(equal + 1, colon - equal - 1);
}
}
if(colon != std::string::npos) {
buffer = bufferFlag.substr(colon + 1, std::string::npos);
}
else {
buffer = key;
}
// buffer name should be lowercase
std::transform(buffer.begin(), buffer.end(), buffer.begin(), ::tolower);
_specificBuffers[KeyValue(key, value)] = buffer;
}
void setBaseLodURL(const std::string& baseLodURL) { _baseLodURL = baseLodURL; }
};
#endif