Assign name for accessor.

Improve .obj export.
Use clipp to parse cmd options.
Add some handy python scripts.
This commit is contained in:
Syoyo Fujita 2020-03-12 21:31:12 +09:00
parent cf30d42cbc
commit 0ee2120965
10 changed files with 7806 additions and 360 deletions

View File

@ -71,6 +71,7 @@ In extension(`ExtensionMap`), JSON number value is parsed as int or float(number
* [glview](examples/glview) : Simple glTF geometry viewer.
* [validator](examples/validator) : Simple glTF validator with JSON schema.
* [basic](examples/basic) : Basic glTF viewer with texturing support.
* [mesh-conv](examples/mesh-conv) : Convert glTF mesh to wavefront .obj, wavefront .obj to glTF mesh.
## Projects using TinyGLTF
@ -216,3 +217,7 @@ We may be better to introduce bounded memory size checking when parsing glTF dat
* catch : Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. Distributed under the Boost Software License, Version 1.0.
* RapidJSON : Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. http://rapidjson.org/
* dlib(uridecode, uriencode) : Copyright (C) 2003 Davis E. King Boost Software License 1.0. http://dlib.net/dlib/server/server_http.cpp.html
### Used in examples
* clipp: MIT License. https://github.com/muellan/clipp

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 André Müller; foss@andremueller-online.de
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

7024
examples/common/clipp.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,34 @@
# Mesh modify experiment
Sometimes we want to tweak mesh attributes(e.g. vertex position, uv coord, etc).
glTF itself does not allow ASCII representation of such data.
glTF itself does not allow ASCII representation of such data so we need to write a converter.
This example show how to
- Export mesh data from .bin to .obj
- Import mesh data to .bin(update corresponding buffer data) from .obj
## Features
* Support skin weights(`JOINTS_N`, `WEIGHTS_N`)
## Supported attributes
* [x] POSITION
* [x] NORMAL
* [x] TANGENT
* [ ] COLOR (vertex color)
* [x] TEXCOORD_N
* Only single texcoord(uv set) is supported
* Specify `--uvset 1` to specify which UV to use.
* [x] WEIGHTS_N, JOINTS_N
## Usage
### Wavefront .obj to glTF
```
$ mesh-modify obj2gltf input.obj
$ mesh-modify --op=obj2gltf input.obj
```
All shapes in .obj are concatenated and create single glTF mesh.
@ -26,7 +41,7 @@ Buffer is stored as external file(`.bin`)
### glTF to Wavefront .obj
```
$ mesh-modify gltf2obj input.gltf
$ mesh-modify --op=gltf2obj input.gltf
```
.obj will be created for each glTF Mesh.

View File

@ -2,11 +2,11 @@
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <limits>
#include <string>
#include <vector>
#include <fstream>
#if !defined(__ANDROID__) && !defined(_WIN32)
#include <wordexp.h>
@ -18,6 +18,7 @@
#endif
#include "../../json.hpp"
#include "../common/clipp.h"
using json = nlohmann::json;
@ -406,7 +407,9 @@ static uint32_t UnpackIndex(const unsigned char *ptr, int type) {
}
static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
example::MeshPrim *out) {
bool verbose, example::MeshPrim *out) {
out->prims.clear();
for (size_t i = 0; i < mesh.primitives.size(); i++) {
const tinygltf::Primitive &primitive = mesh.primitives[i];
@ -415,6 +418,8 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
return false;
}
example::PrimSet out_prim;
// indices.
{
const tinygltf::Accessor &indexAccessor =
@ -429,7 +434,8 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
model.bufferViews[size_t(indexAccessor.bufferView)];
// should be 34963(ELEMENT_ARRAY_BUFFER)
std::cout << "index.target = " << PrintTarget(indexBufferView.target) << "\n";
std::cout << "index.target = " << PrintTarget(indexBufferView.target)
<< "\n";
if (indexBufferView.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) {
std::cerr << "indexBufferView.target must be ELEMENT_ARRAY_BUFFER\n";
return false;
@ -441,21 +447,22 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
std::vector<uint32_t> indices;
for (size_t k = 0; k < num_elements; k++) {
// TODO(syoyo): out-of-bounds check.
const unsigned char *ptr = indexBuffer.data.data() +
indexBufferView.byteOffset + (k * byte_stride) +
indexAccessor.byteOffset;
indexBufferView.byteOffset +
(k * byte_stride) + indexAccessor.byteOffset;
uint32_t idx = UnpackIndex(ptr, indexAccessor.componentType);
if (verbose) {
std::cout << "vertex_index[" << k << "] = " << idx << "\n";
}
indices.push_back(idx);
}
out->indices = indices;
out->indices_type = indexAccessor.componentType;
out_prim.indices = indices;
out_prim.indices_type = indexAccessor.componentType;
}
// attributes
@ -522,7 +529,9 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
bufferView.byteOffset + (k * byte_stride) +
accessor.byteOffset;
float value = Unpack(ptr, accessor.componentType);
if (verbose) {
std::cout << "[" << k << "] value = " << value << "\n";
}
attrib.data.push_back(value);
}
attrib.component_type = accessor.componentType;
@ -530,28 +539,27 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
attrib.name = it->first;
if (attrib.name.compare("POSITION") == 0) {
out->position = attrib;
out_prim.position = attrib;
} else if (attrib.name.compare("NORMAL") == 0) {
out->normal = attrib;
out_prim.normal = attrib;
} else if (attrib.name.compare("TANGENT") == 0) {
out->tangent = attrib;
out_prim.tangent = attrib;
} else if (attrib.name.rfind("TEXCOORD_", 0) == 0) {
int id = GetSlotId(attrib.name);
std::cout << "texcoord[" << id << "]\n";
out->texcoords[id] = attrib;
out_prim.texcoords[id] = attrib;
} else if (attrib.name.rfind("JOINTS_", 0) == 0) {
int id = GetSlotId(attrib.name);
std::cout << "joints[" << id << "]\n";
out->joints[id] = attrib;
out_prim.joints[id] = attrib;
} else if (attrib.name.rfind("WEIGHTS_", 0) == 0) {
int id = GetSlotId(attrib.name);
std::cout << "weights[" << id << "]\n";
out->weights[id] = attrib;
out_prim.weights[id] = attrib;
} else {
std::cerr << "???: attrib.name = " << attrib.name << "\n";
return false;
}
}
}
@ -564,15 +572,18 @@ static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
return false;
}
out->mode = primitive.mode;
out->name = mesh.name;
out_prim.mode = primitive.mode;
out->prims.push_back(out_prim);
}
out->name = mesh.name;
return true;
}
static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model,
std::vector<example::MeshPrim> *outs) {
bool verbose, std::vector<example::MeshPrim> *outs) {
// Get .bin data
{
if (model.buffers.size() != 1) {
@ -595,11 +606,13 @@ static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model,
search_paths.push_back(asset_path);
std::string abs_filepath = FindFile(search_paths, buffer.uri);
std::vector<uint8_t> bin = LoadBin(buffer.uri);
std::vector<uint8_t> bin = LoadBin(abs_filepath);
if (bin.size() != buffer.data.size()) {
std::cerr << "Byte size mismatch. Failed to load file: " << buffer.uri
<< "\n";
std::cerr << " .bin size = " << bin.size() << ", size in 'buffer.uri' = " << buffer.data.size() << "\n";
std::cerr << " Searched absolute file path: " << abs_filepath << "\n";
std::cerr << " .bin size = " << bin.size()
<< ", size in 'buffer.uri' = " << buffer.data.size() << "\n";
return false;
}
}
@ -608,7 +621,7 @@ static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model,
std::cout << "mesh.name: " << mesh.name << "\n";
example::MeshPrim output;
bool ret = DumpMesh(model, mesh, &output);
bool ret = DumpMesh(model, mesh, verbose, &output);
if (!ret) {
return false;
}
@ -622,31 +635,41 @@ static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model,
} // namespace
int main(int argc, char **argv) {
if (argc < 3) {
std::cout << "mesh-modify <op> <args>" << std::endl;
std::cout << " op\n\n";
std::cout << " gltf2obj input.gltf <flip_texcoord_y>\n";
std::cout << " obj2gltf input.obj <verbose>\n";
std::string op;
std::string input_filename;
std::string output_filename = "output.gltf";
int uvset = 0;
bool verbose = false;
bool export_skinweight = true;
bool no_flip_texcoord_y = false;
auto cli =
(clipp::required("-i", "--input") &
clipp::value("input filename", input_filename),
clipp::option("-o", "--outout") &
clipp::value("Output filename(obj2fltf)", output_filename),
clipp::option("-v", "--verbose").set(verbose).doc("Verbose output"),
clipp::option("--export_skinweight") &
clipp::value("Export skin weights(gltf2obj). default true.",
export_skinweight),
clipp::option("--uvset").set(uvset).doc("UV set(TEXCOORD_N) to use"),
clipp::option("--op") &
clipp::value("operation mode(`gltf2obj`, `obj2gltf`", op),
clipp::option("--no-flip-texcoord-y")
.set(no_flip_texcoord_y)
.doc("Do not flip texcoord Y"));
if (!clipp::parse(argc, argv, cli)) {
std::cout << clipp::make_man_page(cli, argv[0]);
return EXIT_FAILURE;
}
std::string op = argv[1];
if (op == "gltf2obj") {
bool flip_texcoord_y = true;
if (argc > 3) {
flip_texcoord_y = (std::atoi(argv[3]) > 0) ? true : false;
}
tinygltf::Model model;
tinygltf::TinyGLTF loader;
std::string err;
std::string warn;
std::string input_filename(argv[2]);
std::string ext = GetFilePathExtension(input_filename);
{
@ -657,8 +680,8 @@ int main(int argc, char **argv) {
input_filename.c_str());
} else {
// assume ascii glTF.
ret =
loader.LoadASCIIFromFile(&model, &err, &warn, input_filename.c_str());
ret = loader.LoadASCIIFromFile(&model, &err, &warn,
input_filename.c_str());
}
if (!warn.empty()) {
@ -674,6 +697,7 @@ int main(int argc, char **argv) {
}
}
#if 0
json j;
{
std::ifstream i(input_filename);
@ -703,42 +727,48 @@ int main(int argc, char **argv) {
json j_ret = j.patch(j_patch);
std::cout << "patched = " << j_ret.dump(2) << "\n";
#endif
std::string basedir = GetBaseDir(input_filename);
std::vector<example::MeshPrim> meshes;
bool ret = ExtractMesh(basedir, model, &meshes);
bool ret = ExtractMesh(basedir, model, verbose, &meshes);
size_t n = 0;
for (const auto &mesh : meshes) {
// Assume no duplicated name in .glTF data
std::string filename;
std::string basename;
if (mesh.name.empty()) {
filename = "untitled-" + std::to_string(n) + ".obj";
basename = "untitled-" + std::to_string(n);
} else {
filename = mesh.name + ".obj";
basename = mesh.name;
}
bool ok = example::SaveAsObjMesh(filename, mesh, flip_texcoord_y);
for (size_t primid = 0; primid < mesh.prims.size(); primid++) {
example::ObjExportOption options;
options.primid = int(primid);
options.export_skinweights = export_skinweight;
options.uvset = uvset;
options.flip_texcoord_y = !no_flip_texcoord_y;
bool ok = example::SaveAsObjMesh(basename, mesh, options);
if (!ok) {
return EXIT_FAILURE;
std::cout << "Failed to export mesh[" << mesh.name << "].primitives["
<< primid << "]\n";
// may ok;
}
}
n++;
}
return ret ? EXIT_SUCCESS : EXIT_FAILURE;
} else if (op == "obj2gltf") {
std::string input_filename(argv[2]);
bool verbose = false;
if (argc > 3) {
verbose = (std::atoi(argv[3]) > 0) ? true : false;
}
// Require facevarying layout?
// facevarying representation is required if a vertex can have multiple normal/uv value.
// drawback of facevarying is mesh data increases.
// false = try to keep shared vertex representation as much as possible.
// true = reorder vertex data and re-assign vertex indices for facevarying data layout.
// facevarying representation is required if a vertex can have multiple
// normal/uv value. drawback of facevarying is mesh data increases. false =
// try to keep shared vertex representation as much as possible. true =
// reorder vertex data and re-assign vertex indices for facevarying data
// layout.
bool facevarying = false;
example::MeshPrim mesh;
@ -751,8 +781,6 @@ int main(int argc, char **argv) {
PrintMeshPrim(mesh);
}
std::string output_filename("output.gltf");
ok = example::SaveAsGLTFMesh(output_filename, mesh);
if (!ok) {
std::cerr << "Failed to save mesh as glTF\n";
@ -761,9 +789,7 @@ int main(int argc, char **argv) {
std::cout << "Write glTF: " << output_filename << "\n";
return EXIT_SUCCESS;
} else {
std::cerr << "Unknown operation: " << op << "\n";
return EXIT_FAILURE;
}
}

View File

@ -371,15 +371,18 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
std::vector<tinygltf::Accessor> *accessors,
std::vector<tinygltf::BufferView> *bufferViews,
tinygltf::Buffer *buffer) {
int prim_id = 0;
std::vector<uint8_t> buf;
// single primitive per mesh
tinygltf::Primitive primitive;
const PrimSet &prim = mesh.prims[prim_id];
// vertex index
{
size_t s, e;
if (!SerializeVertexIndicesToBuffer(mesh.indices, mesh.indices_type, &buf,
if (!SerializeVertexIndicesToBuffer(prim.indices, prim.indices_type, &buf,
&s, &e)) {
return false;
}
@ -393,13 +396,14 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
bufferViews->push_back(bufferView);
tinygltf::Accessor accessor;
accessor.name = mesh.name + "#" + std::to_string(prim_id) + "/indices";
accessor.bufferView = bufferViews->size() - 1;
accessor.minValues.resize(1);
accessor.minValues[0] = mesh.indices_min;
accessor.minValues[0] = prim.indices_min;
accessor.maxValues.resize(1);
accessor.maxValues[0] = mesh.indices_max;
accessor.count = mesh.indices.size();
accessor.componentType = mesh.indices_type;
accessor.maxValues[0] = prim.indices_max;
accessor.count = prim.indices.size();
accessor.componentType = prim.indices_type;
accessor.type = TINYGLTF_TYPE_SCALAR;
accessors->push_back(accessor);
@ -409,7 +413,7 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
// position
{
size_t s, e;
if (!SerializeVertexAttribToBuffer(mesh.position, &buf, &s, &e)) {
if (!SerializeVertexAttribToBuffer(prim.position, &buf, &s, &e)) {
return false;
}
std::cout << "postion.byteRange: [" << s << ", " << e << "]\n";
@ -422,15 +426,16 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
bufferViews->push_back(bufferView);
tinygltf::Accessor accessor = ConvertToGLTFAccessor(
mesh.position, bufferViews->size() - 1, /* offset */ 0);
prim.position, bufferViews->size() - 1, /* offset */ 0);
accessor.name = mesh.name + "#" + std::to_string(prim_id) + "/POSITION";
accessors->push_back(accessor);
primitive.attributes["POSITION"] = accessors->size() - 1;
}
if (mesh.normal.data.size() > 0) {
if (prim.normal.data.size() > 0) {
size_t s, e;
if (!SerializeVertexAttribToBuffer(mesh.normal, &buf, &s, &e)) {
if (!SerializeVertexAttribToBuffer(prim.normal, &buf, &s, &e)) {
return false;
}
std::cout << "normal.byteRange: [" << s << ", " << e << "]\n";
@ -443,15 +448,16 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
bufferViews->push_back(bufferView);
tinygltf::Accessor accessor = ConvertToGLTFAccessor(
mesh.normal, bufferViews->size() - 1, /* offset */ 0);
prim.normal, bufferViews->size() - 1, /* offset */ 0);
accessor.name = mesh.name + "#" + std::to_string(prim_id) + "/NORMAL";
accessors->push_back(accessor);
primitive.attributes["NORMAL"] = accessors->size() - 1;
}
if (mesh.tangent.data.size() > 0) {
if (prim.tangent.data.size() > 0) {
size_t s, e;
if (!SerializeVertexAttribToBuffer(mesh.tangent, &buf, &s, &e)) {
if (!SerializeVertexAttribToBuffer(prim.tangent, &buf, &s, &e)) {
return false;
}
std::cout << "tangent.byteRange: [" << s << ", " << e << "]\n";
@ -464,14 +470,15 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
bufferViews->push_back(bufferView);
tinygltf::Accessor accessor = ConvertToGLTFAccessor(
mesh.tangent, bufferViews->size() - 1, /* offset */ 0);
prim.tangent, bufferViews->size() - 1, /* offset */ 0);
accessor.name = mesh.name + "#" + std::to_string(prim_id) + "/TANGENT";
accessors->push_back(accessor);
primitive.attributes["TANGENT"] = accessors->size() - 1;
}
if (mesh.texcoords.size() > 0) {
for (const auto &item : mesh.texcoords) {
if (prim.texcoords.size() > 0) {
for (const auto &item : prim.texcoords) {
size_t s, e;
if (!SerializeVertexAttribToBuffer(item.second, &buf, &s, &e)) {
return false;
@ -487,6 +494,7 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
tinygltf::Accessor accessor = ConvertToGLTFAccessor(
item.second, bufferViews->size() - 1, /* offset */ 0);
accessor.name = mesh.name + "#" + std::to_string(prim_id) + "/TEXCOORD_" + std::to_string(item.first);
accessors->push_back(accessor);
std::string target = "TEXCOORD_" + std::to_string(item.first);
@ -494,8 +502,8 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
}
}
if (mesh.joints.size() > 0) {
for (const auto &item : mesh.joints) {
if (prim.joints.size() > 0) {
for (const auto &item : prim.joints) {
size_t s, e;
if (!SerializeVertexAttribToBuffer(item.second, &buf, &s, &e)) {
return false;
@ -511,7 +519,8 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
bufferViews->push_back(bufferView);
tinygltf::Accessor accessor = ConvertToGLTFAccessor(
mesh.tangent, bufferViews->size() - 1, /* offset */ 0);
item.second, bufferViews->size() - 1, /* offset */ 0);
accessor.name = mesh.name + "#" + std::to_string(prim_id) + "/JOINTS_" + std::to_string(item.first);
accessors->push_back(accessor);
std::string target = "JOINTS_" + std::to_string(item.first);
@ -519,8 +528,8 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
}
}
if (mesh.weights.size() > 0) {
for (const auto &item : mesh.weights) {
if (prim.weights.size() > 0) {
for (const auto &item : prim.weights) {
size_t s, e;
if (!SerializeVertexAttribToBuffer(item.second, &buf, &s, &e)) {
return false;
@ -536,7 +545,8 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
bufferViews->push_back(bufferView);
tinygltf::Accessor accessor = ConvertToGLTFAccessor(
mesh.tangent, bufferViews->size() - 1, /* offset */ 0);
item.second, bufferViews->size() - 1, /* offset */ 0);
accessor.name = mesh.name + "#" + std::to_string(prim_id) + "/WEIGHTS_" + std::to_string(item.first);
accessors->push_back(accessor);
std::string target = "WEIGHTS_" + std::to_string(item.first);
@ -544,7 +554,7 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
}
}
primitive.mode = mesh.mode;
primitive.mode = prim.mode;
gltfmesh->primitives.push_back(primitive);
@ -554,62 +564,134 @@ bool ConvertToGLTFMesh(const MeshPrim &mesh, int buffer_id,
return true;
}
} // namespace
bool HasValidSkinWeights(const PrimSet &prim)
{
if ((prim.weights.size() > 0) && (prim.weights.size() == prim.joints.size())) {
bool SaveAsObjMesh(const std::string &filename, const MeshPrim &mesh,
bool flip_texcoord_y) {
std::ofstream ofs(filename);
if (!ofs) {
std::cerr << "Failed to open .obj to write: " << filename << "\n";
if (prim.weights.size() != prim.joints.size()) {
std::cerr << "# of JOINTS(" << prim.joints.size() << ") and WEIGHTS(" << prim.weights.size() << ") differs\n";
return false;
}
size_t num_slots = prim.weights.size();
// Assume weight slots are tightly packed.
for (size_t slot = 0; slot < num_slots; slot++) {
if (!prim.weights.count(slot)) {
std::cerr << "WEIGHTS_" << slot << " not found.\n";
return false;;
}
if (!prim.joints.count(slot)) {
std::cerr << "JOINTS_" << slot << " not found.\n";
return false;;
}
}
return true;
}
return false;
}
} // namespace
bool SaveAsObjMesh(const std::string &basename, const MeshPrim &mesh, const ObjExportOption &options) {
if (options.primid >= mesh.prims.size()) {
std::cerr << "mesh( " << mesh.name << ") does not contain " << options.primid << "th primitive. mesh.primitives.length = " << mesh.prims.size() << "\n";
return false;
}
const PrimSet &prim = mesh.prims[options.primid];
if (prim.texcoords.count(options.uvset)) {
std::cerr << "Exporting uvset " << options.uvset << " requested, but mesh( " << mesh.name << ") does not contain TEXCOORD_" << options.uvset << "\n";
std::cerr << "UV coord will not be exported to .obj\n";
}
std::string obj_filename = basename + "_" + std::to_string(options.primid) + ".obj";
std::ofstream ofs(obj_filename);
if (!ofs) {
std::cerr << "Failed to open .obj to write: " << obj_filename << "\n";
return false;
}
bool has_vn = false;
bool has_vt = false;
has_vn = mesh.normal.data.size() == mesh.position.data.size();
has_vt = mesh.texcoords.count(0) &&
(mesh.texcoords.at(0).data.size() > 0); // TEXCOORD_0
has_vn = prim.normal.data.size() == prim.position.data.size();
has_vt = prim.texcoords.count(options.uvset) &&
(prim.texcoords.at(options.uvset).data.size() > 0);
// v
for (size_t i = 0; i < mesh.position.data.size() / 3; i++) {
ofs << "v " << mesh.position.data[3 * i + 0] << " "
<< mesh.position.data[3 * i + 1] << " " << mesh.position.data[3 * i + 2]
for (size_t i = 0; i < prim.position.data.size() / 3; i++) {
ofs << "v " << prim.position.data[3 * i + 0] << " "
<< prim.position.data[3 * i + 1] << " " << prim.position.data[3 * i + 2]
<< "\n";
}
// vn
for (size_t i = 0; i < mesh.normal.data.size() / 3; i++) {
ofs << "vn " << mesh.normal.data[3 * i + 0] << " "
<< mesh.normal.data[3 * i + 1] << " " << mesh.normal.data[3 * i + 2]
for (size_t i = 0; i < prim.normal.data.size() / 3; i++) {
ofs << "vn " << prim.normal.data[3 * i + 0] << " "
<< prim.normal.data[3 * i + 1] << " " << prim.normal.data[3 * i + 2]
<< "\n";
}
assert((mesh.texcoords.at(0).data.size() / 2) ==
(mesh.position.data.size() / 3));
if (has_vt) {
assert((prim.texcoords.at(options.uvset).data.size() / 2) == (prim.position.data.size() / 3));
// vt
for (size_t i = 0; i < mesh.texcoords.at(0).data.size() / 2; i++) {
float y = mesh.texcoords.at(0).data[2 * i + 1];
if (flip_texcoord_y) {
for (size_t i = 0; i < prim.texcoords.at(options.uvset).data.size() / 2; i++) {
float y = prim.texcoords.at(options.uvset).data[2 * i + 1];
if (options.flip_texcoord_y) {
y = 1.0f - y;
}
ofs << "vt " << mesh.texcoords.at(0).data[2 * i + 0] << " " << y << "\n";
ofs << "vt " << prim.texcoords.at(options.uvset).data[2 * i + 0] << " " << y << "\n";
}
}
if (options.export_skinweights && HasValidSkinWeights(prim)) {
// WEIGHTS_ and JOINTS_ slots are tightly packed.
size_t num_slots = prim.weights.size();
for (size_t v = 0; v < prim.position.data.size() / 3; v++) { // vec3
std::vector<float> weights(num_slots * 4, 0.0f);
std::vector<float> joints(num_slots * 4, 0.0f);
for (size_t slot = 0; slot < num_slots; slot++) {
for (size_t k = 0; k < 4; k++) {
weights[slot * 4 + k] = prim.weights.at(slot).data[4 * v + k];
joints[slot * 4 + k] = prim.joints.at(slot).data[4 * v + k];
}
}
// vertex index start with 0.
ofs << "vw " << v << " " ;
for (size_t i = 0; i < weights.size(); i++) {
ofs << int(joints[i]) << " " << weights[i] << " ";
}
ofs << "\n";
}
}
// v, vn, vt has same index
for (size_t i = 0; i < mesh.indices.size() / 3; i++) {
for (size_t i = 0; i < prim.indices.size() / 3; i++) {
// .obj's index start with 1.
int f0 = int(mesh.indices[3 * i + 0]) + 1;
int f1 = int(mesh.indices[3 * i + 1]) + 1;
int f2 = int(mesh.indices[3 * i + 2]) + 1;
int f0 = int(prim.indices[3 * i + 0]) + 1;
int f1 = int(prim.indices[3 * i + 1]) + 1;
int f2 = int(prim.indices[3 * i + 2]) + 1;
ofs << "f " << make_triple(f0, has_vn, has_vt) << " "
<< make_triple(f1, has_vn, has_vt) << " "
<< make_triple(f2, has_vn, has_vt) << "\n";
}
// TODO(syoyo): Write joints/weights
return true;
}
@ -769,12 +851,14 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
PrimSet prim;
// reorder texcoords and normals so that it has same indexing to vertices.
if (facevarying) {
mesh->position.data.clear();
mesh->normal.data.clear();
mesh->tangent.data.clear();
mesh->texcoords[0] = VertexAttrib();
prim.position.data.clear();
prim.normal.data.clear();
prim.tangent.data.clear();
prim.texcoords[0] = VertexAttrib();
// Concat shapes
for (size_t s = 0; s < shapes.size(); s++) {
@ -882,43 +966,43 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
n[2][2] = n[0][2];
}
mesh->position.data.push_back(v[0][0]);
mesh->position.data.push_back(v[0][1]);
mesh->position.data.push_back(v[0][2]);
prim.position.data.push_back(v[0][0]);
prim.position.data.push_back(v[0][1]);
prim.position.data.push_back(v[0][2]);
mesh->position.data.push_back(v[1][0]);
mesh->position.data.push_back(v[1][1]);
mesh->position.data.push_back(v[1][2]);
prim.position.data.push_back(v[1][0]);
prim.position.data.push_back(v[1][1]);
prim.position.data.push_back(v[1][2]);
mesh->position.data.push_back(v[2][0]);
mesh->position.data.push_back(v[2][1]);
mesh->position.data.push_back(v[2][2]);
prim.position.data.push_back(v[2][0]);
prim.position.data.push_back(v[2][1]);
prim.position.data.push_back(v[2][2]);
mesh->normal.data.push_back(n[0][0]);
mesh->normal.data.push_back(n[0][1]);
mesh->normal.data.push_back(n[0][2]);
prim.normal.data.push_back(n[0][0]);
prim.normal.data.push_back(n[0][1]);
prim.normal.data.push_back(n[0][2]);
mesh->normal.data.push_back(n[1][0]);
mesh->normal.data.push_back(n[1][1]);
mesh->normal.data.push_back(n[1][2]);
prim.normal.data.push_back(n[1][0]);
prim.normal.data.push_back(n[1][1]);
prim.normal.data.push_back(n[1][2]);
mesh->normal.data.push_back(n[2][0]);
mesh->normal.data.push_back(n[2][1]);
mesh->normal.data.push_back(n[2][2]);
prim.normal.data.push_back(n[2][0]);
prim.normal.data.push_back(n[2][1]);
prim.normal.data.push_back(n[2][2]);
mesh->texcoords[0].data.push_back(tc[0][0]);
mesh->texcoords[0].data.push_back(tc[0][1]);
prim.texcoords[0].data.push_back(tc[0][0]);
prim.texcoords[0].data.push_back(tc[0][1]);
mesh->texcoords[0].data.push_back(tc[1][0]);
mesh->texcoords[0].data.push_back(tc[1][1]);
prim.texcoords[0].data.push_back(tc[1][0]);
prim.texcoords[0].data.push_back(tc[1][1]);
mesh->texcoords[0].data.push_back(tc[2][0]);
mesh->texcoords[0].data.push_back(tc[2][1]);
prim.texcoords[0].data.push_back(tc[2][0]);
prim.texcoords[0].data.push_back(tc[2][1]);
size_t idx = mesh->indices.size();
mesh->indices.push_back(int(idx) + 0);
mesh->indices.push_back(int(idx) + 1);
mesh->indices.push_back(int(idx) + 2);
size_t idx = prim.indices.size();
prim.indices.push_back(int(idx) + 0);
prim.indices.push_back(int(idx) + 1);
prim.indices.push_back(int(idx) + 2);
}
}
@ -935,16 +1019,17 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
maxn = std::max(vertex_skin_weights[i].weightValues.size(), maxn);
}
std::cout << "Max # of weights = " << maxn << "\n";
int num_slots = 0;
if (maxn > 0) {
num_slots = (((maxn - 1) / 4) + 1) * 4;
num_slots = maxn / 4;
}
std::cout << "# of slots = " << num_slots << "\n";
std::cout << "Max # of slots = " << num_slots << "\n";
for (size_t t = 0; t < size_t(num_slots); t++) {
VertexAttrib weights, joints;
size_t num_faceverts = mesh->indices.size();
size_t num_faceverts = prim.indices.size();
// facevarying weights/joints Fill with zeros
weights.data.resize(4 * num_faceverts, 0.0f);
@ -971,42 +1056,42 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
}
}
mesh->weights[t] = weights;
mesh->joints[t] = joints;
prim.weights[t] = weights;
prim.joints[t] = joints;
}
}
} else {
// position/texcoord/normal can be represented in shared vertex manner
mesh->position.data.clear();
prim.position.data.clear();
for (size_t v = 0; v < attrib.vertices.size(); v++) {
mesh->position.data.push_back(attrib.vertices[v]);
prim.position.data.push_back(attrib.vertices[v]);
}
mesh->normal.data.clear();
prim.normal.data.clear();
for (size_t v = 0; v < attrib.normals.size(); v++) {
mesh->normal.data.push_back(attrib.normals[v]);
prim.normal.data.push_back(attrib.normals[v]);
}
mesh->texcoords[0] = VertexAttrib();
prim.texcoords[0] = VertexAttrib();
for (size_t v = 0; v < attrib.texcoords.size(); v++) {
mesh->texcoords[0].data.push_back(attrib.texcoords[v]);
prim.texcoords[0].data.push_back(attrib.texcoords[v]);
}
mesh->indices_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT;
mesh->indices.clear();
prim.indices_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT;
prim.indices.clear();
size_t face_index_offset = 0;
for (size_t s = 0; s < shapes.size(); s++) {
const tinyobj::shape_t &shape = shapes[s];
for (size_t f = 0; f < shape.mesh.indices.size(); f++) {
mesh->indices.push_back(uint32_t(face_index_offset) +
prim.indices.push_back(uint32_t(face_index_offset) +
uint32_t(shape.mesh.indices[f].vertex_index));
}
face_index_offset = mesh->indices.size();
face_index_offset = prim.indices.size();
}
// weights/joints
@ -1017,9 +1102,10 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
maxn = std::max(attrib.skin_weights[i].weightValues.size(), maxn);
}
std::cout << "Max # of weights = " << maxn << "\n";
int num_slots = 0;
if (maxn > 0) {
num_slots = (((maxn - 1) / 4) + 1) * 4;
num_slots = maxn / 4;
}
std::cout << "# of slots = " << num_slots << "\n";
@ -1027,13 +1113,13 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
VertexAttrib weights, joints;
// Fill with zeros
weights.data.resize(4 * (mesh->position.data.size() / 3), 0.0f);
joints.data.resize(4 * (mesh->position.data.size() / 3), 0.0f);
weights.data.resize(4 * (prim.position.data.size() / 3), 0.0f);
joints.data.resize(4 * (prim.position.data.size() / 3), 0.0f);
for (size_t v = 0; v < attrib.skin_weights.size(); v++) {
const tinyobj::skin_weight_t &sw = attrib.skin_weights[v];
assert(sw.vertex_id < (mesh->position.data.size() / 3));
assert(sw.vertex_id < (prim.position.data.size() / 3));
size_t dst_vid = sw.vertex_id;
@ -1050,13 +1136,13 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
weights.data_type = TINYGLTF_TYPE_VEC4;
weights.component_type =
TINYGLTF_COMPONENT_TYPE_FLOAT; // storage format
mesh->weights[s] = weights;
prim.weights[s] = weights;
joints.data_type = TINYGLTF_TYPE_VEC4;
joints.component_type =
TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT; // storage format
mesh->joints[s] = joints;
prim.joints[s] = joints;
}
}
}
@ -1066,15 +1152,15 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
{
uint32_t minv = 0.0;
uint32_t maxv = 0.0;
for (size_t i = 0; i < mesh->indices.size(); i++) {
minv = std::min(minv, uint32_t(mesh->indices[i]));
maxv = std::max(maxv, uint32_t(mesh->indices[i]));
for (size_t i = 0; i < prim.indices.size(); i++) {
minv = std::min(minv, uint32_t(prim.indices[i]));
maxv = std::max(maxv, uint32_t(prim.indices[i]));
}
mesh->indices_min = int(minv);
mesh->indices_max = int(maxv);
prim.indices_min = int(minv);
prim.indices_max = int(maxv);
mesh->indices_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT;
prim.indices_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT;
}
{
@ -1083,25 +1169,25 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
for (size_t i = 0; i < mesh->position.data.size() / 3; i++) {
for (size_t i = 0; i < prim.position.data.size() / 3; i++) {
for (size_t k = 0; k < 3; k++) {
bmin[k] = std::min(bmin[k], mesh->position.data[3 * i + k]);
bmax[k] = std::max(bmax[k], mesh->position.data[3 * i + k]);
bmin[k] = std::min(bmin[k], prim.position.data[3 * i + k]);
bmax[k] = std::max(bmax[k], prim.position.data[3 * i + k]);
}
}
mesh->position.minValues.resize(3);
mesh->position.minValues[0] = bmin[0];
mesh->position.minValues[1] = bmin[1];
mesh->position.minValues[2] = bmin[2];
prim.position.minValues.resize(3);
prim.position.minValues[0] = bmin[0];
prim.position.minValues[1] = bmin[1];
prim.position.minValues[2] = bmin[2];
mesh->position.maxValues.resize(3);
mesh->position.maxValues[0] = bmax[0];
mesh->position.maxValues[1] = bmax[1];
mesh->position.maxValues[2] = bmax[2];
prim.position.maxValues.resize(3);
prim.position.maxValues[0] = bmax[0];
prim.position.maxValues[1] = bmax[1];
prim.position.maxValues[2] = bmax[2];
mesh->position.data_type = TINYGLTF_TYPE_VEC3;
mesh->position.component_type = TINYGLTF_COMPONENT_TYPE_FLOAT;
prim.position.data_type = TINYGLTF_TYPE_VEC3;
prim.position.component_type = TINYGLTF_COMPONENT_TYPE_FLOAT;
}
{
@ -1110,57 +1196,57 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
for (size_t i = 0; i < mesh->normal.data.size() / 3; i++) {
for (size_t i = 0; i < prim.normal.data.size() / 3; i++) {
for (size_t k = 0; k < 3; k++) {
bmin[k] = std::min(bmin[k], mesh->normal.data[3 * i + k]);
bmax[k] = std::max(bmax[k], mesh->normal.data[3 * i + k]);
bmin[k] = std::min(bmin[k], prim.normal.data[3 * i + k]);
bmax[k] = std::max(bmax[k], prim.normal.data[3 * i + k]);
}
}
mesh->normal.minValues.resize(3);
mesh->normal.minValues[0] = bmin[0];
mesh->normal.minValues[1] = bmin[1];
mesh->normal.minValues[2] = bmin[2];
prim.normal.minValues.resize(3);
prim.normal.minValues[0] = bmin[0];
prim.normal.minValues[1] = bmin[1];
prim.normal.minValues[2] = bmin[2];
mesh->normal.maxValues.resize(3);
mesh->normal.maxValues[0] = bmax[0];
mesh->normal.maxValues[1] = bmax[1];
mesh->normal.maxValues[2] = bmax[2];
prim.normal.maxValues.resize(3);
prim.normal.maxValues[0] = bmax[0];
prim.normal.maxValues[1] = bmax[1];
prim.normal.maxValues[2] = bmax[2];
mesh->normal.data_type = TINYGLTF_TYPE_VEC3;
mesh->normal.component_type = TINYGLTF_COMPONENT_TYPE_FLOAT;
prim.normal.data_type = TINYGLTF_TYPE_VEC3;
prim.normal.component_type = TINYGLTF_COMPONENT_TYPE_FLOAT;
}
{
float bmin[4];
float bmax[4];
bmin[0] = bmin[1] = bmin[2] = bmin[3] = std::numeric_limits<float>::max();
bmax[0] = bmax[1] = bmax[2] = bmin[3] =
bmax[0] = bmax[1] = bmax[2] = bmax[3] =
-std::numeric_limits<float>::max();
size_t n = 3;
for (size_t i = 0; i < mesh->tangent.data.size() / n; i++) {
for (size_t i = 0; i < prim.tangent.data.size() / n; i++) {
for (size_t k = 0; k < n; k++) {
bmin[k] = std::min(bmin[k], mesh->tangent.data[n * i + k]);
bmax[k] = std::max(bmax[k], mesh->tangent.data[n * i + k]);
bmin[k] = std::min(bmin[k], prim.tangent.data[n * i + k]);
bmax[k] = std::max(bmax[k], prim.tangent.data[n * i + k]);
}
}
mesh->tangent.minValues.resize(n);
mesh->tangent.maxValues.resize(n);
prim.tangent.minValues.resize(n);
prim.tangent.maxValues.resize(n);
for (size_t k = 0; k < n; k++) {
mesh->tangent.minValues[k] = bmin[k];
mesh->tangent.maxValues[k] = bmax[k];
prim.tangent.minValues[k] = bmin[k];
prim.tangent.maxValues[k] = bmax[k];
}
mesh->tangent.data_type =
prim.tangent.data_type =
(n == 3) ? TINYGLTF_TYPE_VEC3 : TINYGLTF_TYPE_VEC4;
mesh->tangent.component_type = TINYGLTF_COMPONENT_TYPE_FLOAT;
prim.tangent.component_type = TINYGLTF_COMPONENT_TYPE_FLOAT;
}
// texcoord
for (auto &item : mesh->texcoords) {
for (auto &item : prim.texcoords) {
float bmin[2];
float bmax[2];
bmin[0] = bmin[1] = std::numeric_limits<float>::max();
@ -1185,22 +1271,32 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
}
// joints
for (auto &item : mesh->joints) {
float bmin;
float bmax;
bmin = std::numeric_limits<float>::max();
bmax = -std::numeric_limits<float>::max();
for (auto &item : prim.joints) {
for (size_t i = 0; i < item.second.data.size(); i++) {
bmin = std::min(bmin, item.second.data[i]);
bmax = std::max(bmax, item.second.data[i]);
std::cout << "joint -- " << item.first << "\n";
float bmin[4];
float bmax[4];
bmin[0] = bmin[1] = bmin[2] = bmin[3] = float(std::numeric_limits<uint16_t>::max());
bmax[0] = bmax[1] = bmax[2] = bmax[3] =
float(-std::numeric_limits<uint16_t>::max());
size_t n = 4;
for (size_t i = 0; i < item.second.data.size() / n; i++) {
for (size_t k = 0; k < n; k++) {
bmin[k] = std::min(bmin[k], item.second.data[n * i + k]);
bmax[k] = std::max(bmax[k], item.second.data[n * i + k]);
}
}
item.second.minValues.resize(1);
item.second.maxValues.resize(1);
item.second.minValues[0] = bmin;
item.second.maxValues[0] = bmax;
// TODO(syoyo): check if the value is within ushort max
item.second.minValues.resize(n);
item.second.maxValues.resize(n);
for (size_t k = 0; k < n; k++) {
item.second.minValues[k] = bmin[k];
item.second.maxValues[k] = bmax[k];
}
item.second.data_type = TINYGLTF_TYPE_VEC4;
item.second.component_type =
@ -1208,22 +1304,29 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
}
// weights
for (auto &item : mesh->weights) {
float bmin;
float bmax;
bmin = std::numeric_limits<float>::max();
bmax = -std::numeric_limits<float>::max();
for (auto &item : prim.weights) {
for (size_t i = 0; i < item.second.data.size(); i++) {
bmin = std::min(bmin, item.second.data[i]);
bmax = std::max(bmax, item.second.data[i]);
float bmin[4];
float bmax[4];
bmin[0] = bmin[1] = bmin[2] = bmin[3] = std::numeric_limits<float>::max();
// do not allow negative weight
bmax[0] = bmax[1] = bmax[2] = bmax[3] = 0.0f;
size_t n = 4;
for (size_t i = 0; i < item.second.data.size() / n; i++) {
for (size_t k = 0; k < 4; k++) {
bmin[k] = std::min(bmin[k], item.second.data[n * i + k]);
bmax[k] = std::max(bmax[k], item.second.data[n * i + k]);
}
}
item.second.minValues.resize(1);
item.second.maxValues.resize(1);
item.second.minValues[0] = bmin;
item.second.maxValues[0] = bmax;
item.second.minValues.resize(n);
item.second.maxValues.resize(n);
for (size_t k = 0; k < n; k++) {
item.second.minValues[k] = bmin[k];
item.second.maxValues[k] = bmax[k];
}
item.second.data_type = TINYGLTF_TYPE_VEC4;
item.second.component_type =
@ -1231,83 +1334,91 @@ bool LoadObjMesh(const std::string &filename, bool facevarying,
}
}
prim.mode = TINYGLTF_MODE_TRIANGLES;
mesh->prims.clear();
mesh->prims.push_back(prim);
// Use filename as mesh's name
mesh->name = GetBaseFilename(filename);
mesh->mode = TINYGLTF_MODE_TRIANGLES;
return true;
}
void PrintMeshPrim(const MeshPrim &mesh) {
for (size_t p = 0; p < mesh.prims.size(); p++) {
const PrimSet &prim = mesh.prims[p];
std::cout << "--- primitive[" << p << "] ---\n";
std::cout << "indices.component_type : "
<< PrintComponentType(mesh.indices_type) << "\n";
std::cout << "# of indices : " << mesh.indices.size() << "\n";
std::cout << " indices.min = " << mesh.indices_min
<< ", max = " << mesh.indices_max << "\n";
for (size_t i = 0; i < mesh.indices.size(); i++) {
std::cout << " index[" << i << "] = " << mesh.indices[i] << "\n";
<< PrintComponentType(prim.indices_type) << "\n";
std::cout << "# of indices : " << prim.indices.size() << "\n";
std::cout << " indices.min = " << prim.indices_min
<< ", max = " << prim.indices_max << "\n";
for (size_t i = 0; i < prim.indices.size(); i++) {
std::cout << " index[" << i << "] = " << prim.indices[i] << "\n";
}
std::cout << "position.type : " << PrintType(mesh.position.data_type) << "\n";
std::cout << "position.type : " << PrintType(prim.position.data_type) << "\n";
std::cout << "position.component_type : "
<< PrintComponentType(mesh.position.component_type) << "\n";
std::cout << "# of positions : " << mesh.position.data.size() / 3 << "\n";
if ((mesh.position.minValues.size() == 3) &&
(mesh.position.maxValues.size() == 3)) {
std::cout << " position.min = " << mesh.position.minValues
<< ", max = " << mesh.position.maxValues << "\n";
<< PrintComponentType(prim.position.component_type) << "\n";
std::cout << "# of positions : " << prim.position.data.size() / 3 << "\n";
if ((prim.position.minValues.size() == 3) &&
(prim.position.maxValues.size() == 3)) {
std::cout << " position.min = " << prim.position.minValues
<< ", max = " << prim.position.maxValues << "\n";
}
for (size_t i = 0; i < mesh.position.data.size() / 3; i++) {
std::cout << " position[" << i << "] = " << mesh.position.data[3 * i + 0]
<< ", " << mesh.position.data[3 * i + 1] << ", "
<< mesh.position.data[3 * i + 2] << std::endl;
for (size_t i = 0; i < prim.position.data.size() / 3; i++) {
std::cout << " position[" << i << "] = " << prim.position.data[3 * i + 0]
<< ", " << prim.position.data[3 * i + 1] << ", "
<< prim.position.data[3 * i + 2] << std::endl;
}
std::cout << "normal.type : " << PrintType(mesh.normal.data_type) << "\n";
std::cout << "normal.type : " << PrintType(prim.normal.data_type) << "\n";
std::cout << "normal.component_type : "
<< PrintComponentType(mesh.normal.component_type) << "\n";
std::cout << "# of normals : " << mesh.normal.data.size() / 3 << "\n";
if ((mesh.normal.minValues.size() == 3) &&
(mesh.normal.maxValues.size() == 3)) {
std::cout << " normal.min = " << mesh.normal.minValues
<< ", max = " << mesh.normal.maxValues << "\n";
<< PrintComponentType(prim.normal.component_type) << "\n";
std::cout << "# of normals : " << prim.normal.data.size() / 3 << "\n";
if ((prim.normal.minValues.size() == 3) &&
(prim.normal.maxValues.size() == 3)) {
std::cout << " normal.min = " << prim.normal.minValues
<< ", max = " << prim.normal.maxValues << "\n";
}
for (size_t i = 0; i < mesh.normal.data.size() / 3; i++) {
std::cout << " normal[" << i << "] = " << mesh.normal.data[3 * i + 0]
<< ", " << mesh.normal.data[3 * i + 1] << ", "
<< mesh.normal.data[3 * i + 2] << std::endl;
for (size_t i = 0; i < prim.normal.data.size() / 3; i++) {
std::cout << " normal[" << i << "] = " << prim.normal.data[3 * i + 0]
<< ", " << prim.normal.data[3 * i + 1] << ", "
<< prim.normal.data[3 * i + 2] << std::endl;
}
if (mesh.tangent.data.size() > 0) {
assert((mesh.tangent.data_type == TINYGLTF_TYPE_VEC3) ||
(mesh.tangent.data_type == TINYGLTF_TYPE_VEC4));
if (prim.tangent.data.size() > 0) {
assert((prim.tangent.data_type == TINYGLTF_TYPE_VEC3) ||
(prim.tangent.data_type == TINYGLTF_TYPE_VEC4));
size_t n = mesh.tangent.data_type == TINYGLTF_TYPE_VEC3 ? 3 : 4;
size_t n = prim.tangent.data_type == TINYGLTF_TYPE_VEC3 ? 3 : 4;
std::cout << "tangent.type : " << PrintType(mesh.tangent.data_type) << "\n";
std::cout << "tangent.type : " << PrintType(prim.tangent.data_type) << "\n";
std::cout << "tangent.component_type : "
<< PrintComponentType(mesh.tangent.component_type) << "\n";
std::cout << "# of tangents : " << mesh.tangent.data.size() / n << "\n";
if ((mesh.tangent.minValues.size() == 3) &&
(mesh.tangent.maxValues.size() == 3)) {
std::cout << " tangent.min = " << mesh.tangent.minValues
<< ", max = " << mesh.tangent.maxValues << "\n";
<< PrintComponentType(prim.tangent.component_type) << "\n";
std::cout << "# of tangents : " << prim.tangent.data.size() / n << "\n";
if ((prim.tangent.minValues.size() == 3) &&
(prim.tangent.maxValues.size() == 3)) {
std::cout << " tangent.min = " << prim.tangent.minValues
<< ", max = " << prim.tangent.maxValues << "\n";
}
for (size_t i = 0; i < mesh.tangent.data.size() / n; i++) {
std::cout << " tangent[" << i << "] = " << mesh.tangent.data[n * i + 0]
<< ", " << mesh.tangent.data[n * i + 1] << ", "
<< mesh.tangent.data[n * i + 2];
for (size_t i = 0; i < prim.tangent.data.size() / n; i++) {
std::cout << " tangent[" << i << "] = " << prim.tangent.data[n * i + 0]
<< ", " << prim.tangent.data[n * i + 1] << ", "
<< prim.tangent.data[n * i + 2];
if (n == 4) {
std::cout << ", " << mesh.tangent.data[n * i + 3];
std::cout << ", " << prim.tangent.data[n * i + 3];
}
std::cout << std::endl;
}
}
std::cout << "# of texcoord slots : " << mesh.texcoords.size() << "\n";
for (const auto &item : mesh.texcoords) {
std::cout << "# of texcoord slots : " << prim.texcoords.size() << "\n";
for (const auto &item : prim.texcoords) {
std::cout << "TEXCOORD_" << item.first << "\n";
assert(item.second.data_type == TINYGLTF_TYPE_VEC2);
@ -1327,10 +1438,10 @@ void PrintMeshPrim(const MeshPrim &mesh) {
}
}
assert(mesh.joints.size() == mesh.weights.size());
std::cout << "# of joints/weights slots : " << mesh.joints.size() << "\n";
for (const auto &item : mesh.joints) {
assert(mesh.weights.count(item.first));
assert(prim.joints.size() == prim.weights.size());
std::cout << "# of joints/weights slots : " << prim.joints.size() << "\n";
for (const auto &item : prim.joints) {
assert(prim.weights.count(item.first));
assert(item.second.data_type == TINYGLTF_TYPE_VEC4);
@ -1349,7 +1460,7 @@ void PrintMeshPrim(const MeshPrim &mesh) {
<< "\n";
}
const VertexAttrib &attrib = mesh.weights.at(item.first);
const VertexAttrib &attrib = prim.weights.at(item.first);
// weight must be uint8 or uint16(normalized), or float
assert((attrib.component_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) ||
@ -1365,5 +1476,6 @@ void PrintMeshPrim(const MeshPrim &mesh) {
}
}
}
}
} // namespace example

View File

@ -28,9 +28,7 @@ struct VertexAttrib {
};
struct MeshPrim {
std::string name;
int32_t id{-1};
struct PrimSet {
int mode; // e.g. TRIANGLES
@ -49,14 +47,35 @@ struct MeshPrim {
int indices_max{-1};
int indices_type{-1}; // storage type(componentType) of `indices`.
std::vector<uint32_t> indices; // vertex indices
};
struct MeshPrim {
std::string name;
int32_t id{-1};
std::vector<PrimSet> prims;
};
struct ObjExportOption
{
bool export_skinweights{true};
int primid{0}; /// Primitive id to export(default 0).
int uvset{0}; /// Tex coord ID to export(default 0).
bool flip_texcoord_y{true}; /// Flip texture coordinate V?(default true).
};
///
/// Save MeshPrim as wavefront .obj
///
bool SaveAsObjMesh(const std::string &filename, const MeshPrim &mesh, bool flip_texcoord_y = true);
/// @param[in] basename Base filename. ".obj" will be appended.
/// @param[in] mesh MeshPrim.
/// @param[in] option Export options
///
bool SaveAsObjMesh(const std::string &basename, const MeshPrim &mesh, const ObjExportOption &option);
//
/// Save MeshPrim as glTF mesh
///
bool SaveAsGLTFMesh(const std::string &filename, const MeshPrim &mesh);

View File

@ -0,0 +1,18 @@
# concat_mesh.py
Append(merge) mesh of glTF A to glTF B.
`meshes`, `accessors`, `bufferViews`, `materials` of glTF A is appended to glTF B(index to accessor, bufferViews, etc will be recomputed).
`skin`, `nodes`, etc are not appended(to be merged).
## TODO
* [ ] Support multiple glTFs to merge
* [ ] Support merging skin
* [ ] Support merging different node hierarchies
* [ ] `images`, `textures`
# replace_attrib.py
Replace the accessor id of specified attribute.

View File

@ -0,0 +1,110 @@
# concat mesh to glTF
import json
import sys, os
prefix = "added/"
def main():
if len(sys.argv) < 4:
print("Needs source.gltf target.gltf output.gltf")
sys.exit(-1)
source_filename = sys.argv[1]
target_filename = sys.argv[2]
output_filename = sys.argv[3]
source = json.loads(open(source_filename).read())
target = json.loads(open(target_filename).read())
num_target_meshes = len(target["meshes"])
num_target_buffers = len(target["buffers"])
num_target_bufferViews = len(target["bufferViews"])
num_target_accessors = len(target["accessors"])
num_target_materials = len(target["materials"])
print("num_target_meshes: ", num_target_meshes)
print("num_target_buffers: ", num_target_buffers)
print("num_target_bufferViews: ", num_target_bufferViews)
print("num_target_accessors: ", num_target_accessors)
print("num_target_materials: ", num_target_accessors)
num_source_meshes = len(source["meshes"])
num_source_buffers = len(source["buffers"])
num_source_bufferViews = len(source["bufferViews"])
num_source_accessors = len(source["accessors"])
num_source_materials = len(source["materials"]) if "materials" in source else 0
print("num_source_meshes: ", num_source_meshes)
print("num_source_buffers: ", num_source_buffers)
print("num_source_bufferViews: ", num_source_bufferViews)
print("num_source_accessors: ", num_source_accessors)
print("num_source_materials: ", num_source_materials)
#
# Adjust name and index
#
for i in range(len(source["buffers"])):
if "name" in source["buffers"][i]:
source["buffers"][i]["name"] = prefix + source["buffers"][i]["name"]
for i in range(len(source["bufferViews"])):
if "name" in source["bufferViews"][i]:
source["bufferViews"][i]["name"] = prefix + source["bufferViews"][i]["name"]
source["bufferViews"][i]["buffer"] += num_target_buffers
for i in range(len(source["accessors"])):
if "name" in source["accessors"][i]:
source["accessors"][i]["name"] = prefix + source["accessors"][i]["name"]
source["accessors"][i]["bufferView"] += num_target_bufferViews
for i in range(len(source["meshes"])):
mesh = source["meshes"][i]
if "name" in mesh:
source["meshes"][i]["name"] = prefix + source["meshes"][i]["name"]
for primid in range(len(mesh["primitives"])):
for attrib in mesh["primitives"][primid]["attributes"]:
#print(source["meshes"][i]["primitives"][primid]["attributes"][attrib])
source["meshes"][i]["primitives"][primid]["attributes"][attrib] += num_target_accessors
source["meshes"][i]["primitives"][primid]["indices"] += num_target_accessors
if "material" in source["meshes"][i]["primitives"][primid]:
source["meshes"][i]["primitives"][primid]["material"] += num_target_materials
#
# Append mesh info
#
target["buffers"] += source["buffers"]
target["bufferViews"] += source["bufferViews"]
target["meshes"] += source["meshes"]
target["accessors"] += source["accessors"]
if "materials" in source:
target["materials"] += source["materials"]
#
# add some info
#
extraInfo = {}
extraInfo["num_target_meshes"] = num_target_meshes
extraInfo["num_target_buffers"] = num_target_buffers
extraInfo["num_target_bufferViews"] = num_target_bufferViews
extraInfo["num_target_accessors"] = num_target_accessors
extraInfo["num_target_materials"] = num_target_materials
extraInfo["num_source_meshes"] = num_source_meshes
extraInfo["num_source_buffers"] = num_source_buffers
extraInfo["num_source_bufferViews"] = num_source_bufferViews
extraInfo["num_source_accessors"] = num_source_accessors
extraInfo["num_source_materials"] = num_source_materials
target["asset"]["extras"] = extraInfo
with open(output_filename, "w") as f:
f.write(json.dumps(target, indent=2))
print("Merged glTF was exported to : ", output_filename)
main()

View File

@ -0,0 +1,97 @@
# Replace accessor id of attributes for speicified mesh
# Usually called after concat_mesh.py
# Example usecase is to replace UV coordinate of a mesh.
import json
import sys, os
attrib_names = ["TEXCOORD_0"]
def check_accessor(src, target):
if src["componentType"] != target["componentType"]:
print("componentType mismatch!")
return False
if src["count"] != target["count"]:
print("`count` mismatch!")
return False
if src["type"] != target["type"]:
print("`type` mismatch!")
return False
return True
def main():
if len(sys.argv) < 5:
print("Needs input.gltf output.gltf source_mesh_name target_mesh_name <source_primid> <target_primid>")
sys.exit(-1)
input_filename = sys.argv[1]
output_filename = sys.argv[2]
source_mesh_name = sys.argv[3]
target_mesh_name = sys.argv[4]
source_primid = 0
target_primid = 0
if len(sys.argv) > 5:
source_primid = int(sys.argv[5])
if len(sys.argv) > 6:
target_primid = int(sys.argv[6])
gltf = json.loads(open(input_filename).read())
source_mesh_id = -1
target_mesh_id = -1
for i in range(len(gltf["meshes"])):
mesh = gltf["meshes"][i]
print("mesh[{}].name = {}".format(i, mesh["name"]))
if target_mesh_name == mesh["name"]:
target_mesh_id = i
if source_mesh_name == mesh["name"]:
source_mesh_id = i
if source_mesh_id == -1:
print("source mesh with name [{}] not found.".format(source_mesh_name))
sys.exit(-1)
if target_mesh_id == -1:
print("target mesh with name [{}] not found.".format(target_mesh_name))
sys.exit(-1)
print("target: name = {}, id = {}".format(target_mesh_name, target_mesh_id))
print("source: name = {}, id = {}".format(source_mesh_name, source_mesh_id))
source_mesh = gltf["meshes"][source_mesh_id]
target_mesh = gltf["meshes"][target_mesh_id]
source_prim = source_mesh["primitives"][source_primid]
target_prim = target_mesh["primitives"][target_primid]
for attrib in target_prim["attributes"]:
print("attrib ", attrib)
if attrib in attrib_names:
if attrib in source_prim["attributes"]:
target_accessor_id = target_prim["attributes"][attrib]
src_accessor_id = source_prim["attributes"][attrib]
if check_accessor(gltf["accessors"][src_accessor_id], gltf["accessors"][target_accessor_id]):
gltf["meshes"][target_mesh_id]["primitives"][target_primid]["attributes"][attrib] = src_accessor_id
print("Replaced accessor id for attrib {} from {} to {}".format(attrib, target_accessor_id, src_accessor_id))
else:
print("Accessor type/format is not identical. Skip replace")
print(" attrib {}".format(attrib))
else:
print("attribute[{}] not found in source primitive: mesh[{}].primitives[{}]".format(attrib, source_mesh_name, source_primid))
with open(output_filename, "w") as f:
f.write(json.dumps(gltf, indent=2))
print("Merged glTF was exported to : ", output_filename)
main()