GLVis  v4.2
Accurate and flexible finite element visualization
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Pages
gltf.cpp
Go to the documentation of this file.
1 // Copyright (c) 2010-2022, Lawrence Livermore National Security, LLC. Produced
2 // at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3 // LICENSE and NOTICE for details. LLNL-CODE-443271.
4 //
5 // This file is part of the GLVis visualization tool and library. For more
6 // information and source code availability see https://glvis.org.
7 //
8 // GLVis is free software; you can redistribute it and/or modify it under the
9 // terms of the BSD-3 license. We welcome feedback and contributions, see file
10 // CONTRIBUTING.md for details.
11 
12 #include "gltf.hpp"
13 #include "aux_vis.hpp" // SaveAsPNG
14 
15 using namespace std;
16 
17 
18 const char *glTF_Builder::tensorTypes[] =
19 {
20  "SCALAR", "VEC2", "VEC3", "VEC4", "MAT2", "MAT3", "MAT4"
21 };
22 
24 glTF_Builder::addBuffer(const string &bufferName)
25 {
26  buffers.resize(buffers.size() + 1);
27  auto &buf = buffers.back();
28  buf.uri.value = file_prefix + "." + bufferName + ".bin";
29  buf.uri.valid = true;
30  buf.byteLength.value = 0;
31  buf.byteLength.valid = true;
32  buf.file.reset(new ofstream(buf.uri.value, ios::out | ios::binary));
33 
34  return {(unsigned)buffers.size() - 1};
35 }
36 
39  const void *data,
40  size_t byteLength,
41  size_t byteStride,
42  size_t byteAlign,
43  target_type target)
44 {
45  if (buffer.id >= buffers.size()) { return {INVALID_ID}; }
46 
47  buffer_views.resize(buffer_views.size() + 1);
48  auto &buf_view = buffer_views.back();
49  auto &buf = buffers[buffer.id];
50 
51  buf_view.buffer.value = buffer.id;
52  buf_view.buffer.valid = true;
53 
54  const unsigned buf_offset = buf.byteLength.value;
55  const unsigned new_offset = byteAlign*((buf_offset+byteAlign-1)/byteAlign);
56  buf_view.byteOffset.value = new_offset;
57  buf_view.byteOffset.valid = true;
58 
59  buf_view.byteLength.value = byteLength;
60  buf_view.byteLength.valid = true;
61 
62  if (target == target_type::ARRAY_BUFFER)
63  {
64  buf_view.byteStride.value = byteStride;
65  buf_view.byteStride.valid = true;
66  }
67 
68  buf_view.target.value = (unsigned)target;
69  buf_view.target.valid = true;
70 
71  // append padding to file
72  for (unsigned i = buf_offset; i != new_offset; ++i) { buf.file->put('\0'); }
73  // write data to file
74  buf.file->write(reinterpret_cast<const char *>(data), byteLength);
75 
76  buf.byteLength.value = new_offset + byteLength;
77 
78  return {(unsigned)buffer_views.size() - 1};
79 }
80 
82  const void *data,
83  size_t byteLength)
84 {
85  if (bufferView.id >= buffer_views.size()) { return; }
86 
87  auto &buf_view = buffer_views[bufferView.id];
88  auto &buf = buffers[buf_view.buffer.value];
89 
90  buf_view.byteLength.value += byteLength;
91 
92  buf.file->write(reinterpret_cast<const char *>(data), byteLength);
93  buf.byteLength.value += byteLength;
94 }
95 
98  size_t byteOffset,
99  component_type componentType,
100  size_t count,
101  tensor_type tensorType)
102 {
103  if (bufferView.id >= buffer_views.size() || count == 0)
104  {
105  return {INVALID_ID};
106  }
107 
108  accessors.resize(accessors.size() + 1);
109  auto &acc = accessors.back();
110 
111  acc.bufferView.value = bufferView.id;
112  acc.bufferView.valid = true;
113 
114  acc.byteOffset.value = byteOffset;
115  acc.byteOffset.valid = true;
116 
117  acc.componentType.value = (unsigned)componentType;
118  acc.componentType.valid = true;
119 
120  acc.count.value = count;
121  acc.count.valid = true;
122 
123  acc.type.value = tensorTypes[(unsigned)tensorType];
124  acc.type.valid = true;
125 
126  // Note: acc.min and acc.max remain invalid and will not be written too file
127 
128  if (componentType != component_type::FLOAT &&
129  buffer_views[bufferView.id].target.value !=
130  (unsigned)target_type::ELEMENT_ARRAY_BUFFER)
131  {
132  acc.normalized.value = true;
133  acc.normalized.valid = true;
134  }
135 
136  return {(unsigned)accessors.size() - 1};
137 }
138 
141  size_t byteOffset,
142  size_t count,
143  vec2f min,
144  vec2f max)
145 {
146  auto id = addAccessor(bufferView,
147  byteOffset,
148  component_type::FLOAT,
149  count,
150  tensor_type::VEC2);
151 
152  if (id.id != INVALID_ID)
153  {
154  auto &acc = accessors[id.id];
155  acc.min.value.assign(min.begin(), min.end());
156  acc.min.valid = true;
157  acc.max.value.assign(max.begin(), max.end());
158  acc.max.valid = true;
159  }
160 
161  return id;
162 }
163 
166  size_t byteOffset,
167  size_t count,
168  vec3f min,
169  vec3f max)
170 {
171  auto id = addAccessor(bufferView,
172  byteOffset,
173  component_type::FLOAT,
174  count,
175  tensor_type::VEC3);
176 
177  if (id.id != INVALID_ID)
178  {
179  auto &acc = accessors[id.id];
180  acc.min.value.assign(min.begin(), min.end());
181  acc.min.valid = true;
182  acc.max.value.assign(max.begin(), max.end());
183  acc.max.valid = true;
184  }
185 
186  return id;
187 }
188 
190 glTF_Builder::addImage(const string &imageName,
191  int width,
192  int height,
193  const color4f *pixels)
194 {
195 #ifndef GLVIS_USE_LIBPNG
196 
197  return {INVALID_ID};
198 
199 #else
200 
201  images.resize(images.size() + 1);
202  auto &img = images.back();
203 
204  img.uri.value = file_prefix + "." + imageName + ".png";
205  img.uri.valid = true;
206 
207  img.name.value = imageName;
208  img.name.valid = true;
209 
210  // write the image
211  auto get_row = [&](int row, void *pxls)
212  {
213  auto pxls_out = reinterpret_cast<array<uint8_t,4>*>(pxls);
214  auto pxls_in = pixels + row*width;
215  for (int i = 0; i < width; ++i)
216  {
217  for (int j = 0; j < 4; ++j)
218  {
219  pxls_out[i][j] = std::min(int(pxls_in[i][j]*256), 255);
220  }
221  }
222  };
223  SaveAsPNG(img.uri.value.c_str(), width, height,
224  /* is_hidpi: */ false, /* with_alpha: */ true, get_row);
225 
226  return {(unsigned)images.size() - 1};
227 
228 #endif // GLVIS_USE_LIBPNG
229 }
230 
233  min_filter minFilter,
234  wrap_type wrapS,
235  wrap_type wrapT)
236 {
237  samplers.resize(samplers.size() + 1);
238  auto &sampler = samplers.back();
239 
240  sampler.magFilter.value = (unsigned)magFilter;
241  sampler.magFilter.valid = true;
242 
243  sampler.minFilter.value = (unsigned)minFilter;
244  sampler.minFilter.valid= true;
245 
246  sampler.wrapS.value = (unsigned)wrapS;
247  sampler.wrapS.valid = true;
248 
249  sampler.wrapT.value = (unsigned)wrapT;
250  sampler.wrapT.valid = true;
251 
252  return {(unsigned)samplers.size() - 1};
253 }
254 
257 {
258  if (sampler.id >= samplers.size() || source.id >= images.size())
259  {
260  return {INVALID_ID};
261  }
262 
263  textures.resize(textures.size() + 1);
264  auto &tex = textures.back();
265 
266  tex.sampler.value = sampler.id;
267  tex.sampler.valid = true;
268 
269  tex.source.value = source.id;
270  tex.source.valid = true;
271 
272  return {(unsigned)textures.size() - 1};
273 }
274 
276 glTF_Builder::addMaterial(const string &materialName,
277  const pbr_matallic_roughness &pbrMetallicRoughness,
278  bool doubleSided)
279 {
280  if (pbrMetallicRoughness.haveTexture &&
281  pbrMetallicRoughness.baseColorTexture.id >= textures.size())
282  {
283  return {INVALID_ID};
284  }
285 
286  materials.resize(materials.size() + 1);
287  auto &mat = materials.back();
288 
289  mat.name.value = materialName;
290  mat.name.valid = true;
291 
292  auto &pbr = mat.pbrMetallicRoughness.value;
293 
294  pbr.baseColorFactor.value = pbrMetallicRoughness.baseColorFactor;
295  pbr.baseColorFactor.valid = true;
296 
297  auto &tex_info = pbr.baseColorTexture.value;
298 
299  tex_info.index.value = pbrMetallicRoughness.baseColorTexture.id;
300  tex_info.index.valid = true;
301 
302  tex_info.texCoord.value = 0;
303  tex_info.texCoord.valid = true;
304 
305  pbr.baseColorTexture.valid = pbrMetallicRoughness.haveTexture;
306 
307  pbr.metallicFactor.value = pbrMetallicRoughness.metallicFactor;
308  pbr.metallicFactor.valid = true;
309 
310  pbr.roughnessFactor.value = pbrMetallicRoughness.roughnessFactor;
311  pbr.roughnessFactor.valid = true;
312 
313  mat.pbrMetallicRoughness.valid = true;
314 
315  mat.doubleSided.value = doubleSided;
316  mat.doubleSided.valid = true;
317 
318  return {(unsigned)materials.size() - 1};
319 }
320 
322 glTF_Builder::addMesh(const string &meshName)
323 {
324  meshes.resize(meshes.size() + 1);
325  auto &mesh = meshes.back();
326 
327  mesh.name.value = meshName;
328  mesh.name.valid = true;
329 
330  return {(unsigned)meshes.size() - 1};
331 }
332 
334  accessor_id vertexPositions,
335  accessor_id vertexNormals,
336  accessor_id vertexTexCoords0,
337  accessor_id vertexIndices,
338  material_id material)
339 {
340  if (mesh.id >= meshes.size() || vertexPositions.id >= accessors.size())
341  { return; }
342 
343  auto &primitives = meshes[mesh.id].primitives;
344  primitives.resize(primitives.size() + 1);
345  auto &pri = primitives.back();
346 
347  pri.attributes.value.POSITION.value = vertexPositions.id;
348  pri.attributes.value.POSITION.valid = true;
349 
350  if (vertexNormals.id < accessors.size())
351  {
352  pri.attributes.value.NORMAL.value = vertexNormals.id;
353  pri.attributes.value.NORMAL.valid = true;
354  }
355 
356  if (vertexTexCoords0.id < accessors.size())
357  {
358  pri.attributes.value.TEXCOORD_0.value = vertexTexCoords0.id;
359  pri.attributes.value.TEXCOORD_0.valid = true;
360  }
361 
362  pri.attributes.valid = true;
363 
364  if (vertexIndices.id < accessors.size())
365  {
366  pri.indices.value = vertexIndices.id;
367  pri.indices.valid = true;
368  }
369 
370  if (material.id < materials.size())
371  {
372  pri.material.value = material.id;
373  pri.material.valid = true;
374  }
375 
376  // pri.mode remains undefined since default is 4 = TRIANGLES
377 }
378 
380  accessor_id vertexPositions,
381  accessor_id vertexTexcoords0,
382  accessor_id vertexColors0,
383  material_id material)
384 {
385  if (mesh.id >= meshes.size()) { return; }
386 
387  auto &primitives = meshes[mesh.id].primitives;
388  primitives.resize(primitives.size() + 1);
389  auto &pri = primitives.back();
390 
391  pri.attributes.value.POSITION.value = vertexPositions.id;
392  pri.attributes.value.POSITION.valid = true;
393 
394  if (vertexTexcoords0.id < accessors.size())
395  {
396  pri.attributes.value.TEXCOORD_0.value = vertexTexcoords0.id;
397  pri.attributes.value.TEXCOORD_0.valid = true;
398  }
399  else if (vertexColors0.id < accessors.size())
400  {
401  pri.attributes.value.COLOR_0.value = vertexColors0.id;
402  pri.attributes.value.COLOR_0.valid = true;
403  }
404 
405  // NORMAL remains undefined
406 
407  pri.attributes.valid = true;
408 
409  // pri.indices remain undefined
410 
411  if (material.id < materials.size())
412  {
413  pri.material.value = material.id;
414  pri.material.valid = true;
415  }
416 
417  pri.mode.value = 1; // = LINES
418  pri.mode.valid = true;
419 }
420 
422 glTF_Builder::addNode(const string &nodeName)
423 {
424  nodes.resize(nodes.size() + 1);
425  auto &node = nodes.back();
426 
427  node.name.value = nodeName;
428  node.name.valid = true;
429 
430  return {(unsigned)nodes.size() - 1};
431 }
432 
434 {
435  if (node.id >= nodes.size()) { return; }
436 
437  nodes[node.id].mesh.value = mesh.id;
438  nodes[node.id].mesh.valid = true;
439 }
440 
442 {
443  if (node.id >= nodes.size()) { return; }
444 
445  nodes[node.id].scale.value = scale;
446  nodes[node.id].scale.valid = true;
447 }
448 
450 {
451  if (node.id >= nodes.size()) { return; }
452 
453  nodes[node.id].translation.value = translation;
454  nodes[node.id].translation.valid = true;
455 }
456 
458  pbr_matallic_roughness &pbr_mr_copy)
459 {
460  if (material.id >= materials.size()) { return; }
461 
462  auto &mat = materials[material.id];
463  auto &pbr = mat.pbrMetallicRoughness.value;
464 
465  pbr_mr_copy.haveTexture = pbr.baseColorTexture.valid;
466  pbr_mr_copy.baseColorFactor = pbr.baseColorFactor.value;
467  pbr_mr_copy.baseColorTexture = {pbr.baseColorTexture.value.index.value};
468  pbr_mr_copy.metallicFactor = pbr.metallicFactor.value;
469  pbr_mr_copy.roughnessFactor = pbr.roughnessFactor.value;
470 }
471 
473 {
474  if (nodes.size() == 0)
475  {
476  return 1;
477  }
478 
479  ofstream gltf(file_prefix + ".gltf");
480  gltf.precision(8);
481  gltf.setf(ios::boolalpha);
482 
483  // ~~~ Tutorial ~~~
484  // https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/README.md
485 
486  // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-scene
487  gltf <<
488  "{\n"
489  " \"scene\": 0,\n"
490  " \"scenes\" : [ {\n"
491  " \"nodes\" : [";
492  for (size_t i = 0; i != nodes.size(); ++i) { gltf << sep(i) << ' ' << i; }
493  gltf <<
494  " ]\n"
495  " } ],\n\n";
496 
497  // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-node
498  gltf << " \"nodes\" : [";
499  for (size_t i = 0; i != nodes.size(); ++i)
500  {
501  gltf << sep(i) << " {";
502  int pos = 0;
503  print_node(gltf, pos, "\n ", nodes[i].name);
504  print_node(gltf, pos, "\n ", nodes[i].mesh);
505  print_node(gltf, pos, "\n ", nodes[i].scale);
506  print_node(gltf, pos, "\n ", nodes[i].translation);
507  gltf << "\n }";
508  }
509  gltf << " ],\n\n";
510 
511  // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-mesh
512  gltf << " \"meshes\" : [";
513  for (size_t i = 0; i != meshes.size(); ++i)
514  {
515  gltf << sep(i) << " {";
516  int pos = 0;
517  print_node(gltf, pos, "\n ", meshes[i].name);
518  gltf << sep(pos++) << "\n \"primitives\" : [";
519  auto &primitives = meshes[i].primitives;
520  for (size_t j = 0; j != primitives.size(); ++j)
521  {
522  gltf << sep(j) << " {";
523  int pos2 = 0;
524  print_node(gltf, pos2, "\n ", primitives[j].attributes);
525  print_node(gltf, pos2, "\n ", primitives[j].indices);
526  print_node(gltf, pos2, "\n ", primitives[j].material);
527  print_node(gltf, pos2, "\n ", primitives[j].mode);
528  gltf << "\n }";
529  }
530  gltf << " ]\n }";
531  }
532  gltf << " ],\n\n";
533 
534  // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-material
535  gltf << " \"materials\" : [";
536  for (size_t i = 0; i != materials.size(); ++i)
537  {
538  gltf << sep(i) << " {";
539  int pos = 0;
540  print_node(gltf, pos, "\n ", materials[i].name);
541  print_node(gltf, pos, "\n ", materials[i].pbrMetallicRoughness);
542  print_node(gltf, pos, "\n ", materials[i].doubleSided);
543  gltf << "\n }";
544  }
545  gltf << " ],\n\n";
546 
547  gltf << " \"textures\" : [";
548  for (size_t i = 0; i != textures.size(); ++i)
549  {
550  gltf << sep(i) << " {";
551  int pos = 0;
552  print_node(gltf, pos, "\n ", textures[i].sampler);
553  print_node(gltf, pos, "\n ", textures[i].source);
554  gltf << "\n }";
555  }
556  gltf << " ],\n\n";
557 
558  gltf << " \"images\" : [";
559  for (size_t i = 0; i != images.size(); ++i)
560  {
561  gltf << sep(i) << " {";
562  int pos = 0;
563  print_node(gltf, pos, "\n ", images[i].name);
564  print_node(gltf, pos, "\n ", images[i].uri);
565  gltf << "\n }";
566  }
567  gltf << " ],\n\n";
568 
569  // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-sampler
570  // see also: PaletteState::{ToTextureDiscrete(),ToTextureSmooth()}
571  gltf << " \"samplers\" : [";
572  for (size_t i = 0; i != samplers.size(); ++i)
573  {
574  gltf << sep(i) << " {";
575  int pos = 0;
576  print_node(gltf, pos, "\n ", samplers[i].magFilter);
577  print_node(gltf, pos, "\n ", samplers[i].minFilter);
578  print_node(gltf, pos, "\n ", samplers[i].wrapS);
579  print_node(gltf, pos, "\n ", samplers[i].wrapT);
580  gltf << "\n }";
581  }
582  gltf << " ],\n\n";
583 
584  // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-buffer
585  gltf << " \"buffers\" : [";
586  for (size_t i = 0; i != buffers.size(); ++i)
587  {
588  gltf << sep(i) << " {";
589  int pos = 0;
590  print_node(gltf, pos, "\n ", buffers[i].uri);
591  print_node(gltf, pos, "\n ", buffers[i].byteLength);
592  gltf << "\n }";
593  }
594  gltf << " ],\n\n";
595 
596  // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-bufferview
597  gltf << " \"bufferViews\" : [";
598  for (size_t i = 0; i != buffer_views.size(); ++i)
599  {
600  gltf << sep(i) << " {";
601  int pos = 0;
602  print_node(gltf, pos, "\n ", buffer_views[i].buffer);
603  print_node(gltf, pos, "\n ", buffer_views[i].byteOffset);
604  print_node(gltf, pos, "\n ", buffer_views[i].byteLength);
605  print_node(gltf, pos, "\n ", buffer_views[i].byteStride);
606  print_node(gltf, pos, "\n ", buffer_views[i].target);
607  gltf << "\n }";
608  }
609  gltf << " ],\n\n";
610 
611  // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-accessor
612  gltf << " \"accessors\" : [";
613  for (size_t i = 0; i != accessors.size(); ++i)
614  {
615  gltf << sep(i) << " {";
616  int pos = 0;
617  print_node(gltf, pos, "\n ", accessors[i].bufferView);
618  print_node(gltf, pos, "\n ", accessors[i].byteOffset);
619  print_node(gltf, pos, "\n ", accessors[i].componentType);
620  print_node(gltf, pos, "\n ", accessors[i].count);
621  print_node(gltf, pos, "\n ", accessors[i].type);
622  print_node(gltf, pos, "\n ", accessors[i].min);
623  print_node(gltf, pos, "\n ", accessors[i].max);
624  print_node(gltf, pos, "\n ", accessors[i].normalized);
625  gltf << "\n }";
626  }
627  gltf << " ],\n\n";
628 
629  // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-asset
630  gltf <<
631  " \"asset\" : {\n"
632  " \"version\" : \"2.0\",\n"
633  " \"generator\" : \"GLVis\"\n"
634  " }\n"
635  "}\n";
636 
637  return 0;
638 }
material_id addMaterial(const std::string &materialName, const pbr_matallic_roughness &pbrMetallicRoughness, bool doubleSided=false)
Definition: gltf.cpp:276
void addNodeScale(node_id node, vec3f scale)
Definition: gltf.cpp:441
accessor_id addAccessorVec3f(buffer_view_id bufferView, size_t byteOffset, size_t count, vec3f min, vec3f max)
Definition: gltf.cpp:165
buffer_view_id addBufferView(buffer_id buffer, const void *data, size_t byteLength, size_t byteStride, size_t byteAlign, target_type target)
Definition: gltf.cpp:38
sampler_id addSampler(mag_filter magFilter=mag_filter::NEAREST, min_filter minFilter=min_filter::NEAREST, wrap_type wrapS=wrap_type::CLAMP_TO_EDGE, wrap_type wrapT=wrap_type::CLAMP_TO_EDGE)
Definition: gltf.cpp:232
void appendToBufferView(buffer_view_id bufferView, const void *data, size_t byteLength)
Definition: gltf.cpp:81
void addMeshLines(mesh_id mesh, accessor_id vertexPositions, accessor_id vertexTexcoords0, accessor_id vertexColors0, material_id material)
Definition: gltf.cpp:379
void addNodeTranslation(node_id node, vec3f translation)
Definition: gltf.cpp:449
void addMeshTriangles(mesh_id mesh, accessor_id vertexPositions, accessor_id vertexNormals, accessor_id vertexTexCoords0, accessor_id vertexIndices, material_id material)
Definition: gltf.cpp:333
buffer_id addBuffer(const std::string &bufferName)
Definition: gltf.cpp:24
std::array< float, 3 > vec3f
Definition: gltf.hpp:28
texture_id addTexture(sampler_id sampler, image_id source)
Definition: gltf.cpp:256
mesh_id addMesh(const std::string &meshName)
Definition: gltf.cpp:322
int SaveAsPNG(const char *fname, int w, int h, bool is_hidpi, bool with_alpha, std::function< void(int, void *)> get_row)
Definition: aux_vis.cpp:895
node_id addNode(const std::string &nodeName)
Definition: gltf.cpp:422
image_id addImage(const std::string &imageName, int width, int height, const color4f *pixels)
Definition: gltf.cpp:190
accessor_id addAccessor(buffer_view_id bufferView, size_t byteOffset, component_type componentType, size_t count, tensor_type tensorType)
Definition: gltf.cpp:97
int writeFile()
Definition: gltf.cpp:472
std::array< float, 4 > color4f
Definition: gltf.hpp:29
void getMaterialPBRMR(material_id material, pbr_matallic_roughness &pbr_mr_copy)
Definition: gltf.cpp:457
Material materials[5]
Definition: material.cpp:14
void addNodeMesh(node_id node, mesh_id mesh)
Definition: gltf.cpp:433
std::array< float, 2 > vec2f
Definition: gltf.hpp:27
static const char * tensorTypes[]
Definition: gltf.hpp:252
accessor_id addAccessorVec2f(buffer_view_id bufferView, size_t byteOffset, size_t count, vec2f min, vec2f max)
Definition: gltf.cpp:140