Merge remote-tracking branch 'origin/devel' into generic_extension_support

This commit is contained in:
Selmar Kok 2018-03-26 16:22:18 +02:00
commit 5210f1539e
8 changed files with 2200 additions and 84 deletions

View File

@ -47,13 +47,20 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch.
* [glview](examples/glview) : Simple glTF geometry viewer.
* [validator](examples/validator) : Simple glTF validator with JSON schema.
## Projects using TinyGLTF
* Physical based rendering with Vulkan using glTF 2.0 models https://github.com/SaschaWillems/Vulkan-glTF-PBR
* GLTF loader plugin for OGRE 2.1. Support for PBR materials via HLMS/PBS https://github.com/Ybalrid/Ogre_glTF
* Your projects here!(Plese send PR)
## TODOs
* [ ] Write C++ code generator from json schema for robust parsing.
* [ ] Write C++ code generator from jSON schema for robust parsing.
* [x] Serialization
* [ ] Compression/decompression(Open3DGC, etc)
* [ ] Support `extensions` and `extras` property
* [ ] HDR image?
* [ ] OpenEXR extension through TinyEXR.
* [ ] Write tests for `animation` and `skin`
## Licenses

View File

@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.6)
project(gltfutil)
set(CMAKE_CXX_STANDARD 11)
include_directories(../../)
file(GLOB gltfutil_sources *.cc *.h)
add_executable(gltfutil ${gltfutil_sources})

View File

@ -0,0 +1,60 @@
#include <algorithm>
#include <array>
#include <fstream>
#include <iostream>
#include <string>
#include "texture_dumper.h"
namespace gltfutil {
enum class ui_mode { cli, interactive };
enum class cli_action { not_set, help, dump };
enum class FileType { Ascii, Binary, Unknown };
/// Probe inside the file, or check the extension to determine if we have to
/// load a text file, or a binary file
FileType detectType(const std::string& path) {
// Quickly open the file as binary and chekc if there's the gltf binary magic
// number
{
auto probe = std::ifstream(path, std::ios_base::binary);
if (!probe) throw std::runtime_error("Could not open " + path);
std::array<char, 5> buffer;
for (size_t i{0}; i < 4; ++i) probe >> buffer[i];
buffer[4] = 0;
if (std::string("glTF") == std::string(buffer.data())) {
std::cout
<< "Detected binary file thanks to the magic number at the start!\n";
return FileType::Binary;
}
}
// If we don't have any better, check the file extension.
auto extension = path.substr(path.find_last_of('.') + 1);
std::transform(std::begin(extension), std::end(extension),
std::begin(extension),
[](char c) { return char(::tolower(int(c))); });
if (extension == "gltf") return FileType::Ascii;
if (extension == "glb") return FileType::Binary;
return FileType::Unknown;
}
struct configuration {
std::string input_path, output_dir;
ui_mode mode;
cli_action action = cli_action::not_set;
texture_dumper::texture_output_format requested_format =
texture_dumper::texture_output_format::not_specified;
bool has_output_dir;
bool is_valid() {
// TODO impl check
return true;
}
};
} // namespace gltfutil

123
examples/gltfutil/main.cc Normal file
View File

