harfang3d/tools/gltf_converter/gltf_exporter.cpp
2022-09-26 12:07:03 +02:00

1205 lines
42 KiB
C++
Raw Blame History

// HARFANG(R) Copyright (C) 2021 Emmanuel Julien, NWNC HARFANG. Released under GPL/LGPL/Commercial Licence, see licence.txt for details.
#include "engine/assets.h"
#include <engine/vertex.h>
#include <engine/geometry.h>
#include <engine/model_builder.h>
#include <engine/node.h>
#include <engine/physics.h>
#include <engine/render_pipeline.h>
#include <engine/forward_pipeline.h>
#include <engine/scene.h>
#include <foundation/build_info.h>
#include <foundation/cext.h>
#include <foundation/cmd_line.h>
#include <foundation/dir.h>
#include <foundation/format.h>
#include <foundation/log.h>
#include <foundation/math.h>
#include <foundation/matrix3.h>
#include <foundation/matrix4.h>
#include <foundation/pack_float.h>
#include <foundation/path_tools.h>
#include <foundation/projection.h>
#include <foundation/string.h>
#include <foundation/time.h>
#include <foundation/vector3.h>
#include "stb_image.h"
#include "tiny_gltf.h"
#include <fstream>
#include <iostream>
#include <mutex>
#undef CopyFile
#undef GetObject
#include <foundation/file.h>
#include "platform/window_system.h"
#include "limits.h"
#include <vector>
using namespace tinygltf;
//
static inline const hg::Material::Value *GetMaterialValue(const hg::Material &mat, const std::string &name) {
const auto &i = mat.values.find(name);
return i != std::end(mat.values) ? &i->second : (hg::Material::Value *)nullptr;
}
std::map<hg::NodeRef, int> NodeRef_to_IdNode;
/// Adapts an array of bytes to an array of T. Will advace of byte_stride each
/// elements.
template <typename T> struct arrayAdapter {
/// Pointer to the bytes
const unsigned char *dataPtr;
/// Number of elements in the array
const size_t elemCount;
/// Stride in bytes between two elements
const size_t stride;
/// Construct an array adapter.
/// \param ptr Pointer to the start of the data, with offset applied
/// \param count Number of elements in the array
/// \param byte_stride Stride betweens elements in the array
arrayAdapter(const unsigned char *ptr, size_t count, size_t byte_stride) : dataPtr(ptr), elemCount(count), stride(byte_stride) {}
/// Returns a *copy* of a single element. Can't be used to modify it.
T operator[](size_t pos) const {
if (pos >= elemCount)
throw std::out_of_range("Tried to access beyond the last element of an array adapter with "
"count " +
std::to_string(elemCount) + " while getting elemnet number " + std::to_string(pos));
return *(reinterpret_cast<const T *>(dataPtr + pos * stride));
}
};
/// Interface of any adapted array that returns integer data
struct intArrayBase {
virtual ~intArrayBase() = default;
virtual unsigned int operator[](size_t) const = 0;
virtual size_t size() const = 0;
};
/// Interface of any adapted array that returns float data
struct floatArrayBase {
virtual ~floatArrayBase() = default;
virtual float operator[](size_t) const = 0;
virtual size_t size() const = 0;
};
/// An array that loads interger types, returns them as int
template <class T> struct intArray : public intArrayBase {
arrayAdapter<T> adapter;
explicit intArray(const arrayAdapter<T> &a) : adapter(a) {}
unsigned int operator[](size_t position) const override { return static_cast<unsigned int>(adapter[position]); }
size_t size() const override { return adapter.elemCount; }
};
template <class T> struct floatArray : public floatArrayBase {
arrayAdapter<T> adapter;
explicit floatArray(const arrayAdapter<T> &a) : adapter(a) {}
float operator[](size_t position) const override { return static_cast<float>(adapter[position]); }
size_t size() const override { return adapter.elemCount; }
};
struct v2fArray {
arrayAdapter<hg::Vec2> adapter;
explicit v2fArray(const arrayAdapter<hg::Vec2> &a) : adapter(a) {}
hg::Vec2 operator[](size_t position) const { return adapter[position]; }
size_t size() const { return adapter.elemCount; }
};
struct v3fArray {
arrayAdapter<hg::Vec3> adapter;
explicit v3fArray(const arrayAdapter<hg::Vec3> &a) : adapter(a) {}
hg::Vec3 operator[](size_t position) const { return adapter[position]; }
size_t size() const { return adapter.elemCount; }
};
struct v4fArray {
arrayAdapter<hg::Vec4> adapter;
explicit v4fArray(const arrayAdapter<hg::Vec4> &a) : adapter(a) {}
hg::Vec4 operator[](size_t position) const { return adapter[position]; }
size_t size() const { return adapter.elemCount; }
};
struct Config {
std::string input_path;
std::string name; // output name (may be empty)
std::string base_output_path{"./"};
std::string compiled_output_path{"./"};
std::string source_output_path{"./"};
bool binary;
bool filter_disabled_node;
};
static bool GetOutputPath(std::string &path, const std::string &base, const std::string &name, const std::string &prefix, const std::string &ext) {
if (base.empty())
return false;
const auto filename = name.empty() ? prefix : (prefix.empty() ? name : prefix + "-" + name);
path = hg::CleanPath(base + "/" + filename + "." + ext);
return true;
}
#define for_with_index(V, ...) \
for (size_t V = 0, _brk_ = 0, _i_ = 1; _i_; _i_ = 0) \
for (__VA_ARGS__) \
if (_brk_) \
break; \
else \
for (_brk_ = 1; _brk_; _brk_ = 0, V++)
template <typename T> uint32_t serialize(std::vector<T> const &from, std::vector<unsigned char> &to, std::size_t offset) {
uint32_t bytesToSerialize = sizeof(T) * static_cast<uint32_t>(from.size());
to.resize(to.size() + bytesToSerialize);
std::memcpy(&to[offset], &from[0], bytesToSerialize);
return bytesToSerialize;
}
std::string GetTargetPathGltf(const std::string &hg_target) {
// get target
if (hg_target == "Position")
return "translation";
else if (hg_target == "Rotation")
return "rotation";
else if (hg_target == "Scale")
return "scale";
else if (hg_target == "Weights")
return "weights";
return "";
}
//
static void ExportMotions(Model &model, hg::Scene &scene) {
for (const auto &scene_anime_ref : scene.GetSceneAnims()) {
tinygltf::Animation animation;
hg::SceneAnim *scene_anim = scene.GetSceneAnim(scene_anime_ref);
for (const auto &node_anim : scene_anim->node_anims) {
tinygltf::AnimationSampler sampler;
tinygltf::AnimationChannel channel;
auto *anim = scene.GetAnim(node_anim.anim);
//check if anim track can be exported TODO maybe add some other type
if (!anim->vec3_tracks.size() && !anim->quat_tracks.size())
continue;
Buffer InputBuffer, OutputBuffer;
BufferView InputBufferView, OutputBufferView;
Accessor InputAccessor, OutputAccessor;
InputAccessor.byteOffset = 0;
OutputAccessor.byteOffset = 0;
InputAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
InputAccessor.type = TINYGLTF_TYPE_SCALAR;
if (anim->quat_tracks.size()) {
OutputAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
OutputAccessor.type = TINYGLTF_TYPE_VEC4;
std::vector<float> input;
float input_min = UINT_MAX;
float input_max = 0;
std::vector<float> output;
hg::Vec4 output_min(std::numeric_limits<float>::max()), output_max(std::numeric_limits<float>::lowest());
for (const auto &key : anim->quat_tracks[0].keys) {
// t
auto t = hg::time_to_sec_f(key.t);
input.push_back(t);
input_min = t < input_min ? t : input_min;
input_max = t > input_max ? t : input_max;
// add quat
auto quat = key.v;
auto r = hg::ToEuler(quat);
quat = hg::QuaternionFromEuler(-r.x, -r.y, r.z);
output.push_back(quat.x);
output.push_back(quat.y);
output.push_back(quat.z);
output.push_back(quat.w);
output_min.x = quat.x < output_min.x ? quat.x : output_min.x;
output_min.y = quat.y < output_min.y ? quat.y : output_min.y;
output_min.z = quat.z < output_min.z ? quat.z : output_min.z;
output_min.w = quat.w < output_min.w ? quat.w : output_min.w;
output_max.x = quat.x > output_max.x ? quat.x : output_max.x;
output_max.y = quat.y > output_max.y ? quat.y : output_max.y;
output_max.z = quat.z > output_max.z ? quat.z : output_max.z;
output_max.w = quat.w > output_max.w ? quat.z : output_max.w;
}
// t
uint32_t indexSize = serialize(input, InputBuffer.data, 0);
InputAccessor.minValues.push_back(input_min);
InputAccessor.maxValues.push_back(input_max);
InputAccessor.count = input.size();
InputBufferView.byteLength = InputBuffer.data.size();
// save input buffer
InputBufferView.buffer = model.buffers.size();
model.buffers.push_back(InputBuffer);
InputAccessor.bufferView = model.bufferViews.size();
model.bufferViews.push_back(InputBufferView);
sampler.input = model.accessors.size();
model.accessors.push_back(InputAccessor);
// quat
serialize(output, OutputBuffer.data, 0);
OutputAccessor.minValues.push_back(output_min.x);
OutputAccessor.minValues.push_back(output_min.y);
OutputAccessor.minValues.push_back(output_min.z);
OutputAccessor.minValues.push_back(output_min.w);
OutputAccessor.maxValues.push_back(output_max.x);
OutputAccessor.maxValues.push_back(output_max.y);
OutputAccessor.maxValues.push_back(output_max.z);
OutputAccessor.maxValues.push_back(output_max.w);
OutputAccessor.count = output.size() / 4;
OutputBufferView.byteLength = OutputBuffer.data.size();
// save output buffer
OutputBufferView.buffer = model.buffers.size();
model.buffers.push_back(OutputBuffer);
OutputAccessor.bufferView = model.bufferViews.size();
model.bufferViews.push_back(OutputBufferView);
sampler.output = model.accessors.size();
model.accessors.push_back(OutputAccessor);
// get target
channel.target_path = GetTargetPathGltf(anim->quat_tracks[0].target);
} else if (anim->vec3_tracks.size()) {
OutputAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
OutputAccessor.type = TINYGLTF_TYPE_VEC3;
std::vector<float> input;
float input_min = UINT_MAX;
float input_max = 0;
std::vector<float> output;
hg::Vec3 output_min(std::numeric_limits<float>::max()), output_max(std::numeric_limits<float>::lowest());
for (const auto &key : anim->vec3_tracks[0].keys) {
// t
auto t = hg::time_to_sec_f(key.t);
input.push_back(t);
input_min = t < input_min ? t : input_min;
input_max = t > input_max ? t : input_max;
// add vec
auto vec = key.v;
vec.z = -vec.z;
output.push_back(vec.x);
output.push_back(vec.y);
output.push_back(vec.z);
output_min.x = vec.x < output_min.x ? vec.x : output_min.x;
output_min.y = vec.y < output_min.y ? vec.y : output_min.y;
output_min.z = vec.z < output_min.z ? vec.z : output_min.z;
output_max.x = vec.x > output_max.x ? vec.x : output_max.x;
output_max.y = vec.y > output_max.y ? vec.y : output_max.y;
output_max.z = vec.z > output_max.z ? vec.z : output_max.z;
}
// t
uint32_t indexSize = serialize(input, InputBuffer.data, 0);
InputAccessor.minValues.push_back(input_min);
InputAccessor.maxValues.push_back(input_max);
InputAccessor.count = input.size();
InputBufferView.byteLength = InputBuffer.data.size();
// save input buffer
InputBufferView.buffer = model.buffers.size();
model.buffers.push_back(InputBuffer);
InputAccessor.bufferView = model.bufferViews.size();
model.bufferViews.push_back(InputBufferView);
sampler.input = model.accessors.size();
model.accessors.push_back(InputAccessor);
// quat
serialize(output, OutputBuffer.data, 0);
OutputAccessor.minValues.push_back(output_min.x);
OutputAccessor.minValues.push_back(output_min.y);
OutputAccessor.minValues.push_back(output_min.z);
OutputAccessor.maxValues.push_back(output_max.x);
OutputAccessor.maxValues.push_back(output_max.y);
OutputAccessor.maxValues.push_back(output_max.z);
OutputAccessor.count = output.size() / 3;
OutputBufferView.byteLength = OutputBuffer.data.size();
// save output buffer
OutputBufferView.buffer = model.buffers.size();
model.buffers.push_back(OutputBuffer);
OutputAccessor.bufferView = model.bufferViews.size();
model.bufferViews.push_back(OutputBufferView);
sampler.output = model.accessors.size();
model.accessors.push_back(OutputAccessor);
// get target
channel.target_path = GetTargetPathGltf(anim->vec3_tracks[0].target);
}
channel.target_node = NodeRef_to_IdNode[node_anim.node];
channel.sampler = animation.samplers.size();
animation.samplers.push_back(sampler);
animation.channels.push_back(channel);
animation.name = scene_anim->name;
}
if(animation.channels.size())
model.animations.push_back(animation);
}
}
std::map<std::string, size_t> textures_cache;
static size_t ExportTexture(Model &model, const hg::TextureRef &tex_ref, const Config &config, hg::PipelineResources &resources) {
auto texture_name = resources.textures.GetName(tex_ref);
if (texture_name == "")
return -1;
std::string texture_path(hg::PathJoin({config.source_output_path, texture_name}));
if (textures_cache.find(texture_path) == textures_cache.end()) {
Image image;
Texture texture;
hg::Picture pic;
// check extension is png or jpg, else convert to png
auto file_ext = hg::GetFileExtension(texture_name);
if (file_ext != "png" && file_ext != "jpg" && file_ext != "jpeg") {
if (!hg::LoadPicture(pic, texture_path.c_str()))
return -1;
texture_name = hg::CutFileExtension(texture_name) + ".png";
texture_path = hg::PathJoin({ config.source_output_path, texture_name });
SavePNG(pic, texture_path.c_str());
}
if (!hg::LoadPicture(pic, texture_path.c_str()))
return -1;
image.name = hg::CutFileExtension(texture_name);
image.mimeType = file_ext == "jpg" || file_ext == "jpeg" ? "image/jpeg" : "image/png";
image.component = pic.GetFormat() == hg::PF_RGB24 ? 3 : 4;
image.bits = 8;
image.pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
image.width = pic.GetWidth();
image.height = pic.GetHeight();
// image.image = std::vector<unsigned char>(&pic.GetData()[0], &pic.GetData()[image.width * image.height * image.component]);
// open the file:
std::streampos fileSize;
std::ifstream file(hg::PathJoin({config.source_output_path, texture_name}).c_str(), std::ios::binary);
// get its size:
file.seekg(0, std::ios::end);
fileSize = file.tellg();
file.seekg(0, std::ios::beg);
// read the data:
std::vector<unsigned char> fileData(fileSize);
file.read((char *)&fileData[0], fileSize);
// save data into buffer
Buffer buffer;
BufferView bufferView;
serialize(fileData, buffer.data, 0);
bufferView.byteLength = buffer.data.size();
bufferView.buffer = model.buffers.size();
model.buffers.push_back(buffer);
image.bufferView = model.bufferViews.size();
model.bufferViews.push_back(bufferView);
texture.source = model.images.size();
model.images.push_back(image);
auto texture_id = model.textures.size();
model.textures.push_back(texture);
textures_cache.emplace(texture_path, texture_id);
// remove the temporary file
if (file_ext != "png" && file_ext != "jpg" && file_ext != "jpeg")
remove(hg::PathJoin({config.source_output_path, texture_name}).c_str());
return texture_id;
} else {
return textures_cache[texture_path];
}
}
static void ExportMaterial(Model &model, Material &mat, hg::Material &m, const Config &config, hg::PipelineResources &resources) {
auto t = hg::GetMaterialTexture(m, "uBaseOpacityMap");
if (t != hg::InvalidTextureRef)
mat.pbrMetallicRoughness.baseColorTexture.index = ExportTexture(model, t, config, resources);
else {
t = hg::GetMaterialTexture(m, "uDiffuseMap");
if (t != hg::InvalidTextureRef)
mat.pbrMetallicRoughness.baseColorTexture.index = ExportTexture(model, t, config, resources);
}
t = hg::GetMaterialTexture(m, "uOcclusionRoughnessMetalnessMap");
if (t != hg::InvalidTextureRef)
mat.pbrMetallicRoughness.metallicRoughnessTexture.index = ExportTexture(model, t, config, resources);
t = hg::GetMaterialTexture(m, "uOcclusionMap");
if (t != hg::InvalidTextureRef) {
mat.occlusionTexture.index = ExportTexture(model, t, config, resources);
mat.occlusionTexture.texCoord = 1;
} else if (mat.pbrMetallicRoughness.metallicRoughnessTexture.index >= 0)
mat.occlusionTexture.index = mat.pbrMetallicRoughness.metallicRoughnessTexture.index;
t = hg::GetMaterialTexture(m, "uNormalMap");
if (t != hg::InvalidTextureRef)
mat.normalTexture.index = ExportTexture(model, t, config, resources);
t = hg::GetMaterialTexture(m, "uSelfMap");
if (t != hg::InvalidTextureRef)
mat.emissiveTexture.index = ExportTexture(model, t, config, resources);
if (const auto &v = GetMaterialValue(m, "uBaseOpacityColor")) {
mat.pbrMetallicRoughness.baseColorFactor[0] = v->value[0];
mat.pbrMetallicRoughness.baseColorFactor[1] = v->value[1];
mat.pbrMetallicRoughness.baseColorFactor[2] = v->value[2];
mat.pbrMetallicRoughness.baseColorFactor[3] = v->value[3];
}else if (const auto &v = GetMaterialValue(m, "uDiffuseColor")) {
mat.pbrMetallicRoughness.baseColorFactor[0] = v->value[0];
mat.pbrMetallicRoughness.baseColorFactor[1] = v->value[1];
mat.pbrMetallicRoughness.baseColorFactor[2] = v->value[2];
mat.pbrMetallicRoughness.baseColorFactor[3] = v->value[3];
}
if (const auto &v = GetMaterialValue(m, "uOcclusionRoughnessMetalnessColor")) {
mat.pbrMetallicRoughness.roughnessFactor = v->value[1];
mat.pbrMetallicRoughness.metallicFactor = v->value[2];
}
if (const auto &v = GetMaterialValue(m, "uSelfColor")) {
mat.emissiveFactor.resize(3, 0);
mat.emissiveFactor[0] = v->value[0];
mat.emissiveFactor[1] = v->value[1];
mat.emissiveFactor[2] = v->value[2];
}
if (GetMaterialBlendMode(m) == hg::BM_Alpha)
mat.alphaMode = "BLEND";
if (GetMaterialFaceCulling(m) == hg::FC_Disabled)
mat.doubleSided = true;
}
//
static hg::Vertex PreparePolygonVertex(const hg::Geometry &geo, size_t i_bind, size_t i_vtp) {
hg::Vertex vtx;
vtx.pos = geo.vtx[geo.binding[i_bind + i_vtp]];
if (!geo.normal.empty())
vtx.normal = geo.normal[i_bind + i_vtp];
if (!geo.tangent.empty()) {
vtx.tangent = geo.tangent[i_bind + i_vtp].T;
vtx.binormal = geo.tangent[i_bind + i_vtp].B;
}
if (!geo.color.empty())
vtx.color0 = geo.color[i_bind + i_vtp];
for (auto i = 0; i < geo.uv.size(); ++i)
if (!geo.uv[i].empty())
*(&vtx.uv0 + i) = geo.uv[i][i_bind + i_vtp];
return vtx;
}
static void ExportGeometry(Model &model, Mesh &mesh, const int &nb_materials, const hg::Geometry &geo, const Config &config) {
// prepare triangulation
const uint8_t mat_count = GetMaterialCount(geo);
std::map<int, std::vector<uint32_t>> geo_mat_indices;
std::vector<hg::Vertex> geo_vtx;
for (auto i_mat = 0; i_mat < mat_count; ++i_mat) {
geo_mat_indices.emplace(i_mat, std::vector<uint32_t>());
size_t i_bind = 0;
for (auto &pol : geo.pol) {
if (pol.material == i_mat) {
for (auto i_vtp = 1; i_vtp < pol.vtx_count - 1; ++i_vtp) {
geo_mat_indices[i_mat].push_back(geo_vtx.size());
geo_vtx.push_back(PreparePolygonVertex(geo, i_bind, 0));
geo_mat_indices[i_mat].push_back(geo_vtx.size());
geo_vtx.push_back(PreparePolygonVertex(geo, i_bind, i_vtp + 1));
geo_mat_indices[i_mat].push_back(geo_vtx.size());
geo_vtx.push_back(PreparePolygonVertex(geo, i_bind, i_vtp));
}
}
i_bind += pol.vtx_count;
}
}
int accessor_id_position = -1, accessor_id_normal = -1, accessor_id_tangent = -1, accessor_id_texcoord0 = -1, accessor_id_texcoord1 = -1;
// VERTEX BUFFER
{
Buffer buffer;
BufferView bufferView;
Accessor verticesAccessor;
verticesAccessor.byteOffset = 0;
verticesAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
verticesAccessor.type = TINYGLTF_TYPE_VEC3;
bufferView.byteOffset = 0;
// bufferView.byteStride = 4; // not sur if needed yet
std::vector<float> vertices;
hg::Vec3 min(std::numeric_limits<float>::max()), max(std::numeric_limits<float>::lowest());
for (const auto &vertex : geo_vtx) {
const auto &vtx = vertex.pos;
vertices.push_back(vtx.x);
vertices.push_back(vtx.y);
vertices.push_back(-vtx.z);
min.x = vtx.x < min.x ? vtx.x : min.x;
min.y = vtx.y < min.y ? vtx.y : min.y;
min.z = -vtx.z < min.z ? -vtx.z : min.z;
max.x = vtx.x > max.x ? vtx.x : max.x;
max.y = vtx.y > max.y ? vtx.y : max.y;
max.z = -vtx.z > max.z ? -vtx.z : max.z;
}
serialize(vertices, buffer.data, 0);
verticesAccessor.minValues.push_back(min.x);
verticesAccessor.minValues.push_back(min.y);
verticesAccessor.minValues.push_back(min.z);
verticesAccessor.maxValues.push_back(max.x);
verticesAccessor.maxValues.push_back(max.y);
verticesAccessor.maxValues.push_back(max.z);
verticesAccessor.count = vertices.size() / 3;
bufferView.byteLength = buffer.data.size();
// save vertices buffer
bufferView.buffer = model.buffers.size();
model.buffers.push_back(buffer);
verticesAccessor.bufferView = model.bufferViews.size();
model.bufferViews.push_back(bufferView);
accessor_id_position = model.accessors.size();
model.accessors.push_back(verticesAccessor);
}
// NORMAL BUFFER
{
Buffer buffer;
BufferView bufferView;
Accessor normalAccessor;
normalAccessor.byteOffset = 0;
normalAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
normalAccessor.type = TINYGLTF_TYPE_VEC3;
bufferView.byteOffset = 0;
// bufferView.byteStride = 4; // not sur if needed yet
std::vector<float> normal;
hg::Vec3 min(std::numeric_limits<float>::max()), max(std::numeric_limits<float>::lowest());
for (const auto &vertex : geo_vtx) {
const auto &nrm = vertex.normal;
normal.push_back(nrm.x);
normal.push_back(nrm.y);
normal.push_back(-nrm.z);
min.x = nrm.x < min.x ? nrm.x : min.x;
min.y = nrm.y < min.y ? nrm.y : min.y;
min.z = -nrm.z < min.z ? -nrm.z : min.z;
max.x = nrm.x > max.x ? nrm.x : max.x;
max.y = nrm.y > max.y ? nrm.y : max.y;
max.z = -nrm.z > max.z ? -nrm.z : max.z;
}
serialize(normal, buffer.data, 0);
normalAccessor.minValues.push_back(min.x);
normalAccessor.minValues.push_back(min.y);
normalAccessor.minValues.push_back(min.z);
normalAccessor.maxValues.push_back(max.x);
normalAccessor.maxValues.push_back(max.y);
normalAccessor.maxValues.push_back(max.z);
normalAccessor.count = normal.size() / 3;
bufferView.byteLength = buffer.data.size();
// save normal buffer
bufferView.buffer = model.buffers.size();
model.buffers.push_back(buffer);
normalAccessor.bufferView = model.bufferViews.size();
model.bufferViews.push_back(bufferView);
accessor_id_normal = model.accessors.size();
model.accessors.push_back(normalAccessor);
}
/* // TANGENT BUFFER
if(!geo.tangent.empty())
{
Buffer buffer;
BufferView bufferView;
Accessor tangentAccessor;
tangentAccessor.byteOffset = 0;
tangentAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
tangentAccessor.type = TINYGLTF_TYPE_VEC4;
bufferView.byteOffset = 0;
// bufferView.byteStride = 4; // not sur if needed yet
std::vector<float> tangent;
hg::Vec3 min(std::numeric_limits<float>::max()), max(std::numeric_limits<float>::lowest()/*);
for (const auto &tgt : geo.tangent) {
tangent.push_back(tgt.T.x);
tangent.push_back(tgt.T.y);
tangent.push_back(tgt.T.z);
tangent.push_back(1.0);
min.x = tgt.T.x < min.x ? tgt.T.x : min.x;
min.y = tgt.T.y < min.y ? tgt.T.y : min.y;
min.z = tgt.T.z < min.z ? tgt.T.z : min.z;
max.x = tgt.T.x > max.x ? tgt.T.x : max.x;
max.y = tgt.T.y > max.y ? tgt.T.y : max.y;
max.z = tgt.T.z > max.z ? tgt.T.z : max.z;
}
serialize(tangent, buffer.data, 0);
tangentAccessor.minValues.push_back(min.x);
tangentAccessor.minValues.push_back(min.y);
tangentAccessor.minValues.push_back(min.z);
tangentAccessor.minValues.push_back(1);
tangentAccessor.maxValues.push_back(max.x);
tangentAccessor.maxValues.push_back(max.y);
tangentAccessor.maxValues.push_back(max.z);
tangentAccessor.maxValues.push_back(1);
tangentAccessor.count = tangent.size() / 4;
bufferView.byteLength = buffer.data.size();
// save tangent buffer
bufferView.buffer = model.buffers.size();
model.buffers.push_back(buffer);
tangentAccessor.bufferView = model.bufferViews.size();
model.bufferViews.push_back(bufferView);
accessor_id_tangent = model.accessors.size();
model.accessors.push_back(tangentAccessor);
}*/
// UV0 BUFFER
if (!geo.uv[0].empty()) {
Buffer buffer;
BufferView bufferView;
Accessor uv0Accessor;
uv0Accessor.byteOffset = 0;
uv0Accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
uv0Accessor.type = TINYGLTF_TYPE_VEC2;
bufferView.byteOffset = 0;
// bufferView.byteStride = 4; // not sur if needed yet
std::vector<float> uv0;
hg::Vec3 min(std::numeric_limits<float>::max()), max(std::numeric_limits<float>::lowest());
for (const auto &vertex : geo_vtx) {
const auto &uv = vertex.uv0;
uv0.push_back(uv.x);
uv0.push_back(uv.y);
min.x = uv.x < min.x ? uv.x : min.x;
min.y = uv.y < min.y ? uv.y : min.y;
max.x = uv.x > max.x ? uv.x : max.x;
max.y = uv.y > max.y ? uv.y : max.y;
}
serialize(uv0, buffer.data, 0);
uv0Accessor.minValues.push_back(min.x);
uv0Accessor.minValues.push_back(min.y);
uv0Accessor.maxValues.push_back(max.x);
uv0Accessor.maxValues.push_back(max.y);
uv0Accessor.count = uv0.size() / 2;
bufferView.byteLength = buffer.data.size();
// save uv0 buffer
bufferView.buffer = model.buffers.size();
model.buffers.push_back(buffer);
uv0Accessor.bufferView = model.bufferViews.size();
model.bufferViews.push_back(bufferView);
accessor_id_texcoord0 = model.accessors.size();
model.accessors.push_back(uv0Accessor);
}
// UV1 BUFFER
if (!geo.uv[1].empty()) {
Buffer buffer;
BufferView bufferView;
Accessor uv1Accessor;
uv1Accessor.byteOffset = 0;
uv1Accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
uv1Accessor.type = TINYGLTF_TYPE_VEC2;
bufferView.byteOffset = 0;
// bufferView.byteStride = 4; // not sur if needed yet
std::vector<float> uv1;
hg::Vec3 min(std::numeric_limits<float>::max()), max(std::numeric_limits<float>::lowest());
for (const auto &vertex : geo_vtx) {
const auto &uv = vertex.uv1;
uv1.push_back(uv.x);
uv1.push_back(uv.y);
min.x = uv.x < min.x ? uv.x : min.x;
min.y = uv.y < min.y ? uv.y : min.y;
max.x = uv.x > max.x ? uv.x : max.x;
max.y = uv.y > max.y ? uv.y : max.y;
}
serialize(uv1, buffer.data, 0);
uv1Accessor.minValues.push_back(min.x);
uv1Accessor.minValues.push_back(min.y);
uv1Accessor.maxValues.push_back(max.x);
uv1Accessor.maxValues.push_back(max.y);
uv1Accessor.count = uv1.size() / 2;
bufferView.byteLength = buffer.data.size();
// save uv1 buffer
bufferView.buffer = model.buffers.size();
model.buffers.push_back(buffer);
uv1Accessor.bufferView = model.bufferViews.size();
model.bufferViews.push_back(bufferView);
accessor_id_texcoord1 = model.accessors.size();
model.accessors.push_back(uv1Accessor);
}
for (int i_mat = 0; i_mat < nb_materials; ++i_mat) {
Primitive primitive;
primitive.mode = TINYGLTF_MODE_TRIANGLES;
// one primitive per material using the same buffer (not really optimised for now!!!)
if (accessor_id_position != -1)
primitive.attributes["POSITION"] = accessor_id_position;
if (accessor_id_normal != -1)
primitive.attributes["NORMAL"] = accessor_id_normal;
if (accessor_id_tangent != -1)
primitive.attributes["TANGENT"] = accessor_id_tangent;
if (accessor_id_texcoord0 != -1)
primitive.attributes["TEXCOORD_0"] = accessor_id_texcoord0;
if (accessor_id_texcoord1 != -1)
primitive.attributes["TEXCOORD_1"] = accessor_id_texcoord1;
// INDICES BUFFER
{
Buffer buffer;
BufferView bufferView;
Accessor indicesAccessor;
indicesAccessor.byteOffset = 0;
indicesAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT;
indicesAccessor.type = TINYGLTF_TYPE_SCALAR;
bufferView.byteOffset = 0;
// bufferView.byteStride = 4; // not sur if needed yet
const auto pol_idx = ComputePolygonIndex(geo);
std::vector<uint32_t> indices;
uint32_t min = UINT_MAX;
uint32_t max = 0;
for (const auto &i : geo_mat_indices[i_mat]) {
indices.push_back(i);
min = i < min ? i : min;
max = i > max ? i : max;
}
uint32_t indexSize = serialize(indices, buffer.data, 0);
indicesAccessor.minValues.push_back(min);
indicesAccessor.maxValues.push_back(max);
indicesAccessor.count = indices.size();
bufferView.byteLength = buffer.data.size();
// save indices buffer
bufferView.buffer = model.buffers.size();
model.buffers.push_back(buffer);
indicesAccessor.bufferView = model.bufferViews.size();
model.bufferViews.push_back(bufferView);
primitive.indices = model.accessors.size();
model.accessors.push_back(indicesAccessor);
}
mesh.primitives.push_back(primitive);
}
}
std::map<std::string, std::vector<Primitive>> geo_to_primitives;
static const size_t EmptyMeshID = static_cast<size_t>(-1);
static const size_t ExportObject(Model &model, const hg::Object &object, const Config &config, hg::PipelineResources &resources) {
Mesh mesh;
// export geo
auto mdl_ref = object.GetModelRef();
if (resources.models.IsValidRef(mdl_ref)) {
auto mdl_name = resources.models.GetName_unsafe_(mdl_ref.ref.idx);
mesh.name = mdl_name;
std::string geo_name = hg::PathJoin({config.source_output_path, mdl_name});
if (geo_to_primitives.find(geo_name) == geo_to_primitives.end()) {
// this geo was not exported
const auto geo = hg::LoadGeometryFromFile(geo_name.c_str());
// add primitive to mesh or multiple primitive (one per material)
if (geo.vtx.size()) {
ExportGeometry(model, mesh, object.GetMaterialCount(), geo, config);
geo_to_primitives[geo_name] = mesh.primitives;
} else
return EmptyMeshID; // no geo, no mesh
} else {
mesh.primitives = geo_to_primitives[geo_name];
}
} else
return EmptyMeshID; // no geo, no mesh
// export materials
for (int i = 0; i < object.GetMaterialCount(); ++i) {
Material mat;
auto m = object.GetMaterial(i);
mat.name = object.GetMaterialName(i);
ExportMaterial(model, mat, m, config, resources);
mesh.primitives[i].material = model.materials.size();
model.materials.push_back(mat);
}
size_t id_mesh = model.meshes.size();
model.meshes.push_back(mesh);
return id_mesh;
}
static size_t ExportCamera(Model& model, const hg::Camera& cam) {
Camera gltf_camera;
if (cam.GetIsOrthographic()) {
gltf_camera.type = "orthographic";
gltf_camera.orthographic.znear = cam.GetZNear();
gltf_camera.orthographic.zfar = cam.GetZFar();
} else {
gltf_camera.type = "perspective";
gltf_camera.perspective.znear = cam.GetZNear();
gltf_camera.perspective.zfar = cam.GetZFar();
gltf_camera.perspective.yfov = (double)cam.GetFov();
}
model.cameras.push_back(gltf_camera);
return model.cameras.size() - 1;
}
static int ExportLight(Model &model, const hg::Light &light) {
Light gltf_light;
gltf_light.color.push_back(light.GetDiffuseColor().r);
gltf_light.color.push_back(light.GetDiffuseColor().g);
gltf_light.color.push_back(light.GetDiffuseColor().b);
gltf_light.intensity = light.GetDiffuseIntensity();
gltf_light.spot.innerConeAngle = light.GetInnerAngle();
gltf_light.spot.outerConeAngle = light.GetOuterAngle();
gltf_light.range = light.GetRadius();
if (light.GetType() == hg::LT_Point) {
gltf_light.type = "point";
} else if (light.GetType() == hg::LT_Linear) {
gltf_light.type = "directional";
} else if (light.GetType() == hg::LT_Spot) {
gltf_light.type = "spot";
}
model.lights.push_back(gltf_light);
return model.lights.size() - 1;
}
//
static const int ExportNode(Model &model, const hg::NodeRef nodeRef, const std::vector<hg::NodeRef> &children, const hg::NodesChildren &nodes_children,
const hg::Scene &scene, const Config &config, hg::PipelineResources &resources) {
// [EJ] this code is confusing/confused...
// is nodeRef.gen known to be outdated? if so, why pass a ref and not an index?
// why is the returned Node object stored as a reference to a temporary?
const auto &node = scene.GetNode(scene.GetNodeRef(nodeRef.idx));
Node n;
n.name = node.GetName();
// if disable
if (!node.IsEnabled()) {
if (config.filter_disabled_node)
return -1;
if (std::find(model.extensionsUsed.begin(), model.extensionsUsed.end(), "KHR_nodes_disable") == model.extensionsUsed.end())
model.extensionsUsed.push_back("KHR_nodes_disable");
n.extensions["KHR_nodes_disable"] = Value({ { "visible", Value(false) } });
}
if (node.GetTransform()) {
auto p = node.GetTransform().GetPos();
n.translation.push_back(p.x);
n.translation.push_back(p.y);
n.translation.push_back(-p.z);
auto r = node.GetTransform().GetRot();
r.x = -r.x;
r.y = -r.y;
auto q = hg::QuaternionFromEuler(r);
n.rotation.push_back(q.x);
n.rotation.push_back(q.y);
n.rotation.push_back(q.z);
n.rotation.push_back(q.w);
auto s = node.GetTransform().GetScale();
n.scale.push_back(s.x);
n.scale.push_back(s.y);
n.scale.push_back(s.z);
}
// is it a camera
if (node.HasCamera()) {
n.camera = ExportCamera(model, node.GetCamera());
}
// is it a light
if (node.HasLight()) {
if (std::find(model.extensionsUsed.begin(), model.extensionsUsed.end(), "KHR_lights_punctual") == model.extensionsUsed.end())
model.extensionsUsed.push_back("KHR_lights_punctual");
n.extensions["KHR_lights_punctual"] = Value({{"light", Value(ExportLight(model, node.GetLight()))}});
}
//
if (node.HasObject()) {
auto id_mesh = ExportObject(model, node.GetObject(), config, resources);
if (id_mesh != EmptyMeshID)
n.mesh = id_mesh;
}
// save children
for (const auto &child : children) {
auto id_node = ExportNode(model, child, nodes_children.GetChildren(child), nodes_children, scene, config, resources);
if (id_node >= 0)
n.children.push_back(id_node);
}
auto id_node = model.nodes.size();
NodeRef_to_IdNode[node.ref] = id_node;
model.nodes.push_back(n);
return id_node;
}
static bool ExportGltfScene(const std::string &path, const Config &config) {
const auto t_start = hg::time_now();
// init bgfx
hg::WindowSystemInit();
//
auto win = hg::RenderInit(1, 1, BGFX_RESET_MSAA_X4, bgfx::TextureFormat::Count); // , BGFX_DEBUG_STATS);
hg::HideWindow(win);
// create output directory if missing
if (hg::Exists(config.base_output_path.c_str())) {
if (!hg::IsDir(config.base_output_path.c_str()))
return false; // can't output to this path
} else {
if (!hg::MkDir(config.base_output_path.c_str()))
return false;
}
// add assets path
hg::AddAssetsFolder(config.compiled_output_path.c_str());
// load scene
hg::PipelineResources pl_resources;
hg::Scene scene;
hg::LoadSceneContext ctx;
if (!hg::LoadSceneFromAssets(hg::PathStripPrefix(path, config.compiled_output_path).c_str(), scene, pl_resources, hg::GetForwardPipelineInfo(), ctx, hg::LSSF_All))
return false;
// get root nodes to export (then proceed with children)
std::vector<hg::NodeRef> root_nodes;
const auto nodes = scene.GetNodes();
for (auto &node : nodes)
if (!scene.IsValidNodeRef(node.GetTransform().GetParent())) // skip nodes with parent
root_nodes.push_back(node.ref);
const auto nodes_children = scene.BuildNodesChildren();
//
Model model;
TinyGLTF saver;
std::string err;
std::string warn;
model.asset.generator = "Harfang Exporter";
model.asset.version = "2.0";
Scene scn;
// export root node (following by the children)
for (const auto &node : root_nodes) {
auto id_node = ExportNode(model, node, nodes_children.GetChildren(node), nodes_children, scene, config, pl_resources);
if (id_node >= 0)
scn.nodes.push_back(id_node);
}
ExportMotions(model, scene);
model.scenes.push_back(scn);
std::string export_path = config.name.empty() ? hg::GetFileName(path) : config.name;
// if no extension, set to binary
if (hg::GetFileExtension(export_path) == "")
export_path += ".glb";
std::string out_path;
bool embedImages = true;
bool embedBuffers = true;
bool prettyPrint = true;
bool writeBinary = config.binary || (hg::GetFileExtension(export_path) == "glb");
if (!GetOutputPath(out_path, config.base_output_path, hg::GetFileName(export_path), {}, hg::GetFileExtension(export_path))) {
hg::error("failed to compute output path");
return false;
}
bool ret = saver.WriteGltfSceneToFile(&model, out_path, embedImages, embedBuffers, prettyPrint, writeBinary);
if (!ret) {
hg::error(hg::format("failed to write scene to %1").arg(out_path.c_str()));
return false;
}
hg::log(hg::format("Export complete, took %1 ms").arg(hg::time_to_ms(hg::time_now() - t_start)));
return true;
}
static void OutputUsage(const hg::CmdLineFormat &cmd_format) {
hg::debug((std::string("Usage: gltf_exporter ") + hg::word_wrap(hg::FormatCmdLineArgs(cmd_format), 80, 21) + "\n").c_str());
hg::debug((hg::FormatCmdLineArgsDescription(cmd_format)).c_str());
hg::debug((std::string("Header-only tiny glTF 2.0 loader and serializer.The MIT License (MIT)") +
"Copyright (c) 2015 - 2019 Syoyo Fujita, Aur<75>lien Chatelain and manycontributors." +
"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.")
.c_str());
}
//
static std::mutex log_mutex;
static bool quiet = false;
int main(int argc, const char **argv) {
hg::set_log_hook(
[](const char *msg, int mask, const char *details, void *user) {
if (quiet && !(mask & hg::LL_Error))
return; // skip masked entries
std::lock_guard<std::mutex> guard(log_mutex);
std::cout << msg << std::endl;
},
nullptr);
hg::set_log_level(hg::LL_All);
hg::debug(hg::format("Harfang->GLTF Converter %1 (%2)").arg(hg::get_version_string()).arg(hg::get_build_sha()).c_str());
hg::CmdLineFormat cmd_format = {
{
{"-quiet", "Quiet log, only log errors"},
{"-binary", "Export binary file"},
{"-filter_disabled_node", "Don't export disabled node"},
},
{{"-out", "Output directory", true}, {"-compiled-resource-path", "Where the scene compiled resources are", true},
{"-source-resource-path", "Where the scene not compiled resources are", true}, {"-name", "Specify the output scene name", true}},
{
{"input", "Input harfang scene file to convert"},
},
{{"-o", "-out"}, {"-h", "-help"}, {"-q", "-quiet"}},
};
hg::CmdLineContent cmd_content;
if (!hg::ParseCmdLine({argv + 1, argv + argc}, cmd_format, cmd_content)) {
OutputUsage(cmd_format);
return -1;
}
//
Config config;
config.base_output_path = hg::CleanPath(hg::GetCmdLineSingleValue(cmd_content, "-out", "./"));
config.compiled_output_path = hg::CleanPath(hg::GetCmdLineSingleValue(cmd_content, "-compiled-resource-path", ""));
config.source_output_path = hg::CleanPath(hg::GetCmdLineSingleValue(cmd_content, "-source-resource-path", ""));
config.name = hg::CleanPath(hg::GetCmdLineSingleValue(cmd_content, "-name", ""));
quiet = hg::GetCmdLineFlagValue(cmd_content, "-quiet");
config.binary = hg::GetCmdLineFlagValue(cmd_content, "-binary");
config.filter_disabled_node = hg::GetCmdLineFlagValue(cmd_content, "-filter_disabled_node");
//
if (cmd_content.positionals.size() != 1) {
hg::debug("No input file");
OutputUsage(cmd_format);
return -2;
}
//
config.input_path = cmd_content.positionals[0];
auto res = ExportGltfScene(cmd_content.positionals[0], config);
const auto msg = std::string("[ExportScene") + std::string(res ? ": OK]" : ": KO]");
hg::log(msg.c_str());
return res ? 0 : 1;
}