mirror of
https://github.com/harfang3d/harfang3d.git
synced 2024-07-01 01:39:42 +00:00
1704 lines
65 KiB
C++
1704 lines
65 KiB
C++
// HARFANG(R) Copyright (C) 2021 Emmanuel Julien, NWNC HARFANG. Released under GPL/LGPL/Commercial Licence, see licence.txt for details.
|
|
#include <engine/animation.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/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/sha1.h>
|
|
#include <foundation/string.h>
|
|
#include <foundation/time.h>
|
|
#include <foundation/vector3.h>
|
|
|
|
#include "json.hpp"
|
|
#include "stb_image.h"
|
|
#include "tiny_gltf.h"
|
|
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <mutex>
|
|
|
|
#undef CopyFile
|
|
#undef GetObject
|
|
|
|
#include <foundation/file.h>
|
|
|
|
using namespace tinygltf;
|
|
using nlohmann::json;
|
|
|
|
std::map<int, hg::NodeRef> idNode_to_NodeRef;
|
|
std::vector<std::string> already_saved_picture;
|
|
|
|
std::map<std::string, std::string> primitiveIdsToGeoPath;
|
|
std::map<std::string, int> geoPathOcurrence;
|
|
|
|
static std::string Indent(const int indent) {
|
|
std::string s;
|
|
for (int i = 0; i < indent; i++) {
|
|
s += " ";
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/// 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 byte data
|
|
struct byteArrayBase {
|
|
virtual ~byteArrayBase() = default;
|
|
virtual unsigned char operator[](size_t) const = 0;
|
|
virtual size_t size() const = 0;
|
|
};
|
|
|
|
/// 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 byte
|
|
template <class T> struct byteArray : public byteArrayBase {
|
|
arrayAdapter<T> adapter;
|
|
|
|
explicit byteArray(const arrayAdapter<T> &a) : adapter(a) {}
|
|
unsigned char operator[](size_t position) const override { return static_cast<unsigned char>(adapter[position]); }
|
|
|
|
size_t size() const override { return adapter.elemCount; }
|
|
};
|
|
|
|
/// 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 m44fArray {
|
|
arrayAdapter<hg::Mat44> adapter;
|
|
explicit m44fArray(const arrayAdapter<hg::Mat44> &a) : adapter(a) {}
|
|
|
|
hg::Mat44 operator[](size_t position) const { return adapter[position]; }
|
|
size_t size() const { return adapter.elemCount; }
|
|
};
|
|
|
|
enum class ImportPolicy { SkipExisting, Overwrite, Rename, SkipAlways };
|
|
|
|
struct Config {
|
|
ImportPolicy import_policy_geometry{ImportPolicy::SkipExisting}, import_policy_material{ImportPolicy::SkipExisting},
|
|
import_policy_texture{ImportPolicy::SkipExisting}, import_policy_scene{ImportPolicy::SkipExisting}, import_policy_anim{ImportPolicy::SkipExisting};
|
|
|
|
std::string input_path;
|
|
std::string name; // output name (may be empty)
|
|
std::string base_output_path{"./"};
|
|
std::string prj_path;
|
|
std::string prefix;
|
|
std::string shader;
|
|
|
|
float geometry_scale{1.f};
|
|
int frame_per_second{24};
|
|
|
|
bool import_animation{true};
|
|
bool recalculate_normal{false}, recalculate_tangent{false};
|
|
|
|
std::string finalizer_script;
|
|
};
|
|
|
|
static bool GetOutputPath(
|
|
std::string &path, const std::string &base, const std::string &name, const std::string &prefix, const std::string &ext, ImportPolicy import_policy) {
|
|
if (base.empty())
|
|
return false;
|
|
|
|
const auto filename = name.empty() ? prefix : (prefix.empty() ? name : prefix + "-" + name);
|
|
path = hg::CleanPath(base + "/" + filename + "." + ext);
|
|
|
|
// check folder exists and created
|
|
hg::MkTree(hg::CutFileName(path).c_str());
|
|
|
|
switch (import_policy) {
|
|
default:
|
|
return false;
|
|
|
|
case ImportPolicy::SkipAlways:
|
|
return false; // WARNING do not move to the start of the function, we need the path for the resource even if it is not exported
|
|
|
|
case ImportPolicy::SkipExisting:
|
|
if (hg::Exists(path.c_str()))
|
|
return false;
|
|
break;
|
|
|
|
case ImportPolicy::Overwrite:
|
|
return true;
|
|
|
|
case ImportPolicy::Rename:
|
|
for (auto n = 0; hg::Exists(path.c_str()) && n < 10000; ++n) {
|
|
std::ostringstream ss;
|
|
ss << base << "/" << filename << "-" << std::setw(4) << std::setfill('0') << n << "." << ext;
|
|
path = ss.str();
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//
|
|
static std::string MakeRelativeResourceName(const std::string &name, const std::string &base_path, const std::string &prefix) {
|
|
if (hg::starts_with(name, base_path, hg::case_sensitivity::insensitive)) {
|
|
const auto stripped_name = hg::lstrip(hg::slice(name, base_path.length()), "/");
|
|
return prefix.empty() ? stripped_name : prefix + "/" + stripped_name;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
static bool LoadFinalizerScript(const std::string &path) {
|
|
const auto source = hg::FileToString(path.c_str());
|
|
|
|
if (source.empty())
|
|
return false;
|
|
|
|
// if (!hg_lua_bind_harfang(vm, "hg") || !hg::Execute(vm, source, path))
|
|
// return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void FinalizeMaterial(hg::Material &mat, const std::string &name, const std::string &geo_name) {
|
|
/*
|
|
if (vm->IsOpen()) {
|
|
auto fn = vm->Get("FinalizeMaterial");
|
|
if (fn.IsValidAndNonNull())
|
|
if (!vm->Call(fn, {vm->ValueToObject(hg::TypeValue(mat)), vm->CreateObject(name), vm->CreateObject(geo_name)}))
|
|
failed = true;
|
|
}
|
|
*/
|
|
}
|
|
|
|
static void FinalizeGeometry(hg::Geometry &geo, const std::string &name) {
|
|
/*
|
|
if (vm) {
|
|
auto fn = ng::Get(vm, "FinalizeModel");
|
|
if (fn.IsValidAndNonNull())
|
|
if (!ng::Call(vm, fn, {ng::ValueToObject(vm, geo), ng::ValueToObject(vm, name)}))
|
|
failed = true;
|
|
}
|
|
*/
|
|
}
|
|
|
|
static void FinalizeNode(hg::Node &node) {
|
|
/*
|
|
if (vm->IsOpen()) {
|
|
auto fn = vm->Get("FinalizeNode");
|
|
if (fn.IsValidAndNonNull())
|
|
if (!vm->Call(fn, {vm->ValueToObject(hg::TypeValue(node))}))
|
|
failed = true;
|
|
}
|
|
*/
|
|
}
|
|
|
|
static void FinalizeScene(hg::Scene &scene) {
|
|
/*
|
|
if (vm->IsOpen()) {
|
|
auto fn = vm->Get("FinalizeScene");
|
|
if (fn.IsValidAndNonNull())
|
|
if (!vm->Call(fn, {vm->ValueToObject(hg::TypeValue(scene))}))
|
|
failed = true;
|
|
}
|
|
*/
|
|
}
|
|
|
|
//
|
|
static void ExportMotions(const Model &model, const Scene &gltf_scene, hg::Scene &scene, const Config &config) {
|
|
if (!config.import_animation)
|
|
return;
|
|
|
|
// all animations
|
|
hg::debug(hg::format("animations(items=%1)").arg(model.animations.size()).c_str());
|
|
for (size_t i = 0; i < model.animations.size(); i++) {
|
|
const tinygltf::Animation &animation = model.animations[i];
|
|
hg::debug(hg::format(Indent(1) + "name : %1").arg(animation.name.empty() ? "anim_" + std::to_string(i) : animation.name).c_str());
|
|
|
|
hg::SceneAnim scene_anim{animation.name.empty() ? "anim_" + std::to_string(i) : animation.name, hg::time_from_sec(99999), hg::time_from_sec(-99999)};
|
|
|
|
std::vector<hg::AnimRef> anims;
|
|
|
|
// add animations samplers
|
|
hg::debug(hg::format(Indent(1) + "samplers(items=%1)").arg(animation.samplers.size()).c_str());
|
|
for (size_t j = 0; j < animation.samplers.size(); j++) {
|
|
const tinygltf::AnimationSampler &sampler = animation.samplers[j];
|
|
hg::debug(hg::format(Indent(2) + "input : %1").arg(sampler.input).c_str());
|
|
hg::debug(hg::format(Indent(2) + "interpolation : %1").arg(sampler.interpolation).c_str());
|
|
hg::debug(hg::format(Indent(2) + "output : %1").arg(sampler.output).c_str());
|
|
|
|
// input (usually the time) Get the good buffer from all the gltf micmac
|
|
const auto inputAccessor = model.accessors[sampler.input];
|
|
const auto &inputBufferView = model.bufferViews[inputAccessor.bufferView];
|
|
const auto &inputBuffer = model.buffers[inputBufferView.buffer];
|
|
const auto inputDataPtr = inputBuffer.data.data() + inputBufferView.byteOffset + inputAccessor.byteOffset;
|
|
const auto input_byte_stride = inputAccessor.ByteStride(inputBufferView);
|
|
const auto input_count = inputAccessor.count;
|
|
|
|
// hg::debug(hg::format("input attribute has count %1 and stride %2 bytes").arg(input_count).arg(input_byte_stride).c_str());
|
|
|
|
// output (value for the key) Get the good buffer from all the gltf micmac
|
|
const auto outputAccessor = model.accessors[sampler.output];
|
|
const auto &outputBufferView = model.bufferViews[outputAccessor.bufferView];
|
|
const auto &outputBuffer = model.buffers[outputBufferView.buffer];
|
|
const auto outputDataPtr = outputBuffer.data.data() + outputBufferView.byteOffset + outputAccessor.byteOffset;
|
|
const auto output_byte_stride = outputAccessor.ByteStride(outputBufferView);
|
|
const auto output_count = outputAccessor.count;
|
|
|
|
// hg::debug(hg::format("output attribute has count %1 and stride %2 bytes").arg(output_count).arg(output_byte_stride).c_str());
|
|
|
|
const auto anim_ref = scene.AddAnim({});
|
|
anims.push_back(anim_ref);
|
|
auto anim = scene.GetAnim(anim_ref);
|
|
|
|
// get channel type anim
|
|
std::string target_path = "";
|
|
for (size_t k = 0; k < animation.channels.size(); k++)
|
|
if (animation.channels[k].sampler == j)
|
|
target_path = animation.channels[j].target_path;
|
|
|
|
// Transfers the value and time from the buffer to harfang depending of the type
|
|
switch (inputAccessor.type) {
|
|
case TINYGLTF_TYPE_SCALAR: {
|
|
switch (inputAccessor.componentType) {
|
|
case TINYGLTF_COMPONENT_TYPE_FLOAT: {
|
|
hg::debug("Input type is FLOAT");
|
|
// vector of float
|
|
floatArray<float> time(arrayAdapter<float>(inputDataPtr, input_count, input_byte_stride));
|
|
|
|
hg::debug(hg::format("time's size : %1").arg(time.size()).c_str());
|
|
|
|
switch (outputAccessor.type) {
|
|
case TINYGLTF_TYPE_VEC4: {
|
|
switch (outputAccessor.componentType) {
|
|
case TINYGLTF_COMPONENT_TYPE_FLOAT: {
|
|
hg::debug("Output type is FLOAT");
|
|
|
|
v4fArray value(arrayAdapter<hg::Vec4>(outputDataPtr, output_count, output_byte_stride));
|
|
|
|
hg::debug(hg::format("value's size : %1").arg(value.size()).c_str());
|
|
anim->quat_tracks.push_back({"NO_MATCH", {}});
|
|
anim->flags |= hg::AF_UseQuaternionForRotation;
|
|
|
|
for (size_t k{0}; k < time.size(); ++k) {
|
|
auto t = hg::time_from_sec_f(time[k]);
|
|
auto v = value[k];
|
|
// hg::debug(hg::format("t[%1]: (%2), v[%3]: (%4, %5, %6,
|
|
// %7)").arg(k).arg(t).arg(k).arg(v.x).arg(v.y).arg(v.z).arg(v.w).c_str());
|
|
auto r = hg::ToEuler(hg::Quaternion(v.x, v.y, v.z, v.w));
|
|
hg::SetKey(anim->quat_tracks.back(), t, hg::QuaternionFromEuler(-r.x, -r.y, r.z));
|
|
}
|
|
anim->t_start = hg::time_from_sec_f(time[0]);
|
|
anim->t_end = hg::time_from_sec_f(time[time.size() - 1]);
|
|
|
|
scene_anim.t_start = hg::Min(scene_anim.t_start, anim->t_start);
|
|
scene_anim.t_end = hg::Max(scene_anim.t_end, anim->t_end);
|
|
} break;
|
|
default:
|
|
hg::error("Error: Animation values needs to be Float (else not implemented)");
|
|
break;
|
|
}
|
|
|
|
} break;
|
|
|
|
case TINYGLTF_TYPE_VEC3: {
|
|
switch (outputAccessor.componentType) {
|
|
case TINYGLTF_COMPONENT_TYPE_FLOAT: {
|
|
hg::debug("Output type is FLOAT");
|
|
|
|
v3fArray value(arrayAdapter<hg::Vec3>(outputDataPtr, output_count, output_byte_stride));
|
|
|
|
hg::debug(hg::format("value's size : %1").arg(value.size()).c_str());
|
|
anim->vec3_tracks.push_back({"NO_MATCH", {}});
|
|
|
|
for (size_t k{0}; k < time.size(); ++k) {
|
|
auto t = hg::time_from_sec_f(time[k]);
|
|
auto v = value[k];
|
|
|
|
// fix only for pos
|
|
if (target_path == "translation")
|
|
v.z = -v.z;
|
|
|
|
// hg::debug(hg::format("t[%1]: (%2), v[%3]: (%4, %5,
|
|
// %6)").arg(k).arg(t).arg(k).arg(v.x).arg(v.y).arg(v.z).c_str());
|
|
hg::SetKey(anim->vec3_tracks.back(), t, v);
|
|
}
|
|
anim->t_start = hg::time_from_sec_f(time[0]);
|
|
anim->t_end = hg::time_from_sec_f(time[time.size() - 1]);
|
|
|
|
scene_anim.t_start = hg::Min(scene_anim.t_start, anim->t_start);
|
|
scene_anim.t_end = hg::Max(scene_anim.t_end, anim->t_end);
|
|
} break;
|
|
default:
|
|
hg::error("Error: Animation values needs to be Float (else not implemented)");
|
|
break;
|
|
}
|
|
|
|
} break;
|
|
}
|
|
} break;
|
|
default:
|
|
hg::error("Error: Time values needs to be Float (else not implemented)");
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add animation channel, link from the anim track made above to the type of animaton and to the node
|
|
hg::debug((Indent(1) + "channels : [ ").c_str());
|
|
for (size_t j = 0; j < animation.channels.size(); j++) {
|
|
hg::debug(hg::format(Indent(2) + "sampler : %1").arg(animation.channels[j].sampler).c_str());
|
|
hg::debug(hg::format(Indent(2) + "target.id : %1").arg(animation.channels[j].target_node).c_str());
|
|
hg::debug(hg::format(Indent(2) + "target.path : %1").arg(animation.channels[j].target_path).c_str());
|
|
hg::debug((j != (animation.channels.size() - 1)) ? " , " : "");
|
|
|
|
if (animation.channels[j].sampler < anims.size()) {
|
|
auto anim_ref = anims[animation.channels[j].sampler];
|
|
auto anim = scene.GetAnim(anim_ref);
|
|
|
|
scene_anim.node_anims.push_back({idNode_to_NodeRef[animation.channels[j].target_node], anim_ref});
|
|
|
|
std::string target;
|
|
if (animation.channels[j].target_path == "translation")
|
|
target = "Position";
|
|
else if (animation.channels[j].target_path == "rotation")
|
|
target = "Rotation";
|
|
else if (animation.channels[j].target_path == "scale")
|
|
target = "Scale";
|
|
else if (animation.channels[j].target_path == "weights")
|
|
target = "Weights";
|
|
|
|
// find which track is not empty
|
|
if (anim->bool_tracks.size())
|
|
anim->bool_tracks.back().target = target;
|
|
else if (anim->int_tracks.size())
|
|
anim->int_tracks.back().target = target;
|
|
else if (anim->float_tracks.size())
|
|
anim->float_tracks.back().target = target;
|
|
else if (anim->vec2_tracks.size())
|
|
anim->vec2_tracks.back().target = target;
|
|
else if (anim->vec3_tracks.size())
|
|
anim->vec3_tracks.back().target = target;
|
|
else if (anim->vec4_tracks.size())
|
|
anim->vec4_tracks.back().target = target;
|
|
else if (anim->quat_tracks.size())
|
|
anim->quat_tracks.back().target = target;
|
|
}
|
|
}
|
|
hg::debug(" ]");
|
|
|
|
scene.AddSceneAnim(scene_anim);
|
|
}
|
|
}
|
|
|
|
//
|
|
static void ExportSkins(const Model &model, const Scene &gltf_scene, hg::Scene &scene, const Config &config) {
|
|
// all skins
|
|
hg::debug(hg::format("skin(items=%1)").arg(model.skins.size()).c_str());
|
|
for (size_t gltf_id_node = 0; gltf_id_node < model.nodes.size(); ++gltf_id_node) {
|
|
const auto &gltf_node = model.nodes[gltf_id_node];
|
|
if (gltf_node.skin >= 0) {
|
|
const auto &skin = model.skins[gltf_node.skin];
|
|
auto node = scene.GetNode(idNode_to_NodeRef[gltf_id_node]);
|
|
if (auto object = node.GetObject()) {
|
|
object.SetBoneCount(skin.joints.size());
|
|
for (size_t j = 0; j < skin.joints.size(); j++)
|
|
object.SetBone(j, idNode_to_NodeRef[skin.joints[j]]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----
|
|
static bool ExportDeformers(hg::Model &mdl, hg::Object *object) { return false; }
|
|
|
|
static void GetTextureData(const Model &model, const int textureIndex, hg::Picture &pic, const Config &config) {
|
|
auto texture = model.textures[textureIndex];
|
|
auto image = model.images[texture.source];
|
|
|
|
if (image.uri.empty()) {
|
|
std::string mimeType = image.mimeType.replace(image.mimeType.find("image/", 0), std::string("image/").size(), "");
|
|
std::string name = image.name.empty() ? hg::format("%1").arg(textureIndex) : image.name;
|
|
|
|
std::string dst_path;
|
|
GetOutputPath(dst_path, config.base_output_path, name, {}, mimeType, config.import_policy_texture);
|
|
hg::LoadPicture(pic, dst_path.c_str());
|
|
} else {
|
|
// copy the image
|
|
std::string src_path = image.uri;
|
|
if (!hg::Exists(src_path.c_str())) {
|
|
src_path = hg::CutFilePath(src_path);
|
|
|
|
if (hg::Exists(hg::PathJoin({hg::CutFileName(config.input_path), src_path}).c_str()))
|
|
src_path = hg::PathJoin({hg::CutFileName(config.input_path), src_path});
|
|
else if (hg::Exists(hg::PathJoin({hg::CutFileName(config.input_path), image.uri}).c_str()))
|
|
src_path = hg::PathJoin({hg::CutFileName(config.input_path), image.uri});
|
|
else if (!hg::Exists(src_path.c_str())) {
|
|
hg::error(hg::format("Missing texture file '%1'").arg(src_path));
|
|
}
|
|
}
|
|
hg::LoadPicture(pic, src_path.c_str());
|
|
}
|
|
}
|
|
|
|
static hg::TextureRef ExportTexture(
|
|
const Model &model, const int &textureIndex, const std::string &dst_path, const Config &config, hg::PipelineResources &resources) {
|
|
|
|
auto texture = model.textures[textureIndex];
|
|
uint32_t flags = BGFX_SAMPLER_NONE;
|
|
if (texture.sampler >= 0) {
|
|
auto sampler = model.samplers[texture.sampler];
|
|
|
|
switch (sampler.wrapS) {
|
|
case TINYGLTF_TEXTURE_WRAP_REPEAT:
|
|
break; // default
|
|
case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE:
|
|
flags |= BGFX_SAMPLER_U_CLAMP;
|
|
break;
|
|
}
|
|
switch (sampler.wrapT) {
|
|
case TINYGLTF_TEXTURE_WRAP_REPEAT:
|
|
break; // default
|
|
case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE:
|
|
flags |= BGFX_SAMPLER_V_CLAMP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::string dst_rel_path = MakeRelativeResourceName(dst_path, config.prj_path, config.prefix);
|
|
|
|
return resources.textures.Add(dst_rel_path.c_str(), {flags, BGFX_INVALID_HANDLE});
|
|
}
|
|
|
|
static std::string GetTextureMime(const Model &model, const int &textureIndex) {
|
|
if (textureIndex < 0)
|
|
return "png"; // set default to png
|
|
|
|
const auto &texture = model.textures[textureIndex];
|
|
const auto &image = model.images[texture.source];
|
|
|
|
std::string mimeType = image.mimeType;
|
|
if (const auto indexFind = mimeType.find("image/", 0) != std::string::npos)
|
|
mimeType = mimeType.replace(indexFind, std::string("image/").size(), "");
|
|
return mimeType;
|
|
}
|
|
//
|
|
static hg::TextureRef ExportTexture(const Model &model, const int &textureIndex, const Config &config, hg::PipelineResources &resources) {
|
|
if (textureIndex < 0)
|
|
return hg::InvalidTextureRef;
|
|
|
|
const auto &texture = model.textures[textureIndex];
|
|
const auto &image = model.images[texture.source];
|
|
|
|
std::string dst_path;
|
|
|
|
if (image.uri.empty()) {
|
|
std::string mimeType = GetTextureMime(model, textureIndex);
|
|
std::string name = image.name.empty() ? hg::format("%1").arg(textureIndex) : image.name;
|
|
|
|
GetOutputPath(dst_path, config.base_output_path, name, {}, mimeType, config.import_policy_texture);
|
|
} else {
|
|
// copy the image
|
|
std::string src_path = image.uri;
|
|
if (!hg::Exists(src_path.c_str())) {
|
|
if (hg::Exists(hg::PathJoin({hg::CutFileName(config.input_path), src_path}).c_str())) {
|
|
src_path = hg::PathJoin({hg::CutFileName(config.input_path), src_path});
|
|
} else {
|
|
src_path = hg::CutFilePath(src_path);
|
|
|
|
if (hg::Exists(hg::PathJoin({hg::CutFileName(config.input_path), src_path}).c_str()))
|
|
src_path = hg::PathJoin({hg::CutFileName(config.input_path), src_path});
|
|
else if (!hg::Exists(src_path.c_str())) {
|
|
hg::error(hg::format("Missing texture file '%1'").arg(src_path));
|
|
return {};
|
|
}
|
|
}
|
|
}
|
|
|
|
GetOutputPath(dst_path, config.base_output_path, hg::GetFileName(src_path), {}, hg::GetFileExtension(src_path), config.import_policy_texture);
|
|
}
|
|
return ExportTexture(model, textureIndex, dst_path, config, resources);
|
|
}
|
|
|
|
//
|
|
static hg::Material ExportMaterial(const Model &model, const Material &gltf_mat, const Config &config, hg::PipelineResources &resources) {
|
|
#if 0
|
|
// CWE 563: Variable is assigned a value that is never used
|
|
hg::Color diffuse = {0.5f, 0.5f, 0.5f, 1.f}, emissive = {0, 0, 0, 1}, specular = {0.5f, 0.5f, 0.5f, 1.f}, ambient = {0, 0, 0, 1};
|
|
#endif
|
|
|
|
float glossiness{1.f};
|
|
float reflection{1.f};
|
|
|
|
hg::debug(hg::format("Exporting material '%1'").arg(gltf_mat.name));
|
|
#if 0
|
|
|
|
static const std::string meta_RAW_text("{\"profiles\": {\"default\": {\"compression\": \"RAW\"}}}");
|
|
static const std::string meta_BC1_text("{\"profiles\": {\"default\": {\"compression\": \"BC1\"}}}");
|
|
static const std::string meta_BC3_text("{\"profiles\": {\"default\": {\"compression\": \"BC3\"}}}");
|
|
static const std::string meta_BC4_text("{\"profiles\": {\"default\": {\"compression\": \"BC4\"}}}");
|
|
static const std::string meta_BC6_text("{\"profiles\": {\"default\": {\"compression\": \"BC6\"}}}");
|
|
static const std::string meta_BC7_text("{\"profiles\": {\"default\": {\"compression\": \"BC7\"}}}");
|
|
#endif
|
|
static const std::string meta_BC5_text("{\"profiles\": {\"default\": {\"compression\": \"BC5\"}}}");
|
|
static const std::string meta_BC7_srgb_text("{\"profiles\": {\"default\": {\"compression\": \"BC7\", \"srgb\": 1}}}");
|
|
|
|
//
|
|
std::string dst_path;
|
|
hg::Material mat;
|
|
std::string shader("core/shader/pbr.hps");
|
|
|
|
// export the texture
|
|
auto baseColorTexture = ExportTexture(model, gltf_mat.pbrMetallicRoughness.baseColorTexture.index, config, resources);
|
|
if (baseColorTexture != hg::InvalidTextureRef) {
|
|
hg::debug(hg::format(" - uBaseOpacityMap: %1").arg(resources.textures.GetName(baseColorTexture)));
|
|
|
|
if (GetOutputPath(dst_path, config.prj_path, resources.textures.GetName(baseColorTexture), {}, "meta", config.import_policy_texture)) {
|
|
if (std::FILE *f = std::fopen(dst_path.c_str(), "w")) {
|
|
std::fwrite(meta_BC7_srgb_text.data(), sizeof meta_BC7_srgb_text[0], meta_BC7_srgb_text.size(), f);
|
|
std::fclose(f);
|
|
}
|
|
}
|
|
mat.textures["uBaseOpacityMap"] = {baseColorTexture, 0};
|
|
}
|
|
|
|
// make the orm from the possible value
|
|
hg::TextureRef metallicRoughnessTexture;
|
|
std::string meta_occlusionTexture("{\"profiles\": {\"default\": {\"compression\": \"BC7\"}}}");
|
|
if (gltf_mat.pbrMetallicRoughness.metallicRoughnessTexture.index >= 0) {
|
|
metallicRoughnessTexture = ExportTexture(model, gltf_mat.pbrMetallicRoughness.metallicRoughnessTexture.index, config, resources);
|
|
|
|
// if there is an occlusion texture and it's not the same as the map in metallicRoughness
|
|
if (gltf_mat.occlusionTexture.index >= 0 && gltf_mat.occlusionTexture.index != gltf_mat.pbrMetallicRoughness.metallicRoughnessTexture.index) {
|
|
hg::TextureRef OcclusionTexture = ExportTexture(model, gltf_mat.occlusionTexture.index, config, resources);
|
|
|
|
// if the occlusion texture use the same uv, merge into the same map as roughness metal
|
|
if (gltf_mat.occlusionTexture.texCoord == gltf_mat.pbrMetallicRoughness.metallicRoughnessTexture.texCoord) {
|
|
json meta_occlusionTexture_json = {{"profiles",
|
|
{{"default", {{"compression", "BC7"}, {"preprocess", {{"construct", {resources.textures.GetName(OcclusionTexture), "G", "B"}}}}}}}}};
|
|
meta_occlusionTexture = meta_occlusionTexture_json.dump();
|
|
}
|
|
} else if (gltf_mat.occlusionTexture.index < 0) { // no occlusion texture, use the var
|
|
json meta_occlusionTexture_json = {{"profiles", {{"default", {{"compression", "BC7"}, {"preprocess", {{"construct", {255, "G", "B"}}}}}}}}};
|
|
meta_occlusionTexture = meta_occlusionTexture_json.dump();
|
|
}
|
|
} else if (gltf_mat.occlusionTexture.index >= 0) {
|
|
// no orm map but an occlusion map is here
|
|
metallicRoughnessTexture = ExportTexture(model, gltf_mat.occlusionTexture.index, config, resources);
|
|
json meta_occlusionTexture_json = {{"profiles",
|
|
{{"default", {{"compression", "BC7"}, {"preprocess", {{"construct", {"R", (int)gltf_mat.pbrMetallicRoughness.roughnessFactor * 255,
|
|
(int)gltf_mat.pbrMetallicRoughness.metallicFactor * 255}}}}}}}}};
|
|
meta_occlusionTexture = meta_occlusionTexture_json.dump();
|
|
}
|
|
|
|
if (metallicRoughnessTexture != hg::InvalidTextureRef) {
|
|
hg::debug(hg::format(" - uOcclusionRoughnessMetalnessMap: %1").arg(resources.textures.GetName(metallicRoughnessTexture)));
|
|
|
|
if (GetOutputPath(dst_path, config.prj_path, resources.textures.GetName(metallicRoughnessTexture), {}, "meta", config.import_policy_texture)) {
|
|
if (std::FILE *f = std::fopen(dst_path.c_str(), "w")) {
|
|
std::fwrite(meta_occlusionTexture.data(), sizeof meta_occlusionTexture[0], meta_occlusionTexture.size(), f);
|
|
std::fclose(f);
|
|
}
|
|
}
|
|
mat.textures["uOcclusionRoughnessMetalnessMap"] = {metallicRoughnessTexture, 1};
|
|
}
|
|
|
|
// normal texture
|
|
auto normalTexture = ExportTexture(model, gltf_mat.normalTexture.index, config, resources);
|
|
if (normalTexture != hg::InvalidTextureRef) {
|
|
hg::debug(hg::format(" - uNormalMap: %1").arg(resources.textures.GetName(normalTexture)));
|
|
|
|
if (GetOutputPath(dst_path, config.prj_path, resources.textures.GetName(normalTexture), {}, "meta", config.import_policy_texture)) {
|
|
if (std::FILE *f = std::fopen(dst_path.c_str(), "w")) {
|
|
std::fwrite(meta_BC5_text.data(), sizeof meta_BC5_text[0], meta_BC5_text.size(), f);
|
|
std::fclose(f);
|
|
}
|
|
}
|
|
mat.textures["uNormalMap"] = {normalTexture, 2};
|
|
}
|
|
|
|
// emissive texture
|
|
auto emissiveTexture = ExportTexture(model, gltf_mat.emissiveTexture.index, config, resources);
|
|
if (emissiveTexture != hg::InvalidTextureRef) {
|
|
hg::debug(hg::format(" - uSelfMap: %1").arg(resources.textures.GetName(emissiveTexture)));
|
|
|
|
if (GetOutputPath(dst_path, config.prj_path, resources.textures.GetName(emissiveTexture), {}, "meta", config.import_policy_texture)) {
|
|
if (std::FILE *f = std::fopen(dst_path.c_str(), "w")) {
|
|
std::fwrite(meta_BC7_srgb_text.data(), sizeof meta_BC7_srgb_text[0], meta_BC7_srgb_text.size(), f);
|
|
std::fclose(f);
|
|
}
|
|
}
|
|
mat.textures["uSelfMap"] = {emissiveTexture, 4};
|
|
}
|
|
|
|
mat.values["uBaseOpacityColor"] = {
|
|
bgfx::UniformType::Vec4, {float(gltf_mat.pbrMetallicRoughness.baseColorFactor[0]), float(gltf_mat.pbrMetallicRoughness.baseColorFactor[1]),
|
|
float(gltf_mat.pbrMetallicRoughness.baseColorFactor[2]), float(gltf_mat.pbrMetallicRoughness.baseColorFactor[3])}};
|
|
mat.values["uOcclusionRoughnessMetalnessColor"] = {
|
|
bgfx::UniformType::Vec4, {1.f, float(gltf_mat.pbrMetallicRoughness.roughnessFactor), float(gltf_mat.pbrMetallicRoughness.metallicFactor), -1.f}};
|
|
mat.values["uSelfColor"] = {
|
|
bgfx::UniformType::Vec4, {float(gltf_mat.emissiveFactor[0]), float(gltf_mat.emissiveFactor[1]), float(gltf_mat.emissiveFactor[2]), -1.f}};
|
|
|
|
if (gltf_mat.alphaMode == "BLEND" || gltf_mat.alphaMode == "MASK")
|
|
SetMaterialBlendMode(mat, hg::BM_Alpha);
|
|
|
|
if (gltf_mat.doubleSided)
|
|
SetMaterialFaceCulling(mat, hg::FC_Disabled);
|
|
|
|
if (!config.shader.empty())
|
|
shader = config.shader; // use override
|
|
|
|
hg::debug(hg::format(" - Using pipeline shader '%1'").arg(shader));
|
|
mat.program = resources.programs.Add(shader.c_str(), {});
|
|
|
|
// FinalizeMaterial(mat, gltf_material->GetName(), geo_name);
|
|
return mat;
|
|
}
|
|
|
|
#define __PolIndex (pol_index[p] + v)
|
|
#define __PolRemapIndex (pol_index[p] + (geo.pol[p].vtx_count - 1 - v))
|
|
|
|
static void ExportGeometry(
|
|
const Model &model, const Primitive &meshPrimitive, const int &primitiveID, const Config &config, hg::PipelineResources &resources, hg::Geometry &geo) {
|
|
// TODO detect instancing (using SHA1 on model)
|
|
|
|
// Boolean used to check if we have converted the vertex buffer format
|
|
bool convertedToTriangleList = false;
|
|
// This permit to get a type agnostic way of reading the index buffer
|
|
std::unique_ptr<intArrayBase> indicesArrayPtr = nullptr;
|
|
|
|
if (meshPrimitive.indices == -1) {
|
|
hg::debug("ERROR: Can't load geometry without triangles indices");
|
|
return;
|
|
}
|
|
|
|
{
|
|
const auto &indicesAccessor = model.accessors[meshPrimitive.indices];
|
|
const auto &bufferView = model.bufferViews[indicesAccessor.bufferView];
|
|
const auto &buffer = model.buffers[bufferView.buffer];
|
|
const auto dataAddress = buffer.data.data() + bufferView.byteOffset + indicesAccessor.byteOffset;
|
|
const auto byteStride = indicesAccessor.ByteStride(bufferView);
|
|
const auto count = indicesAccessor.count;
|
|
|
|
// Allocate the index array in the pointer-to-base declared in the
|
|
// parent scope
|
|
switch (indicesAccessor.componentType) {
|
|
case TINYGLTF_COMPONENT_TYPE_BYTE:
|
|
indicesArrayPtr = std::unique_ptr<intArray<char>>(new intArray<char>(arrayAdapter<char>(dataAddress, count, byteStride)));
|
|
break;
|
|
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
|
|
indicesArrayPtr =
|
|
std::unique_ptr<intArray<unsigned char>>(new intArray<unsigned char>(arrayAdapter<unsigned char>(dataAddress, count, byteStride)));
|
|
break;
|
|
|
|
case TINYGLTF_COMPONENT_TYPE_SHORT:
|
|
indicesArrayPtr = std::unique_ptr<intArray<short>>(new intArray<short>(arrayAdapter<short>(dataAddress, count, byteStride)));
|
|
break;
|
|
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
|
|
indicesArrayPtr =
|
|
std::unique_ptr<intArray<unsigned short>>(new intArray<unsigned short>(arrayAdapter<unsigned short>(dataAddress, count, byteStride)));
|
|
break;
|
|
|
|
case TINYGLTF_COMPONENT_TYPE_INT:
|
|
indicesArrayPtr = std::unique_ptr<intArray<int>>(new intArray<int>(arrayAdapter<int>(dataAddress, count, byteStride)));
|
|
break;
|
|
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
|
|
indicesArrayPtr =
|
|
std::unique_ptr<intArray<unsigned int>>(new intArray<unsigned int>(arrayAdapter<unsigned int>(dataAddress, count, byteStride)));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
size_t start_id_pol = geo.pol.size();
|
|
size_t start_id_binding = geo.binding.size();
|
|
size_t start_id_vtx = geo.vtx.size();
|
|
|
|
if (indicesArrayPtr) {
|
|
const auto &indices = *indicesArrayPtr;
|
|
// hg::debug("indices: ");
|
|
// they are all triangles
|
|
geo.pol.resize(start_id_pol + indices.size() / 3);
|
|
for (size_t n = start_id_pol; n < geo.pol.size(); ++n) {
|
|
geo.pol[n].vtx_count = uint8_t(3);
|
|
geo.pol[n].material = primitiveID;
|
|
}
|
|
|
|
const auto pol_index = hg::ComputePolygonIndex(geo);
|
|
geo.binding.resize(hg::ComputeBindingCount(geo));
|
|
|
|
for (size_t p = start_id_pol; p < geo.pol.size(); ++p)
|
|
for (auto v = 0; v < geo.pol[p].vtx_count; ++v) {
|
|
geo.binding[pol_index[p] + v] = indices[pol_index[p] + (geo.pol[p].vtx_count - 1 - v) - start_id_binding] + start_id_vtx;
|
|
}
|
|
}
|
|
|
|
switch (meshPrimitive.mode) {
|
|
// We re-arrange the indices so that it describe a simple list of
|
|
// triangles
|
|
case TINYGLTF_MODE_TRIANGLE_FAN:
|
|
if (!convertedToTriangleList) {
|
|
hg::debug("TRIANGLE_FAN");
|
|
// This only has to be done once per primitive
|
|
convertedToTriangleList = true;
|
|
|
|
// We steal the guts of the vector
|
|
auto triangleFan = std::move(geo.binding);
|
|
// geo.binding.clear();
|
|
|
|
// Push back the indices that describe just one triangle one by one
|
|
for (size_t i{start_id_binding + 2}; i < triangleFan.size(); ++i) {
|
|
geo.binding.push_back(triangleFan[0]);
|
|
geo.binding.push_back(triangleFan[i - 1]);
|
|
geo.binding.push_back(triangleFan[i]);
|
|
}
|
|
}
|
|
case TINYGLTF_MODE_TRIANGLE_STRIP:
|
|
if (!convertedToTriangleList) {
|
|
hg::debug("TRIANGLE_STRIP");
|
|
// This only has to be done once per primitive
|
|
convertedToTriangleList = true;
|
|
|
|
auto triangleStrip = std::move(geo.binding);
|
|
// geo.binding.clear();
|
|
|
|
for (size_t i{start_id_binding + 2}; i < triangleStrip.size(); ++i) {
|
|
geo.binding.push_back(triangleStrip[i - 2]);
|
|
geo.binding.push_back(triangleStrip[i - 1]);
|
|
geo.binding.push_back(triangleStrip[i]);
|
|
}
|
|
}
|
|
case TINYGLTF_MODE_TRIANGLES: { // this is the simpliest case to handle
|
|
hg::debug("TRIANGLES");
|
|
|
|
using AttribWritter = std::function<void(float *w, uint32_t p)>;
|
|
AttribWritter w_position = [](float *w, uint32_t p) {};
|
|
AttribWritter w_normal = [](float *w, uint32_t p) {};
|
|
AttribWritter w_texcoord0 = [](float *w, uint32_t p) {};
|
|
AttribWritter w_texcoord1 = [](float *w, uint32_t p) {};
|
|
AttribWritter w_tangent = [](float *w, uint32_t p) {};
|
|
AttribWritter w_joints0 = [](float *w, uint32_t p) {};
|
|
AttribWritter w_weights0 = [](float *w, uint32_t p) {};
|
|
|
|
// get the accessor
|
|
for (const auto &attribute : meshPrimitive.attributes) {
|
|
const auto attribAccessor = model.accessors[attribute.second];
|
|
const auto &bufferView = model.bufferViews[attribAccessor.bufferView];
|
|
const auto &buffer = model.buffers[bufferView.buffer];
|
|
const auto dataPtr = buffer.data.data() + bufferView.byteOffset + attribAccessor.byteOffset;
|
|
const auto byte_stride = attribAccessor.ByteStride(bufferView);
|
|
const bool normalized = attribAccessor.normalized;
|
|
|
|
AttribWritter *writter = nullptr;
|
|
unsigned int max_components = 0;
|
|
if (attribute.first == "POSITION") {
|
|
writter = &w_position;
|
|
max_components = 3;
|
|
} else if (attribute.first == "NORMAL") {
|
|
writter = &w_normal;
|
|
max_components = 3;
|
|
} else if (attribute.first == "TEXCOORD_0") {
|
|
writter = &w_texcoord0;
|
|
max_components = 2;
|
|
} else if (attribute.first == "TEXCOORD_1") {
|
|
writter = &w_texcoord1;
|
|
max_components = 2;
|
|
} else if (attribute.first == "TANGENT") {
|
|
writter = &w_tangent;
|
|
max_components = 4;
|
|
} else if (attribute.first == "JOINTS_0") {
|
|
writter = &w_joints0;
|
|
max_components = 4;
|
|
} else if (attribute.first == "WEIGHTS_0") {
|
|
writter = &w_weights0;
|
|
max_components = 4;
|
|
}
|
|
|
|
if (!writter)
|
|
continue;
|
|
|
|
switch (attribAccessor.type) {
|
|
case TINYGLTF_TYPE_SCALAR:
|
|
max_components = std::min(max_components, 1u);
|
|
break;
|
|
case TINYGLTF_TYPE_VEC2:
|
|
max_components = std::min(max_components, 2u);
|
|
break;
|
|
case TINYGLTF_TYPE_VEC3:
|
|
max_components = std::min(max_components, 3u);
|
|
break;
|
|
case TINYGLTF_TYPE_VEC4:
|
|
max_components = std::min(max_components, 4u);
|
|
break;
|
|
}
|
|
|
|
switch (attribAccessor.componentType) {
|
|
case TINYGLTF_COMPONENT_TYPE_FLOAT:
|
|
*writter = [dataPtr, byte_stride, max_components](float *w, uint32_t p) {
|
|
const float *f = (const float *)(dataPtr + p * byte_stride);
|
|
for (unsigned int i = 0; i < max_components; ++i) {
|
|
w[i] = f[i];
|
|
}
|
|
};
|
|
break;
|
|
case TINYGLTF_COMPONENT_TYPE_DOUBLE:
|
|
*writter = [dataPtr, byte_stride, max_components](float *w, uint32_t p) {
|
|
const double *f = (const double *)(dataPtr + p * byte_stride);
|
|
for (unsigned int i = 0; i < max_components; ++i) {
|
|
w[i] = (float)f[i];
|
|
}
|
|
};
|
|
break;
|
|
case TINYGLTF_COMPONENT_TYPE_BYTE:
|
|
*writter = [dataPtr, byte_stride, max_components, normalized](float *w, uint32_t p) {
|
|
const int8_t *f = (const int8_t *)(dataPtr + p * byte_stride);
|
|
for (unsigned int i = 0; i < max_components; ++i) {
|
|
w[i] = normalized ? f[i] / (float)128 : f[i];
|
|
}
|
|
};
|
|
break;
|
|
case TINYGLTF_COMPONENT_TYPE_SHORT:
|
|
*writter = [dataPtr, byte_stride, max_components, normalized](float *w, uint32_t p) {
|
|
const int16_t *f = (const int16_t *)(dataPtr + p * byte_stride);
|
|
for (unsigned int i = 0; i < max_components; ++i) {
|
|
w[i] = normalized ? f[i] / (float)32768 : f[i];
|
|
}
|
|
};
|
|
break;
|
|
case TINYGLTF_COMPONENT_TYPE_INT:
|
|
*writter = [dataPtr, byte_stride, max_components, normalized](float *w, uint32_t p) {
|
|
const int32_t *f = (const int32_t *)(dataPtr + p * byte_stride);
|
|
for (unsigned int i = 0; i < max_components; ++i) {
|
|
w[i] = normalized ? f[i] / (float)2147483648 : f[i];
|
|
}
|
|
};
|
|
break;
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
|
|
*writter = [dataPtr, byte_stride, max_components, normalized](float *w, uint32_t p) {
|
|
const uint8_t *f = (const uint8_t *)(dataPtr + p * byte_stride);
|
|
for (unsigned int i = 0; i < max_components; ++i) {
|
|
w[i] = normalized ? f[i] / (float)255 : f[i];
|
|
}
|
|
};
|
|
break;
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
|
|
*writter = [dataPtr, byte_stride, max_components, normalized](float *w, uint32_t p) {
|
|
const uint16_t *f = (const uint16_t *)(dataPtr + p * byte_stride);
|
|
for (unsigned int i = 0; i < max_components; ++i) {
|
|
w[i] = normalized ? f[i] / (float)65535 : f[i];
|
|
}
|
|
};
|
|
break;
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
|
|
*writter = [dataPtr, byte_stride, max_components, normalized](float *w, uint32_t p) {
|
|
const uint32_t *f = (const uint32_t *)(dataPtr + p * byte_stride);
|
|
for (unsigned int i = 0; i < max_components; ++i) {
|
|
w[i] = normalized ? f[i] / (float)4294967295 : f[i];
|
|
}
|
|
};
|
|
break;
|
|
default:
|
|
assert(!"Not supported component type (yet)");
|
|
}
|
|
}
|
|
|
|
// set the values
|
|
for (const auto &attribute : meshPrimitive.attributes) {
|
|
const auto attribAccessor = model.accessors[attribute.second];
|
|
const auto count = attribAccessor.count;
|
|
|
|
// hg::debug(hg::format("current attribute has count %1 and stride %2 bytes").arg(count).arg(byte_stride).c_str());
|
|
hg::debug(hg::format("attribute string is : %1").arg(attribute.first));
|
|
if (attribute.first == "POSITION") {
|
|
hg::debug("found position attribute");
|
|
|
|
// get the position min/max for computing the boundingbox
|
|
/* pMin.x = attribAccessor.minValues[0];
|
|
pMin.y = attribAccessor.minValues[1];
|
|
pMin.z = attribAccessor.minValues[2];
|
|
pMax.x = attribAccessor.maxValues[0];
|
|
pMax.y = attribAccessor.maxValues[1];
|
|
pMax.z = attribAccessor.maxValues[2];
|
|
*/
|
|
// 3D vector of float
|
|
hg::Vec3 v;
|
|
for (size_t i{0}; i < count; ++i) {
|
|
w_position(&v.x, i);
|
|
v.z = -v.z;
|
|
geo.vtx.push_back(v * config.geometry_scale);
|
|
}
|
|
}
|
|
|
|
if (attribute.first == "NORMAL") {
|
|
hg::debug("found normal attribute");
|
|
|
|
hg::Vec3 n;
|
|
for (size_t i{start_id_binding}; i < geo.binding.size(); ++i) {
|
|
w_normal(&n.x, geo.binding[i] - start_id_vtx);
|
|
n.z = -n.z;
|
|
geo.normal.push_back(n);
|
|
}
|
|
}
|
|
|
|
// Face varying comment on the normals is also true for the UVs
|
|
if (attribute.first == "TEXCOORD_0") {
|
|
hg::debug("Found texture coordinates 0");
|
|
|
|
hg::Vec2 uv;
|
|
for (size_t i{start_id_binding}; i < geo.binding.size(); ++i) {
|
|
w_texcoord0(&uv.x, geo.binding[i] - start_id_vtx);
|
|
geo.uv[0].push_back(uv);
|
|
}
|
|
}
|
|
// Face varying comment on the normals is also true for the UVs
|
|
if (attribute.first == "TEXCOORD_1") {
|
|
hg::debug("Found texture coordinates 1");
|
|
|
|
hg::Vec2 uv;
|
|
for (size_t i{start_id_binding}; i < geo.binding.size(); ++i) {
|
|
w_texcoord1(&uv.x, geo.binding[i] - start_id_vtx);
|
|
geo.uv[1].push_back(uv);
|
|
}
|
|
}
|
|
|
|
// JOINTS_0
|
|
if (attribute.first == "JOINTS_0") {
|
|
hg::debug("found JOINTS_0 attribute");
|
|
|
|
if (geo.skin.size() < count + start_id_vtx)
|
|
geo.skin.resize(count + start_id_vtx);
|
|
|
|
hg::Vec4 joints;
|
|
for (size_t i{0}; i < count; ++i) {
|
|
w_joints0(&joints.x, i);
|
|
geo.skin[i + start_id_vtx].index[0] = hg::numeric_cast<uint16_t>((int)(joints.x));
|
|
geo.skin[i + start_id_vtx].index[1] = hg::numeric_cast<uint16_t>((int)(joints.y));
|
|
geo.skin[i + start_id_vtx].index[2] = hg::numeric_cast<uint16_t>((int)(joints.z));
|
|
geo.skin[i + start_id_vtx].index[3] = hg::numeric_cast<uint16_t>((int)(joints.w));
|
|
}
|
|
}
|
|
|
|
// WEIGHTS_0
|
|
if (attribute.first == "WEIGHTS_0") {
|
|
hg::debug("found WEIGHTS_0 attribute");
|
|
|
|
if (geo.skin.size() < count + start_id_vtx)
|
|
geo.skin.resize(count + start_id_vtx);
|
|
|
|
hg::Vec4 weights;
|
|
for (size_t i{0}; i < count; ++i) {
|
|
w_weights0(&weights.x, i);
|
|
geo.skin[i + start_id_vtx].weight[0] = hg::pack_float<uint8_t>(weights.x);
|
|
geo.skin[i + start_id_vtx].weight[1] = hg::pack_float<uint8_t>(weights.y);
|
|
geo.skin[i + start_id_vtx].weight[2] = hg::pack_float<uint8_t>(weights.z);
|
|
geo.skin[i + start_id_vtx].weight[3] = hg::pack_float<uint8_t>(weights.w);
|
|
}
|
|
}
|
|
}
|
|
|
|
// special for the tangent, because it need the normal to compute the bitangent
|
|
for (const auto &attribute : meshPrimitive.attributes) {
|
|
const auto attribAccessor = model.accessors[attribute.second];
|
|
const auto count = attribAccessor.count;
|
|
|
|
// hg::debug(hg::format("current attribute has count %1 and stride %2 bytes").arg(count).arg(byte_stride).c_str());
|
|
|
|
hg::debug(hg::format("attribute string is : %1").arg(attribute.first).c_str());
|
|
if (attribute.first == "TANGENT") {
|
|
hg::debug("found tangent attribute");
|
|
|
|
hg::Vec4 t;
|
|
for (size_t i{start_id_binding}; i < geo.binding.size(); ++i) {
|
|
w_tangent(&t.x, geo.binding[i] - start_id_vtx);
|
|
geo.tangent.push_back(hg::Geometry::TangentFrame{
|
|
hg::Vec3(t.x, t.y, t.z), hg::Cross(geo.normal[geo.binding[i] - start_id_vtx], hg::Vec3(t.x, t.y, t.z)) * t.w});
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
hg::error("primitive mode not implemented");
|
|
break;
|
|
|
|
// These aren't triangles:
|
|
case TINYGLTF_MODE_POINTS:
|
|
case TINYGLTF_MODE_LINE:
|
|
case TINYGLTF_MODE_LINE_LOOP:
|
|
hg::error("primitive is not triangle based, ignoring");
|
|
}
|
|
}
|
|
|
|
//
|
|
static void ExportObject(const Model &model, const Node &gltf_node, hg::Node &node, hg::Scene &scene, const Config &config, hg::PipelineResources &resources,
|
|
const int &gltf_id_node) {
|
|
|
|
// if there is no mesh or no skin, nothing inside object
|
|
if (gltf_node.mesh < 0 && gltf_node.skin < 0)
|
|
return;
|
|
|
|
hg::Geometry geo;
|
|
std::string path = node.GetName();
|
|
std::string primitiveIds;
|
|
auto object = scene.CreateObject();
|
|
|
|
// check path geo
|
|
if (gltf_node.mesh >= 0) {
|
|
auto gltf_mesh = model.meshes[gltf_node.mesh];
|
|
if (!gltf_mesh.name.empty())
|
|
path = hg::CleanFileName(hg::CutFileExtension(gltf_mesh.name));
|
|
for (auto meshPrimitive : gltf_mesh.primitives) {
|
|
// add attribute ids to be sure to have this one particular geo and material
|
|
for (const auto &a : meshPrimitive.attributes)
|
|
primitiveIds += std::to_string(a.second) + "_";
|
|
primitiveIds += std::to_string(meshPrimitive.indices) + "_";
|
|
}
|
|
}
|
|
|
|
primitiveIds += "_" + std::to_string(gltf_node.skin);
|
|
bool found_geo = false;
|
|
|
|
// if there is set of primitive ids existing
|
|
auto primitiveIdsToGeoPath_itr = primitiveIdsToGeoPath.find(primitiveIds);
|
|
if (primitiveIdsToGeoPath_itr != primitiveIdsToGeoPath.end()) {
|
|
// get path to geo
|
|
path = primitiveIdsToGeoPath_itr->second;
|
|
GetOutputPath(path, config.base_output_path, path, {}, "geo", config.import_policy_geometry);
|
|
path = MakeRelativeResourceName(path, config.prj_path, config.prefix);
|
|
} else {
|
|
// export geo mesh
|
|
if (gltf_node.mesh >= 0) {
|
|
auto gltf_mesh = model.meshes[gltf_node.mesh];
|
|
|
|
int primitiveId = 0;
|
|
for (auto meshPrimitive : gltf_mesh.primitives) {
|
|
ExportGeometry(model, meshPrimitive, primitiveId, config, resources, geo);
|
|
++primitiveId;
|
|
}
|
|
|
|
const auto vtx_to_pol = hg::ComputeVertexToPolygon(geo);
|
|
auto vtx_normal = hg::ComputeVertexNormal(geo, vtx_to_pol, hg::Deg(45.f));
|
|
|
|
// recalculate normals
|
|
bool recalculate_normal = config.recalculate_normal;
|
|
if (geo.normal.empty())
|
|
recalculate_normal = true;
|
|
|
|
if (recalculate_normal) {
|
|
hg::debug(" - Recalculate normals");
|
|
geo.normal = vtx_normal;
|
|
} else
|
|
vtx_normal = geo.normal;
|
|
|
|
// recalculate tangent frame
|
|
bool recalculate_tangent = config.recalculate_tangent;
|
|
if (geo.tangent.empty())
|
|
recalculate_tangent = true;
|
|
else if (geo.tangent.size() != geo.normal.size()) { // be sure tangent is same size of normal, some strange things can happen with multiple submesh
|
|
hg::debug("CAREFUL Normal and Tangent are not the same size, can happen if you have submesh (some with tangent and some without)");
|
|
geo.tangent.resize(geo.normal.size());
|
|
}
|
|
|
|
if (recalculate_tangent) {
|
|
hg::debug(" - Recalculate tangent frames (MikkT)");
|
|
if (!geo.uv[0].empty())
|
|
geo.tangent = hg::ComputeVertexTangent(geo, vtx_normal, 0, hg::Deg(45.f));
|
|
}
|
|
}
|
|
// find bind pose in the skins
|
|
if (gltf_node.skin >= 0) {
|
|
hg::debug(hg::format("Exporting geometry skin"));
|
|
|
|
const auto &skin = model.skins[gltf_node.skin];
|
|
geo.bind_pose.resize(skin.joints.size());
|
|
|
|
const auto attribAccessor = model.accessors[skin.inverseBindMatrices];
|
|
const auto &bufferView = model.bufferViews[attribAccessor.bufferView];
|
|
const auto &buffer = model.buffers[bufferView.buffer];
|
|
const auto dataPtr = buffer.data.data() + bufferView.byteOffset + attribAccessor.byteOffset;
|
|
const auto byte_stride = attribAccessor.ByteStride(bufferView);
|
|
const auto count = attribAccessor.count;
|
|
|
|
switch (attribAccessor.type) {
|
|
case TINYGLTF_TYPE_MAT4: {
|
|
switch (attribAccessor.componentType) {
|
|
case TINYGLTF_COMPONENT_TYPE_DOUBLE:
|
|
case TINYGLTF_COMPONENT_TYPE_FLOAT: {
|
|
floatArray<float> value(arrayAdapter<float>(dataPtr, count * 16, sizeof(float)));
|
|
|
|
for (size_t k{0}; k < count; ++k) {
|
|
hg::Mat4 m_InverseBindMatrices(value[k * 16], value[k * 16 + 1], value[k * 16 + 2], value[k * 16 + 4], value[k * 16 + 5],
|
|
value[k * 16 + 6], value[k * 16 + 8], value[k * 16 + 9], value[k * 16 + 10], value[k * 16 + 12], value[k * 16 + 13],
|
|
value[k * 16 + 14]);
|
|
|
|
m_InverseBindMatrices = hg::InverseFast(m_InverseBindMatrices);
|
|
|
|
auto p = hg::GetT(m_InverseBindMatrices);
|
|
p.z = -p.z;
|
|
auto r = hg::GetR(m_InverseBindMatrices);
|
|
r.x = -r.x;
|
|
r.y = -r.y;
|
|
auto s = hg::GetS(m_InverseBindMatrices);
|
|
|
|
geo.bind_pose[k] = hg::InverseFast(hg::TransformationMat4(p, r, s));
|
|
}
|
|
} break;
|
|
default:
|
|
hg::error("Unhandeled component type for inverseBindMatrices");
|
|
}
|
|
} break;
|
|
default:
|
|
hg::error("Unhandeled MAT4 type for inverseBindMatrices");
|
|
}
|
|
}
|
|
|
|
// check if name already taken
|
|
auto geoPathOcurrence_itr = geoPathOcurrence.find(path);
|
|
if (geoPathOcurrence_itr != geoPathOcurrence.end()) {
|
|
geoPathOcurrence_itr->second++;
|
|
path += hg::format("%1").arg(geoPathOcurrence_itr->second).str();
|
|
} else
|
|
geoPathOcurrence[path] = 0;
|
|
|
|
// save it to geo to keep
|
|
primitiveIdsToGeoPath[primitiveIds] = path;
|
|
|
|
if (gltf_node.mesh >= 0 || gltf_node.skin >= 0) {
|
|
if (GetOutputPath(path, config.base_output_path, path, {}, "geo", config.import_policy_geometry)) {
|
|
hg::debug(hg::format("Export geometry to '%1'").arg(path));
|
|
hg::SaveGeometryToFile(path.c_str(), geo);
|
|
}
|
|
|
|
path = MakeRelativeResourceName(path, config.prj_path, config.prefix);
|
|
}
|
|
}
|
|
|
|
// add materials
|
|
if (gltf_node.mesh >= 0) {
|
|
auto gltf_mesh = model.meshes[gltf_node.mesh];
|
|
int primitiveId = 0;
|
|
for (auto meshPrimitive : gltf_mesh.primitives) {
|
|
object.SetMaterialCount(primitiveId + 1);
|
|
|
|
// MATERIALS
|
|
// 1 material per primitive
|
|
if (meshPrimitive.material >= 0) {
|
|
auto gltf_mat = model.materials[meshPrimitive.material];
|
|
auto mat = ExportMaterial(model, gltf_mat, config, resources);
|
|
if (geo.skin.size())
|
|
mat.flags |= hg::MF_EnableSkinning;
|
|
|
|
object.SetMaterial(primitiveId, std::move(mat));
|
|
object.SetMaterialName(primitiveId, gltf_mat.name.empty() ? hg::format("mat_%1").arg(primitiveId) : gltf_mat.name);
|
|
|
|
} else { // make a dummy material to see the object in the engine
|
|
hg::debug(hg::format(" - Has no material, set a dummy one"));
|
|
|
|
hg::Material mat;
|
|
std::string shader;
|
|
|
|
shader = "core/shader/pbr.hps";
|
|
|
|
if (!config.shader.empty())
|
|
shader = config.shader; // use override
|
|
|
|
hg::debug(hg::format(" - Using pipeline shader '%1'").arg(shader));
|
|
mat.program = resources.programs.Add(shader.c_str(), {});
|
|
|
|
mat.values["uBaseOpacityColor"] = {bgfx::UniformType::Vec4, {1.f, 1.f, 1.f, 1.f}};
|
|
mat.values["uOcclusionRoughnessMetalnessColor"] = {bgfx::UniformType::Vec4, {1.f, 1.f, 0.f, -1.f}};
|
|
mat.values["uSelfColor"] = {bgfx::UniformType::Vec4, {0.f, 0.f, 0.f, -1.f}};
|
|
|
|
object.SetMaterial(primitiveId, std::move(mat));
|
|
object.SetMaterialName(primitiveId, "dummy_mat");
|
|
}
|
|
++primitiveId;
|
|
}
|
|
}
|
|
|
|
// set object
|
|
if (gltf_node.mesh >= 0 || gltf_node.skin >= 0) {
|
|
node.SetObject(object);
|
|
object.SetModelRef(resources.models.Add(path.c_str(), {}));
|
|
}
|
|
}
|
|
|
|
static void ExportCamera(const Model &model, const Node &gltf_node, hg::Node &node, hg::Scene &scene, const Config &config, hg::PipelineResources &resources) {
|
|
auto camera = scene.CreateCamera();
|
|
|
|
auto gltf_camera = model.cameras[gltf_node.camera];
|
|
|
|
if (gltf_camera.type == "perspective") {
|
|
camera.SetZNear(gltf_camera.perspective.znear);
|
|
camera.SetZFar(gltf_camera.perspective.zfar);
|
|
camera.SetFov(float(gltf_camera.perspective.yfov));
|
|
camera.SetIsOrthographic(false);
|
|
} else if (gltf_camera.type == "orthographic") {
|
|
camera.SetZNear(gltf_camera.orthographic.znear);
|
|
camera.SetZFar(gltf_camera.orthographic.zfar);
|
|
camera.SetIsOrthographic(true);
|
|
}
|
|
node.SetCamera(camera);
|
|
}
|
|
|
|
static void ExportLight(const Model &model, const size_t &id_light, hg::Node &node, hg::Scene &scene, const Config &config, hg::PipelineResources &resources) {
|
|
auto light = scene.CreateLight();
|
|
|
|
auto gltf_light = model.lights[id_light];
|
|
|
|
light.SetDiffuseColor(hg::Color(gltf_light.color[0], gltf_light.color[1], gltf_light.color[2]));
|
|
light.SetDiffuseIntensity(gltf_light.intensity);
|
|
|
|
light.SetInnerAngle(gltf_light.spot.innerConeAngle);
|
|
light.SetOuterAngle(gltf_light.spot.outerConeAngle);
|
|
|
|
light.SetRadius(gltf_light.range);
|
|
|
|
if (gltf_light.type == "point") {
|
|
light.SetType(hg::LT_Point);
|
|
} else if (gltf_light.type == "directional") {
|
|
light.SetType(hg::LT_Linear);
|
|
} else if (gltf_light.type == "spot") {
|
|
light.SetType(hg::LT_Spot);
|
|
}
|
|
|
|
node.SetLight(light);
|
|
}
|
|
|
|
//
|
|
static hg::Node ExportNode(const Model &model, const int &gltf_id_node, hg::Scene &scene, const Config &config, hg::PipelineResources &resources) {
|
|
const auto &gltf_node = model.nodes[gltf_id_node];
|
|
auto node = scene.CreateNode((gltf_node.name.empty() ? hg::format("node%1").arg(gltf_id_node) : gltf_node.name));
|
|
idNode_to_NodeRef[gltf_id_node] = node.ref;
|
|
|
|
// check if disable
|
|
auto KHR_nodes_disable = gltf_node.extensions.find("KHR_nodes_disable");
|
|
if (KHR_nodes_disable != gltf_node.extensions.end()) {
|
|
if (KHR_nodes_disable->second.Has("visible") && KHR_nodes_disable->second.Get("visible").Get<bool>() == false)
|
|
node.Disable();
|
|
}
|
|
|
|
// set transform
|
|
node.SetTransform(scene.CreateTransform());
|
|
if (gltf_node.matrix.size()) {
|
|
hg::Mat4 m(gltf_node.matrix[0], gltf_node.matrix[1], gltf_node.matrix[2], gltf_node.matrix[4], gltf_node.matrix[5], gltf_node.matrix[6],
|
|
gltf_node.matrix[8], gltf_node.matrix[9], gltf_node.matrix[10], gltf_node.matrix[12], gltf_node.matrix[13], gltf_node.matrix[14]);
|
|
auto p = hg::GetT(m);
|
|
p.z = -p.z;
|
|
auto r = hg::GetR(m);
|
|
r.x = -r.x;
|
|
r.y = -r.y;
|
|
auto s = hg::GetS(m);
|
|
node.GetTransform().SetLocal(hg::TransformationMat4(p, r, s));
|
|
} else {
|
|
if (gltf_node.translation.size())
|
|
node.GetTransform().SetPos(hg::Vec3(gltf_node.translation[0], gltf_node.translation[1], -gltf_node.translation[2]));
|
|
else
|
|
node.GetTransform().SetRot(hg::Vec3(0.f, 0.f, 0.f));
|
|
|
|
if (gltf_node.rotation.size()) {
|
|
auto r = hg::ToEuler(hg::Quaternion(gltf_node.rotation[0], gltf_node.rotation[1], gltf_node.rotation[2], gltf_node.rotation[3]));
|
|
r.x = -r.x;
|
|
r.y = -r.y;
|
|
node.GetTransform().SetRot(r);
|
|
} else
|
|
node.GetTransform().SetRot(hg::Vec3(0.f, 0.f, 0.f));
|
|
|
|
if (gltf_node.scale.size())
|
|
node.GetTransform().SetScale(hg::Vec3(gltf_node.scale[0], gltf_node.scale[1], gltf_node.scale[2]));
|
|
else
|
|
node.GetTransform().SetScale(hg::Vec3(1.f, 1.f, 1.f));
|
|
}
|
|
|
|
// is it a camera
|
|
if (gltf_node.camera >= 0)
|
|
ExportCamera(model, gltf_node, node, scene, config, resources);
|
|
|
|
// is it a light
|
|
auto KHR_lights_punctual = gltf_node.extensions.find("KHR_lights_punctual");
|
|
if (KHR_lights_punctual != gltf_node.extensions.end()) {
|
|
if (KHR_lights_punctual->second.Has("light")) {
|
|
auto id_light = KHR_lights_punctual->second.Get("light").Get<int>();
|
|
ExportLight(model, id_light, node, scene, config, resources);
|
|
}
|
|
}
|
|
|
|
// is it a mesh
|
|
if (gltf_node.mesh >= 0 || gltf_node.skin >= 0) {
|
|
// if the node doesn't have a name, give the geo name, if there is one
|
|
if (gltf_node.name.empty() && gltf_node.mesh >= 0) {
|
|
auto gltf_mesh = model.meshes[gltf_node.mesh];
|
|
if (!gltf_mesh.name.empty())
|
|
node.SetName(hg::CutFileExtension(gltf_mesh.name));
|
|
}
|
|
ExportObject(model, gltf_node, node, scene, config, resources, gltf_id_node);
|
|
}
|
|
|
|
// import children
|
|
for (auto id_child : gltf_node.children) {
|
|
auto child = ExportNode(model, id_child, scene, config, resources);
|
|
if (child && node)
|
|
child.GetTransform().SetParent(node.ref);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
bool LoadImageDataEx(Image *image, const int image_idx, std::string *err, std::string *warn, int req_width, int req_height, const unsigned char *bytes,
|
|
int size, void *user_data) {
|
|
(void)user_data;
|
|
(void)warn;
|
|
|
|
Config *config = static_cast<Config *>(user_data);
|
|
|
|
std::string dst_path;
|
|
|
|
if (image->uri.empty()) { // copy the buffer into image
|
|
std::string mimeType = image->mimeType.replace(image->mimeType.find("image/", 0), std::string("image/").size(), "");
|
|
std::string name = image->name.empty() ? hg::format("%1").arg(image_idx) : image->name;
|
|
|
|
if (GetOutputPath(dst_path, config->base_output_path, name, {}, mimeType, config->import_policy_texture)) {
|
|
auto myfile = std::fstream(dst_path, std::ios::out | std::ios::binary);
|
|
myfile.write((const char *)bytes, size);
|
|
myfile.close();
|
|
}
|
|
} else {
|
|
// copy the image
|
|
std::string src_path = image->uri;
|
|
if (!hg::Exists(src_path.c_str())) {
|
|
if (hg::Exists(hg::PathJoin({hg::CutFileName(config->input_path), src_path}).c_str())) {
|
|
src_path = hg::PathJoin({hg::CutFileName(config->input_path), src_path});
|
|
} else {
|
|
src_path = hg::CutFilePath(src_path);
|
|
|
|
if (hg::Exists(hg::PathJoin({hg::CutFileName(config->input_path), src_path}).c_str()))
|
|
src_path = hg::PathJoin({hg::CutFileName(config->input_path), src_path});
|
|
else if (!hg::Exists(src_path.c_str())) {
|
|
hg::error(hg::format("Missing texture file '%1'").arg(src_path));
|
|
return {};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GetOutputPath(dst_path, config->base_output_path, hg::GetFileName(src_path), {}, hg::GetFileExtension(src_path), config->import_policy_texture)) {
|
|
auto myfile = std::fstream(dst_path, std::ios::out | std::ios::binary);
|
|
myfile.write((const char *)bytes, size);
|
|
myfile.close();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ImportGltfScene(const std::string &path, const Config &config) {
|
|
const auto t_start = hg::time_now();
|
|
|
|
if (config.base_output_path.empty())
|
|
return false;
|
|
// 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;
|
|
}
|
|
|
|
//
|
|
Model model;
|
|
TinyGLTF loader;
|
|
std::string err;
|
|
std::string warn;
|
|
|
|
// set our own save picture
|
|
loader.SetImageLoader(LoadImageDataEx, const_cast<Config *>(&config));
|
|
bool ret;
|
|
if (hg::tolower(hg::GetFileExtension(path)) == "gltf")
|
|
ret = loader.LoadASCIIFromFile(&model, &err, &warn, path);
|
|
else
|
|
ret = loader.LoadBinaryFromFile(&model, &err, &warn, path); // for binary glTF(.glb)
|
|
if (!ret) {
|
|
hg::error(hg::format("failed to load %1: %2").arg(path.c_str()).arg(err.c_str()));
|
|
return false;
|
|
}
|
|
|
|
if (!warn.empty()) {
|
|
hg::log(hg::format("warning %1: %2").arg(path.c_str()).arg(warn.c_str()));
|
|
}
|
|
|
|
hg::log("loaded glTF file has:");
|
|
hg::log(hg::format("%1 accessors").arg(model.accessors.size()).c_str());
|
|
hg::log(hg::format("%1 animations").arg(model.animations.size()).c_str());
|
|
hg::log(hg::format("%1 buffers").arg(model.buffers.size()).c_str());
|
|
hg::log(hg::format("%1 bufferViews").arg(model.bufferViews.size()).c_str());
|
|
hg::log(hg::format("%1 materials").arg(model.materials.size()).c_str());
|
|
hg::log(hg::format("%1 meshes").arg(model.meshes.size()).c_str());
|
|
hg::log(hg::format("%1 nodes").arg(model.nodes.size()).c_str());
|
|
hg::log(hg::format("%1 textures").arg(model.textures.size()).c_str());
|
|
hg::log(hg::format("%1 images").arg(model.images.size()).c_str());
|
|
hg::log(hg::format("%1 skins").arg(model.skins.size()).c_str());
|
|
hg::log(hg::format("%1 samplers").arg(model.samplers.size()).c_str());
|
|
hg::log(hg::format("%1 cameras").arg(model.cameras.size()).c_str());
|
|
hg::log(hg::format("%1 scenes").arg(model.scenes.size()).c_str());
|
|
hg::log(hg::format("%1 lights").arg(model.lights.size()).c_str());
|
|
|
|
if (!config.finalizer_script.empty())
|
|
if (!LoadFinalizerScript(config.finalizer_script))
|
|
return false;
|
|
|
|
for (auto gltf_scene : model.scenes) {
|
|
hg::Scene scene;
|
|
hg::PipelineResources resources;
|
|
|
|
for (auto gltf_id_node : gltf_scene.nodes) {
|
|
auto node = ExportNode(model, gltf_id_node, scene, config, resources);
|
|
}
|
|
|
|
ExportMotions(model, gltf_scene, scene, config);
|
|
ExportSkins(model, gltf_scene, scene, config);
|
|
|
|
FinalizeScene(scene);
|
|
|
|
// add default pbr map
|
|
scene.environment.brdf_map = resources.textures.Add("core/pbr/brdf.dds", {BGFX_SAMPLER_NONE, BGFX_INVALID_HANDLE});
|
|
scene.environment.probe = {};
|
|
scene.environment.probe.irradiance_map = resources.textures.Add("core/pbr/probe.hdr.irradiance", {BGFX_SAMPLER_NONE, BGFX_INVALID_HANDLE});
|
|
scene.environment.probe.radiance_map = resources.textures.Add("core/pbr/probe.hdr.radiance", {BGFX_SAMPLER_NONE, BGFX_INVALID_HANDLE});
|
|
|
|
std::string out_path;
|
|
if (GetOutputPath(out_path, config.base_output_path,
|
|
config.name.empty() ? hg::GetFileName(path) + (gltf_scene.name.empty() ? "" : "_" + gltf_scene.name) : config.name, {}, "scn",
|
|
config.import_policy_scene))
|
|
SaveSceneJsonToFile(out_path.c_str(), scene, resources);
|
|
}
|
|
|
|
hg::log(hg::format("Import complete, took %1 ms").arg(hg::time_to_ms(hg::time_now() - t_start)));
|
|
return true;
|
|
}
|
|
|
|
static ImportPolicy ImportPolicyFromString(const std::string &v) {
|
|
if (v == "skip")
|
|
return ImportPolicy::SkipExisting;
|
|
if (v == "overwrite")
|
|
return ImportPolicy::Overwrite;
|
|
if (v == "rename")
|
|
return ImportPolicy::Rename;
|
|
if (v == "skip_always")
|
|
return ImportPolicy::SkipAlways;
|
|
|
|
return ImportPolicy::SkipExisting;
|
|
}
|
|
|
|
static void OutputUsage(const hg::CmdLineFormat &cmd_format) {
|
|
hg::debug((std::string("Usage: gltf_importer ") + 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?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("GLTF->GS Converter %1 (%2)").arg(hg::get_version_string()).arg(hg::get_build_sha()).c_str());
|
|
|
|
hg::CmdLineFormat cmd_format = {
|
|
{
|
|
{"-fix-geometry-orientation", "Bake a 90? rotation on the X axis of exported geometries"},
|
|
{"-recalculate-normal", "Recreate the vertex normals of exported geometries"},
|
|
{"-recalculate-tangent", "Recreate the vertex tangent frames of exported geometries"},
|
|
{"-detect-geometry-instances", "Detect and optimize geometry instances"},
|
|
{"-anim-to-file", "Scene animations will be exported to separate files and not embedded in scene"},
|
|
{"-quiet", "Quiet log, only log errors"},
|
|
},
|
|
{
|
|
{"-out", "Output directory", true},
|
|
{"-base-resource-path", "Transform references to assets in this directory to be relative", true},
|
|
{"-name", "Specify the output scene name", true},
|
|
{"-prefix", "Specify the file system prefix from which relative assets are to be loaded from", true},
|
|
{"-all-policy", "All file output policy (skip, overwrite, rename or skip_always) [default=skip]", true},
|
|
{"-geometry-policy", "Geometry file output policy (skip, overwrite, rename or skip_always) [default=skip]", true},
|
|
{"-material-policy", "Material file output policy (skip, overwrite, rename or skip_always) [default=skip]", true},
|
|
{"-texture-policy", "Texture file output policy (skip, overwrite, rename or skip_always) [default=skip]", true},
|
|
{"-scene-policy", "Scene file output policy (skip, overwrite, rename or skip_always) [default=skip]", true},
|
|
{"-anim-policy",
|
|
"Animation file output policy (skip, overwrite, rename or skip_always) (note: only applies when saving animations to their own "
|
|
"file) [default=skip]",
|
|
true},
|
|
{"-geometry-scale", "Factor used to scale exported geometries", true},
|
|
{"-finalizer-script", "Path to the Lua finalizer script", true},
|
|
{"-shader", "Material pipeline shader [default=core/shader/pbr.hps]", true},
|
|
},
|
|
{
|
|
{"input", "Input GLTF file to convert"},
|
|
},
|
|
{
|
|
{"-o", "-out"},
|
|
{"-h", "-help"},
|
|
{"-q", "-quiet"},
|
|
{"-s", "-shader"},
|
|
},
|
|
};
|
|
|
|
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.prj_path = hg::CleanPath(hg::GetCmdLineSingleValue(cmd_content, "-base-resource-path", ""));
|
|
config.name = hg::CleanPath(hg::GetCmdLineSingleValue(cmd_content, "-name", ""));
|
|
config.prefix = hg::GetCmdLineSingleValue(cmd_content, "-prefix", "");
|
|
|
|
config.import_policy_anim = config.import_policy_geometry = config.import_policy_material = config.import_policy_scene = config.import_policy_texture =
|
|
ImportPolicyFromString(hg::GetCmdLineSingleValue(cmd_content, "-all-policy", "skip"));
|
|
config.import_policy_geometry = ImportPolicyFromString(hg::GetCmdLineSingleValue(cmd_content, "-geometry-policy", "skip"));
|
|
config.import_policy_material = ImportPolicyFromString(hg::GetCmdLineSingleValue(cmd_content, "-material-policy", "skip"));
|
|
config.import_policy_texture = ImportPolicyFromString(hg::GetCmdLineSingleValue(cmd_content, "-texture-policy", "skip"));
|
|
config.import_policy_scene = ImportPolicyFromString(hg::GetCmdLineSingleValue(cmd_content, "-scene-policy", "skip"));
|
|
config.import_policy_anim = ImportPolicyFromString(hg::GetCmdLineSingleValue(cmd_content, "-anim-policy", "skip"));
|
|
|
|
config.geometry_scale = hg::GetCmdLineSingleValue(cmd_content, "-geometry-scale", 1.f);
|
|
|
|
config.recalculate_normal = hg::GetCmdLineFlagValue(cmd_content, "-recalculate-normal");
|
|
config.recalculate_tangent = hg::GetCmdLineFlagValue(cmd_content, "-recalculate-tangent");
|
|
|
|
config.finalizer_script = hg::GetCmdLineSingleValue(cmd_content, "-finalizer-script", "");
|
|
|
|
config.shader = hg::GetCmdLineSingleValue(cmd_content, "-shader", "");
|
|
|
|
quiet = hg::GetCmdLineFlagValue(cmd_content, "-quiet");
|
|
|
|
//
|
|
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 = ImportGltfScene(cmd_content.positionals[0], config);
|
|
|
|
const auto msg = std::string("[ImportScene") + std::string(res ? ": OK]" : ": KO]");
|
|
hg::log(msg.c_str());
|
|
|
|
return res ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
}
|