1 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph 2 * Copyright 2019 Pelican Mapping 3 * http://osgearth.org 4 * 5 * osgEarth is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Lesser General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 14 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 15 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 16 * IN THE SOFTWARE. 17 * 18 * You should have received a copy of the GNU Lesser General Public License 19 * along with this program. If not, see <http://www.gnu.org/licenses/> 20 */ 21 #ifndef OSGEARTH_GLTF_WRITER_H 22 #define OSGEARTH_GLTF_WRITER_H 23 24 #include <osg/Node> 25 #include <osg/Geometry> 26 #include <osg/MatrixTransform> 27 #include <osgDB/FileNameUtils> 28 #include <osgDB/ReaderWriter> 29 #include <osgEarth/Notify> 30 #include <osgEarth/StringUtils> 31 #include <stack> 32 33 using namespace osgEarth; 34 35 #undef LC 36 #define LC "[GLTFWriter] " 37 38 //! Visitor that builds a GLTF data model from an OSG scene graph. 39 class OSGtoGLTF : public osg::NodeVisitor 40 { 41 private: 42 typedef std::map<const osg::Node*, int> OsgNodeSequenceMap; 43 typedef std::map<const osg::BufferData*, int> ArraySequenceMap; 44 typedef std::map<const osg::Array*, int> AccessorSequenceMap; 45 46 tinygltf::Model& _model; 47 std::stack<tinygltf::Node*> _gltfNodeStack; 48 OsgNodeSequenceMap _osgNodeSeqMap; 49 ArraySequenceMap _buffers; 50 ArraySequenceMap _bufferViews; 51 ArraySequenceMap _accessors; 52 53 public: OSGtoGLTF(tinygltf::Model & model)54 OSGtoGLTF(tinygltf::Model& model) : _model(model) 55 { 56 setTraversalMode(TRAVERSE_ALL_CHILDREN); 57 setNodeMaskOverride(~0); 58 59 // default root scene: 60 _model.scenes.push_back(tinygltf::Scene()); 61 tinygltf::Scene& scene = _model.scenes.back(); 62 _model.defaultScene = 0; 63 } 64 push(tinygltf::Node & gnode)65 void push(tinygltf::Node& gnode) 66 { 67 _gltfNodeStack.push(&gnode); 68 } 69 pop()70 void pop() 71 { 72 _gltfNodeStack.pop(); 73 } 74 apply(osg::Node & node)75 void apply(osg::Node& node) 76 { 77 bool isRoot = _model.scenes[_model.defaultScene].nodes.empty(); 78 if (isRoot) 79 { 80 // put a placeholder here just to prevent any other nodes 81 // from thinking they are the root 82 _model.scenes[_model.defaultScene].nodes.push_back(-1); 83 } 84 85 traverse(node); 86 87 _model.nodes.push_back(tinygltf::Node()); 88 tinygltf::Node& gnode = _model.nodes.back(); 89 int id = _model.nodes.size() - 1; 90 gnode.name = Stringify() << "_gltfNode_" << id; 91 _osgNodeSeqMap[&node] = id; 92 93 if (isRoot) 94 { 95 // replace the placeholder with the actual root id. 96 _model.scenes[_model.defaultScene].nodes.back() = id; 97 } 98 } 99 apply(osg::Group & group)100 void apply(osg::Group& group) 101 { 102 apply(static_cast<osg::Node&>(group)); 103 104 for (unsigned i = 0; i < group.getNumChildren(); ++i) 105 { 106 int id = _osgNodeSeqMap[group.getChild(i)]; 107 _model.nodes.back().children.push_back(id); 108 } 109 } 110 apply(osg::Transform & xform)111 void apply(osg::Transform& xform) 112 { 113 apply(static_cast<osg::Group&>(xform)); 114 115 osg::Matrix matrix; 116 xform.computeLocalToWorldMatrix(matrix, this); 117 const double* ptr = matrix.ptr(); 118 for (unsigned i = 0; i < 16; ++i) 119 _model.nodes.back().matrix.push_back(*ptr++); 120 } 121 getBytesInDataType(GLenum dataType)122 unsigned getBytesInDataType(GLenum dataType) 123 { 124 return 125 dataType == GL_BYTE || dataType == GL_UNSIGNED_BYTE ? 1 : 126 dataType == GL_SHORT || dataType == GL_UNSIGNED_SHORT ? 2 : 127 dataType == GL_INT || dataType == GL_UNSIGNED_INT || dataType == GL_FLOAT ? 4 : 128 0; 129 } 130 getBytesPerElement(const osg::Array * data)131 unsigned getBytesPerElement(const osg::Array* data) 132 { 133 return data->getDataSize() * getBytesInDataType(data->getDataType()); 134 } 135 getBytesPerElement(const osg::DrawElements * data)136 unsigned getBytesPerElement(const osg::DrawElements* data) 137 { 138 return 139 dynamic_cast<const osg::DrawElementsUByte*>(data) ? 1 : 140 dynamic_cast<const osg::DrawElementsUShort*>(data) ? 2 : 141 4; 142 } 143 getOrCreateBuffer(const osg::BufferData * data,GLenum type)144 int getOrCreateBuffer(const osg::BufferData* data, GLenum type) 145 { 146 ArraySequenceMap::iterator a = _buffers.find(data); 147 if (a != _buffers.end()) 148 return a->second; 149 150 _model.buffers.push_back(tinygltf::Buffer()); 151 tinygltf::Buffer& buffer = _model.buffers.back(); 152 int id = _model.buffers.size() - 1; 153 _buffers[data] = id; 154 155 int bytes = getBytesInDataType(type); 156 buffer.data.resize(data->getTotalDataSize()); 157 158 //TODO: account for endianess 159 unsigned char* ptr = (unsigned char*)(data->getDataPointer()); 160 for (unsigned i = 0; i < data->getTotalDataSize(); ++i) 161 buffer.data[i] = *ptr++; 162 163 return id; 164 } 165 getOrCreateBufferView(const osg::BufferData * data,GLenum type,GLenum target)166 int getOrCreateBufferView(const osg::BufferData* data, GLenum type, GLenum target) 167 { 168 ArraySequenceMap::iterator a = _bufferViews.find(data); 169 if (a != _bufferViews.end()) 170 return a->second; 171 172 int bufferId = -1; 173 ArraySequenceMap::iterator buffersIter = _buffers.find(data); 174 if (buffersIter != _buffers.end()) 175 bufferId = buffersIter->second; 176 else 177 bufferId = getOrCreateBuffer(data, type); 178 179 _model.bufferViews.push_back(tinygltf::BufferView()); 180 tinygltf::BufferView& bv = _model.bufferViews.back(); 181 int id = _model.bufferViews.size() - 1; 182 _bufferViews[data] = id; 183 184 bv.buffer = bufferId; 185 bv.byteLength = data->getTotalDataSize(); 186 bv.byteOffset = 0; 187 bv.target = target; 188 189 //ONLY used for vertex attrbs, I guess: 190 //unsigned bytesPerComponent = getBytesPerComponent(data->getDataType()); 191 //unsigned componentsPerElement = data->getDataSize(); 192 //bv.byteStride = bytesPerComponent * componentsPerElement; 193 194 return id; 195 } 196 getOrCreateAccessor(osg::Array * data,osg::PrimitiveSet * pset,tinygltf::Primitive & prim,const std::string & attr)197 int getOrCreateAccessor(osg::Array* data, osg::PrimitiveSet* pset, tinygltf::Primitive& prim, const std::string& attr) 198 { 199 ArraySequenceMap::iterator a = _accessors.find(data); 200 if (a != _accessors.end()) 201 return a->second; 202 203 ArraySequenceMap::iterator bv = _bufferViews.find(data); 204 if (bv == _bufferViews.end()) 205 return -1; 206 207 _model.accessors.push_back(tinygltf::Accessor()); 208 tinygltf::Accessor& accessor = _model.accessors.back(); 209 int accessorId = _model.accessors.size() - 1; 210 prim.attributes[attr] = accessorId; 211 212 accessor.type = 213 data->getDataSize() == 1 ? TINYGLTF_TYPE_SCALAR : 214 data->getDataSize() == 2 ? TINYGLTF_TYPE_VEC2 : 215 data->getDataSize() == 3 ? TINYGLTF_TYPE_VEC3 : 216 data->getDataSize() == 4 ? TINYGLTF_TYPE_VEC4 : 217 TINYGLTF_TYPE_SCALAR; 218 219 accessor.bufferView = bv->second; 220 accessor.byteOffset = 0; 221 accessor.componentType = data->getDataType(); 222 accessor.count = data->getNumElements(); 223 224 const osg::DrawArrays* da = dynamic_cast<const osg::DrawArrays*>(pset); 225 if (da) 226 { 227 accessor.byteOffset = da->getFirst() * getBytesPerElement(data); 228 } 229 230 //TODO: indexed elements 231 osg::DrawElements* de = dynamic_cast<osg::DrawElements*>(pset); 232 if (de) 233 { 234 _model.accessors.push_back(tinygltf::Accessor()); 235 tinygltf::Accessor& idxAccessor = _model.accessors.back(); 236 prim.indices = _model.accessors.size() - 1; 237 238 idxAccessor.type = TINYGLTF_TYPE_SCALAR; 239 idxAccessor.byteOffset = 0; 240 idxAccessor.componentType = de->getDataType(); 241 idxAccessor.count = de->getNumIndices(); 242 243 getOrCreateBuffer(de, idxAccessor.componentType); 244 int idxBV = getOrCreateBufferView(de, idxAccessor.componentType, GL_ELEMENT_ARRAY_BUFFER_ARB); 245 246 idxAccessor.bufferView = idxBV; 247 } 248 249 return accessorId; 250 } 251 apply(osg::Drawable & drawable)252 void apply(osg::Drawable& drawable) 253 { 254 if (drawable.asGeometry()) 255 { 256 apply(static_cast<osg::Node&>(drawable)); 257 258 osg::Geometry* geom = drawable.asGeometry(); 259 260 _model.meshes.push_back(tinygltf::Mesh()); 261 tinygltf::Mesh& mesh = _model.meshes.back(); 262 _model.nodes.back().mesh = _model.meshes.size() - 1; 263 264 osg::Vec3f posMin(FLT_MAX, FLT_MAX, FLT_MAX); 265 osg::Vec3f posMax(-FLT_MAX, -FLT_MAX, -FLT_MAX); 266 osg::Vec3Array* positions = dynamic_cast<osg::Vec3Array*>(geom->getVertexArray()); 267 if (positions) 268 { 269 getOrCreateBufferView(positions, GL_FLOAT, GL_ARRAY_BUFFER_ARB); 270 for (unsigned i = 0; i < positions->size(); ++i) 271 { 272 const osg::Vec3f& v = (*positions)[i]; 273 posMin.x() = osg::minimum(posMin.x(), v.x()); 274 posMin.y() = osg::minimum(posMin.y(), v.y()); 275 posMin.z() = osg::minimum(posMin.z(), v.z()); 276 posMax.x() = osg::maximum(posMax.x(), v.x()); 277 posMax.y() = osg::maximum(posMax.y(), v.y()); 278 posMax.z() = osg::maximum(posMax.z(), v.z()); 279 } 280 } 281 282 osg::Vec3Array* normals = dynamic_cast<osg::Vec3Array*>(geom->getNormalArray()); 283 if (normals) 284 { 285 getOrCreateBufferView(normals, GL_FLOAT, GL_ARRAY_BUFFER_ARB); 286 } 287 288 osg::Vec4Array* colors = dynamic_cast<osg::Vec4Array*>(geom->getColorArray()); 289 if (colors) 290 { 291 getOrCreateBufferView(colors, GL_FLOAT, GL_ARRAY_BUFFER_ARB); 292 } 293 294 for (unsigned i = 0; i < geom->getNumPrimitiveSets(); ++i) 295 { 296 osg::PrimitiveSet* pset = geom->getPrimitiveSet(i); 297 298 mesh.primitives.push_back(tinygltf::Primitive()); 299 tinygltf::Primitive& primitive = mesh.primitives.back(); 300 301 primitive.mode = pset->getMode(); 302 303 int a = getOrCreateAccessor(positions, pset, primitive, "POSITION"); 304 305 // record min/max for position array (required): 306 tinygltf::Accessor& posacc = _model.accessors[a]; 307 posacc.minValues.push_back(posMin.x()); 308 posacc.minValues.push_back(posMin.y()); 309 posacc.minValues.push_back(posMin.z()); 310 posacc.maxValues.push_back(posMax.x()); 311 posacc.maxValues.push_back(posMax.y()); 312 posacc.maxValues.push_back(posMax.z()); 313 314 getOrCreateAccessor(normals, pset, primitive, "NORMAL"); 315 316 getOrCreateAccessor(colors, pset, primitive, "COLOR_0"); 317 } 318 } 319 } 320 }; 321 322 class GLTFWriter 323 { 324 public: write(const osg::Node & node,const std::string & location,bool isBinary,const osgDB::Options * options)325 osgDB::ReaderWriter::WriteResult write(const osg::Node& node, 326 const std::string& location, 327 bool isBinary, 328 const osgDB::Options* options) const 329 { 330 tinygltf::Model model; 331 convertOSGtoGLTF(node, model); 332 333 tinygltf::TinyGLTF writer; 334 335 writer.WriteGltfSceneToFile( 336 &model, 337 location, 338 true, // embedImages 339 true, // embedBuffers 340 true, // prettyPrint 341 isBinary); // writeBinary 342 343 return osgDB::ReaderWriter::WriteResult::FILE_SAVED; 344 } 345 convertOSGtoGLTF(const osg::Node & node,tinygltf::Model & model)346 void convertOSGtoGLTF(const osg::Node& node, tinygltf::Model& model) const 347 { 348 model.asset.version = "2.0"; 349 350 osg::Node& nc_node = const_cast<osg::Node&>(node); // won't change it, promise :) 351 nc_node.ref(); 352 353 // GLTF uses a +X=right +y=up -z=forward coordinate system 354 osg::ref_ptr<osg::MatrixTransform> transform = new osg::MatrixTransform; 355 transform->setMatrix(osg::Matrixd::rotate(osg::Vec3d(0.0, 0.0, 1.0), osg::Vec3d(0.0, 1.0, 0.0))); 356 transform->addChild(&nc_node); 357 358 OSGtoGLTF converter(model); 359 transform->accept(converter); 360 361 transform->removeChild(&nc_node); 362 nc_node.unref_nodelete(); 363 } 364 }; 365 366 #endif // OSGEARTH_GLTF_WRITER_H 367