@ -0,0 +1,123 @@
#include <iostream>
#include <string>
#include "gltfuilconfig.h"
#include "texture_dumper.h"
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#include <tiny_gltf.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
namespace gltfutil {
int usage(int ret = 0) {
using std::cout;
cout << "gltfutil: tool for manipulating gltf files\n"
<< " usage information:\n\n"
<< "\t gltfutil (-d|-h|) (-f [png|bmp|tga]) [path to .gltf/glb] (-o "
"[path to output directory])\n\n"
//<< "\t\t -i: start in interactive mode\n"
<< "\t\t -d: dump enclosed content (image assets)\n"
<< "\t\t -f: file format for image output"
<< "\t\t -o: ouptput directory path"
<< "\t\t -h: print this help\n";
return ret;
}
int arg_error() {
(void)usage();
return -1;
}
int parse_args(int argc, char** argv) {
gltfutil::configuration config;
for (size_t i = 1; i < size_t(argc); ++i) {
char* arg = argv[i];
if (arg[0] == '-') switch (arg[1]) {
case 'h':
return usage(0);
break;
case 'd':
config.mode = ui_mode::cli;
config.action = cli_action::dump;
break;
case 'i':
config.mode = ui_mode::interactive;
break;
case 'o':
i++;
config.output_dir = argv[i];
break;
case 'f':
i++;
config.requested_format =
texture_dumper::get_fromat_from_string(argv[i]);
break;
default:
return arg_error();
}
else {
// set the input path
config.input_path = argv[i];
}
}
if (config.is_valid()) {
tinygltf::TinyGLTF loader;
tinygltf::Model model;
std::string error;
bool state;
switch (detectType(config.input_path)) {
case FileType::Ascii:
state = loader.LoadASCIIFromFile(&model, &error, config.input_path);
break;
case FileType::Binary:
state = loader.LoadBinaryFromFile(&model, &error, config.input_path);
break;
case FileType::Unknown:
default:
return arg_error();
break;
}
std::cerr << "state is " << state << '\n';
if (config.mode == ui_mode::interactive) {
// interactive usage now;
// TODO impl
} else {
switch (config.action) {
case cli_action::help:
return usage();
break;
case cli_action::dump: {
texture_dumper dumper(model);
if (config.requested_format !=
texture_dumper::texture_output_format::not_specified)
dumper.set_output_format(config.requested_format);
if (config.output_dir.empty())
dumper.dump_to_folder();
else
dumper.dump_to_folder(config.output_dir);
} break;
default:
return arg_error();
}
}
} else {
return arg_error();
}
return 0;
}
} // namespace gltfutil
int main(int argc, char* argv[]) {
if (argc == 1) return gltfutil::usage();
if (argc > 1) return gltfutil::parse_args(argc, argv);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
#include <iostream>
#include "stb_image_write.h"
#include "texture_dumper.h"
#include <tiny_gltf.h>
using namespace gltfutil;
using namespace tinygltf;
using std::cout;
texture_dumper::texture_dumper(const Model& input)
: model(input), configured_format(texture_output_format::png) {
cout << "Texture dumper\n";
}
void texture_dumper::dump_to_folder(const std::string& path) {
cout << "dumping to folder " << path << '\n';
cout << "model file has " << model.textures.size() << " textures.\n";
size_t index = 0;
for (const auto& texture : model.textures) {
index++;
const auto& image = model.images[texture.source];
cout << "image name is: \"" << image.name << "\"\n";
cout << "image size is: " << image.width << 'x' << image.height << '\n';
cout << "pixel channel count :" << image.component << '\n';
std::string name = image.name.empty() ? std::to_string(index) : image.name;
switch (configured_format) {
case texture_output_format::png:
name = path + "/" + name + ".png";
std::cout << "Image will be written to " << name << '\n';
stbi_write_png(name.c_str(), image.width, image.height, image.component,
image.image.data(), 0);
break;
case texture_output_format::bmp:
std::cout << "Image will be written to " << name << '\n';
name = path + "/" + name + ".bmp";
stbi_write_bmp(name.c_str(), image.width, image.height, image.component,
image.image.data());
break;
case texture_output_format::tga:
std::cout << "Image will be written to " << name << '\n';
name = path + "/" + name + ".tga";
stbi_write_tga(name.c_str(), image.width, image.height, image.component,
image.image.data());
break;
}
}
}
void texture_dumper::set_output_format(texture_output_format format) {
configured_format = format;
}
texture_dumper::texture_output_format texture_dumper::get_fromat_from_string(
const std::string& str) {
std::string type = str;
std::transform(str.begin(), str.end(), type.begin(), ::tolower);
if (type == "png") return texture_output_format::png;
if (type == "bmp") return texture_output_format::bmp;
if (type == "tga") return texture_output_format::tga;
return texture_output_format::not_specified;
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <tiny_gltf.h>
namespace gltfutil {
class texture_dumper {
public:
enum class texture_output_format { png, bmp, tga, not_specified };
private:
const tinygltf::Model& model;
texture_output_format configured_format;
public:
texture_dumper(const tinygltf::Model& inputModel);
void dump_to_folder(const std::string& path = "./");
void set_output_format(texture_output_format format);
static texture_output_format get_fromat_from_string(const std::string& str);
};
} // namespace gltfutil

View File

@ -734,9 +734,9 @@ typedef bool (*LoadImageDataFunction)(Image *, std::string *, int, int,
#ifndef TINYGLTF_NO_STB_IMAGE
// Declaration of default image loader callback
static bool LoadImageData(Image *image, std::string *err, int req_width,
int req_height, const unsigned char *bytes, int size,
void *);
bool LoadImageData(Image *image, std::string *err, int req_width,
int req_height, const unsigned char *bytes, int size,
void *);
#endif
class TinyGLTF {
@ -1199,9 +1199,9 @@ void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) {
}
#ifndef TINYGLTF_NO_STB_IMAGE
static bool LoadImageData(Image *image, std::string *err, int req_width,
int req_height, const unsigned char *bytes, int size,
void *) {
bool LoadImageData(Image *image, std::string *err, int req_width,
int req_height, const unsigned char *bytes, int size,
void *) {
int w, h, comp;
// if image cannot be decoded, ignore parsing and keep it by its path
// don't break in this case
@ -1736,100 +1736,97 @@ static bool ParseImage(Image *image, std::string *err, const json &o,
LoadImageDataFunction *LoadImageData = nullptr,
void *user_data = nullptr) {
// A glTF image must either reference a bufferView or an image uri
double bufferView = -1;
bool isEmbedded =
ParseNumberProperty(&bufferView, err, o, "bufferView", false);
std::string uri;
std::string tmp_err;
if (!ParseStringProperty(&uri, &tmp_err, o, "uri", false) && !isEmbedded) {
// schema says oneOf [`bufferView`, `uri`]
// TODO(syoyo): Check the type of each parameters.
bool hasBufferView = (o.find("bufferView") != o.end());
bool hasURI = (o.find("uri") != o.end());
if (hasBufferView && hasURI) {
// Should not both defined.
if (err) {
(*err) += "`bufferView` or `uri` required for Image.\n";
(*err) += "Only one of `bufferView` or `uri` should be defined, but both are defined for Image.\n";
}
return false;
}
if (!hasBufferView && !hasURI) {
if (err) {
(*err) += "Neither required `bufferView` nor `uri` defined for Image.\n";
}
return false;
}
ParseStringProperty(&image->name, err, o, "name", false);
if (hasBufferView) {
double bufferView = -1;
if (!ParseNumberProperty(&bufferView, err, o, "bufferView", true)) {
if (err) {
(*err) += "Failed to parse `bufferView` for Image.\n";
}
return false;
}
std::string mime_type;
ParseStringProperty(&mime_type, err, o, "mimeType", false);
double width = 0.0;
ParseNumberProperty(&width, err, o, "width", false);
double height = 0.0;
ParseNumberProperty(&height, err, o, "height", false);
// Just only save some information here. Loading actual image data from
// bufferView is done after this `ParseImage` function.
image->bufferView = static_cast<int>(bufferView);
image->mimeType = mime_type;
image->width = static_cast<int>(width);
image->height = static_cast<int>(height);
return true;
}
// Parse URI & Load image data.
std::string uri;
std::string tmp_err;
if (!ParseStringProperty(&uri, &tmp_err, o, "uri", true)) {
if (err) {
(*err) += "Failed to parse `uri` for Image.\n";
}
return false;
}
std::vector<unsigned char> img;
if (is_binary) {
// Still binary glTF accepts external dataURI. First try external resources.
bool loaded = false;
if (IsDataURI(uri)) {
loaded = DecodeDataURI(&img, uri, 0, false);
} else {
// Assume external file
// Keep texture path (for textures that cannot be decoded)
image->uri = uri;
#ifdef TINYGLTF_NO_EXTERNAL_IMAGE
return true;
#endif
loaded = LoadExternalFile(&img, err, uri, basedir, 0, false);
}
if (!loaded) {
// load data from (embedded) binary data
if ((bin_size == 0) || (bin_data == nullptr)) {
if (err) {
(*err) += "Invalid binary data.\n";
}
return false;
if (IsDataURI(uri)) {
if (!DecodeDataURI(&img, uri, 0, false)) {
if (err) {
(*err) += "Failed to decode 'uri' for image parameter.\n";
}
double buffer_view = -1.0;
if (!ParseNumberProperty(&buffer_view, err, o, "bufferView", true,
"Image")) {
return false;
}
std::string mime_type;
ParseStringProperty(&mime_type, err, o, "mimeType", false);
double width = 0.0;
ParseNumberProperty(&width, err, o, "width", false);
double height = 0.0;
ParseNumberProperty(&height, err, o, "height", false);
// Just only save some information here. Loading actual image data from
// bufferView is done in other place.
image->bufferView = static_cast<int>(buffer_view);
image->mimeType = mime_type;
image->width = static_cast<int>(width);
image->height = static_cast<int>(height);
return true;
return false;
}
} else {
if (IsDataURI(uri)) {
if (!DecodeDataURI(&img, uri, 0, false)) {
if (err) {
(*err) += "Failed to decode 'uri' for image parameter.\n";
}
return false;
}
} else {
// Assume external file
// Keep texture path (for textures that cannot be decoded)
image->uri = uri;
// Assume external file
// Keep texture path (for textures that cannot be decoded)
image->uri = uri;
#ifdef TINYGLTF_NO_EXTERNAL_IMAGE
return true;
return true;
#endif
if (!LoadExternalFile(&img, err, uri, basedir, 0, false)) {
if (err) {
(*err) += "Failed to load external 'uri' for image parameter\n";
}
// If the image cannot be loaded, keep uri as image->uri.
return true;
if (!LoadExternalFile(&img, err, uri, basedir, 0, false)) {
if (err) {
(*err) += "Failed to load external 'uri' for image parameter\n";
}
if (img.empty()) {
if (err) {
(*err) += "Image is empty.\n";
}
return false;
// If the image cannot be loaded, keep uri as image->uri.
return true;
}
if (img.empty()) {
if (err) {
(*err) += "Image is empty.\n";
}
return false;
}
}