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