Massive reorganisation.
This finishes the first phase of the FBX2glTF refactor, breaking utility classes out where things were getting too monolithic. There is an equally important cleanup phase coming where we wrench all the various parts of this code, including the historical ones that we've rarely touched as yet, into a single C++ style paradigm, and modernise everything to C++11 at least. But for now, we're just picking the pieces back on the floor so we can push 0.9.6 out. It's been far too long since a release.
This commit is contained in:
parent
2a4da70de0
commit
bbbba646de
|
@ -139,13 +139,24 @@ set(LIB_SOURCE_FILES
|
|||
src/FBX2glTF.h
|
||||
src/fbx/Fbx2Raw.cpp
|
||||
src/fbx/Fbx2Raw.hpp
|
||||
src/fbx/FbxLayerElementAccess.hpp
|
||||
src/fbx/FbxBlendShapesAccess.cpp
|
||||
src/fbx/FbxBlendShapesAccess.hpp
|
||||
src/fbx/FbxLayerElementAccess.hpp
|
||||
src/fbx/FbxMaterialInfo.hpp
|
||||
src/fbx/FbxMaterialsAccess.cpp
|
||||
src/fbx/FbxMaterialsAccess.hpp
|
||||
src/fbx/FbxRoughMetMaterialInfo.cpp
|
||||
src/fbx/FbxRoughMetMaterialInfo.hpp
|
||||
src/fbx/FbxSkinningAccess.cpp
|
||||
src/fbx/FbxSkinningAccess.hpp
|
||||
src/fbx/FbxTraditionalMaterialInfo.cpp
|
||||
src/fbx/FbxTraditionalMaterialInfo.hpp
|
||||
src/gltf/Raw2Gltf.cpp
|
||||
src/gltf/Raw2Gltf.hpp
|
||||
src/raw/RawModel.cpp
|
||||
src/raw/RawModel.hpp
|
||||
src/gltf/GltfModel.cpp
|
||||
src/gltf/GltfModel.hpp
|
||||
src/gltf/TextureBuilder.cpp
|
||||
src/gltf/TextureBuilder.hpp
|
||||
src/gltf/properties/AccessorData.cpp
|
||||
src/gltf/properties/AccessorData.hpp
|
||||
src/gltf/properties/AnimationData.cpp
|
||||
|
@ -174,23 +185,14 @@ set(LIB_SOURCE_FILES
|
|||
src/gltf/properties/TextureData.cpp
|
||||
src/gltf/properties/TextureData.hpp
|
||||
src/mathfu.hpp
|
||||
src/raw/RawModel.cpp
|
||||
src/raw/RawModel.hpp
|
||||
src/utils/File_Utils.cpp
|
||||
src/utils/File_Utils.hpp
|
||||
src/utils/Image_Utils.cpp
|
||||
src/utils/Image_Utils.hpp
|
||||
src/utils/String_Utils.cpp
|
||||
src/utils/String_Utils.hpp
|
||||
src/fbx/FbxBlendShapesAccess.cpp
|
||||
src/fbx/FbxBlendShapesAccess.hpp
|
||||
src/fbx/FbxLayerElementAccess.hpp
|
||||
src/fbx/FbxMaterialsAccess.cpp
|
||||
src/fbx/FbxMaterialsAccess.hpp
|
||||
src/fbx/FbxRoughMetMaterialInfo.cpp
|
||||
src/fbx/FbxRoughMetMaterialInfo.hpp
|
||||
src/fbx/FbxSkinningAccess.cpp
|
||||
src/fbx/FbxSkinningAccess.hpp
|
||||
src/fbx/FbxTraditionalMaterialInfo.cpp
|
||||
src/fbx/FbxTraditionalMaterialInfo.hpp
|
||||
)
|
||||
|
||||
add_library(libFBX2glTF STATIC ${LIB_SOURCE_FILES})
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#define FBX2GLTF_VERSION std::string("0.9.5")
|
||||
#define FBX2GLTF_VERSION std::string("0.9.6")
|
||||
|
||||
#include <fmt/printf.h>
|
||||
#include <fbxsdk.h>
|
||||
|
@ -39,3 +39,61 @@ using workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<
|
|||
using json = nlohmann::basic_json<workaround_fifo_map>;
|
||||
|
||||
extern bool verboseOutput;
|
||||
|
||||
/**
|
||||
* The variuos situations in which the user may wish for us to (re-)compute normals for our vertices.
|
||||
*/
|
||||
enum class ComputeNormalsOption {
|
||||
NEVER, // do not ever compute any normals (results in broken glTF for some sources)
|
||||
BROKEN, // replace zero-length normals in any mesh that has a normal layer
|
||||
MISSING, // if a mesh lacks normals, compute them all
|
||||
ALWAYS // compute a new normal for every vertex, obliterating whatever may have been there before
|
||||
};
|
||||
|
||||
enum class UseLongIndicesOptions {
|
||||
NEVER, // only ever use 16-bit indices
|
||||
AUTO, // use shorts or longs depending on vertex count
|
||||
ALWAYS, // only ever use 32-bit indices
|
||||
};
|
||||
|
||||
/**
|
||||
* User-supplied options that dictate the nature of the glTF being generated.
|
||||
*/
|
||||
struct GltfOptions
|
||||
{
|
||||
/**
|
||||
* If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that
|
||||
* specify the largest set of attributes that'll ever be kept for a vertex.
|
||||
* The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the
|
||||
* attributes to keep are inferred from which textures are supplied.
|
||||
*/
|
||||
int keepAttribs { -1 };
|
||||
/** Whether to output a .glb file, the binary format of glTF. */
|
||||
bool outputBinary { false };
|
||||
/** If non-binary, whether to inline all resources, for a single (large) .glTF file. */
|
||||
bool embedResources { false };
|
||||
|
||||
/** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */
|
||||
struct {
|
||||
bool enabled = false;
|
||||
int compressionLevel = -1;
|
||||
int quantBitsPosition = -1;
|
||||
int quantBitsTexCoord = -1;
|
||||
int quantBitsNormal = -1;
|
||||
int quantBitsColor = -1;
|
||||
int quantBitsGeneric = -1;
|
||||
} draco;
|
||||
|
||||
/** Whether to use KHR_materials_unlit to extend materials definitions. */
|
||||
bool useKHRMatUnlit { false };
|
||||
/** Whether to populate the pbrMetallicRoughness substruct in materials. */
|
||||
bool usePBRMetRough { false };
|
||||
/** Whether to include blend shape normals, if present according to the SDK. */
|
||||
bool useBlendShapeNormals { false };
|
||||
/** Whether to include blend shape tangents, if present according to the SDK. */
|
||||
bool useBlendShapeTangents { false };
|
||||
/** When to compute vertex normals from geometry. */
|
||||
ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN;
|
||||
/** When to use 32-bit indices. */
|
||||
UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO;
|
||||
};
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "Fbx2Raw.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
@ -24,7 +26,6 @@
|
|||
#include "utils/File_Utils.hpp"
|
||||
#include "utils/String_Utils.hpp"
|
||||
#include "raw/RawModel.hpp"
|
||||
#include "Fbx2Raw.hpp"
|
||||
|
||||
#include "FbxBlendShapesAccess.hpp"
|
||||
#include "FbxLayerElementAccess.hpp"
|
||||
|
|
|
@ -8,3 +8,73 @@
|
|||
*/
|
||||
|
||||
#include "GltfModel.hpp"
|
||||
|
||||
std::shared_ptr<BufferViewData> GltfModel::GetAlignedBufferView(BufferData &buffer, const BufferViewData::GL_ArrayType target)
|
||||
{
|
||||
unsigned long bufferSize = this->binary->size();
|
||||
if ((bufferSize % 4) > 0) {
|
||||
bufferSize += (4 - (bufferSize % 4));
|
||||
this->binary->resize(bufferSize);
|
||||
}
|
||||
return this->bufferViews.hold(new BufferViewData(buffer, bufferSize, target));
|
||||
}
|
||||
|
||||
// add a bufferview on the fly and copy data into it
|
||||
std::shared_ptr<BufferViewData> GltfModel::AddRawBufferView(BufferData &buffer, const char *source, uint32_t bytes)
|
||||
{
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
|
||||
bufferView->byteLength = bytes;
|
||||
|
||||
// make space for the new bytes (possibly moving the underlying data)
|
||||
unsigned long bufferSize = this->binary->size();
|
||||
this->binary->resize(bufferSize + bytes);
|
||||
|
||||
// and copy them into place
|
||||
memcpy(&(*this->binary)[bufferSize], source, bytes);
|
||||
return bufferView;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> GltfModel::AddBufferViewForFile(BufferData &buffer, const std::string &filename)
|
||||
{
|
||||
// see if we've already created a BufferViewData for this precise file
|
||||
auto iter = filenameToBufferView.find(filename);
|
||||
if (iter != filenameToBufferView.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> result;
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
if (file) {
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<char> fileBuffer(size);
|
||||
if (file.read(fileBuffer.data(), size)) {
|
||||
result = AddRawBufferView(buffer, fileBuffer.data(), size);
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename);
|
||||
}
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't open file %s, skipping file.\n", filename);
|
||||
}
|
||||
// note that we persist here not only success, but also failure, as nullptr
|
||||
filenameToBufferView[filename] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void GltfModel::serializeHolders(json &glTFJson)
|
||||
{
|
||||
serializeHolder(glTFJson, "buffers", buffers);
|
||||
serializeHolder(glTFJson, "bufferViews", bufferViews);
|
||||
serializeHolder(glTFJson, "scenes", scenes);
|
||||
serializeHolder(glTFJson, "accessors", accessors);
|
||||
serializeHolder(glTFJson, "images", images);
|
||||
serializeHolder(glTFJson, "samplers", samplers);
|
||||
serializeHolder(glTFJson, "textures", textures);
|
||||
serializeHolder(glTFJson, "materials", materials);
|
||||
serializeHolder(glTFJson, "meshes", meshes);
|
||||
serializeHolder(glTFJson, "skins", skins);
|
||||
serializeHolder(glTFJson, "animations", animations);
|
||||
serializeHolder(glTFJson, "cameras", cameras);
|
||||
serializeHolder(glTFJson, "nodes", nodes);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,25 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
|
||||
#include "gltf/properties/AccessorData.hpp"
|
||||
#include "gltf/properties/AnimationData.hpp"
|
||||
#include "gltf/properties/BufferData.hpp"
|
||||
#include "gltf/properties/BufferViewData.hpp"
|
||||
#include "gltf/properties/CameraData.hpp"
|
||||
#include "gltf/properties/ImageData.hpp"
|
||||
#include "gltf/properties/MaterialData.hpp"
|
||||
#include "gltf/properties/MeshData.hpp"
|
||||
#include "gltf/properties/NodeData.hpp"
|
||||
#include "gltf/properties/PrimitiveData.hpp"
|
||||
#include "gltf/properties/SamplerData.hpp"
|
||||
#include "gltf/properties/SceneData.hpp"
|
||||
#include "gltf/properties/SkinData.hpp"
|
||||
#include "gltf/properties/TextureData.hpp"
|
||||
|
||||
/**
|
||||
* glTF 2.0 is based on the idea that data structs within a file are referenced by index; an accessor will
|
||||
* point to the n:th buffer view, and so on. The Holder class takes a freshly instantiated class, and then
|
||||
|
@ -22,84 +39,39 @@
|
|||
* struct will, by design, outlive all other activity that takes place during in a single conversion run.
|
||||
*/
|
||||
template<typename T>
|
||||
struct Holder
|
||||
class Holder
|
||||
{
|
||||
std::vector<std::shared_ptr<T>> ptrs;
|
||||
public:
|
||||
std::shared_ptr<T> hold(T *ptr)
|
||||
{
|
||||
ptr->ix = ptrs.size();
|
||||
ptrs.emplace_back(ptr);
|
||||
return ptrs.back();
|
||||
}
|
||||
std::vector<std::shared_ptr<T>> ptrs;
|
||||
};
|
||||
|
||||
struct GltfModel
|
||||
class GltfModel
|
||||
{
|
||||
explicit GltfModel(bool _isGlb)
|
||||
: binary(new std::vector<uint8_t>),
|
||||
isGlb(_isGlb)
|
||||
public:
|
||||
explicit GltfModel(const GltfOptions &options)
|
||||
: binary(new std::vector<uint8_t>)
|
||||
, isGlb(options.outputBinary)
|
||||
, defaultSampler(nullptr)
|
||||
, defaultBuffer(buffers.hold(buildDefaultBuffer(options)))
|
||||
{
|
||||
defaultSampler = samplers.hold(buildDefaultSampler());
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> GetAlignedBufferView(BufferData &buffer, const BufferViewData::GL_ArrayType target)
|
||||
{
|
||||
unsigned long bufferSize = this->binary->size();
|
||||
if ((bufferSize % 4) > 0) {
|
||||
bufferSize += (4 - (bufferSize % 4));
|
||||
this->binary->resize(bufferSize);
|
||||
}
|
||||
return this->bufferViews.hold(new BufferViewData(buffer, bufferSize, target));
|
||||
}
|
||||
|
||||
// add a bufferview on the fly and copy data into it
|
||||
std::shared_ptr<BufferViewData> AddRawBufferView(BufferData &buffer, const char *source, uint32_t bytes)
|
||||
{
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
|
||||
bufferView->byteLength = bytes;
|
||||
|
||||
// make space for the new bytes (possibly moving the underlying data)
|
||||
unsigned long bufferSize = this->binary->size();
|
||||
this->binary->resize(bufferSize + bytes);
|
||||
|
||||
// and copy them into place
|
||||
memcpy(&(*this->binary)[bufferSize], source, bytes);
|
||||
return bufferView;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> AddBufferViewForFile(BufferData &buffer, const std::string &filename)
|
||||
{
|
||||
// see if we've already created a BufferViewData for this precise file
|
||||
auto iter = filenameToBufferView.find(filename);
|
||||
if (iter != filenameToBufferView.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> result;
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
if (file) {
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<char> fileBuffer(size);
|
||||
if (file.read(fileBuffer.data(), size)) {
|
||||
result = AddRawBufferView(buffer, fileBuffer.data(), size);
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename);
|
||||
}
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't open file %s, skipping file.\n", filename);
|
||||
}
|
||||
// note that we persist here not only success, but also failure, as nullptr
|
||||
filenameToBufferView[filename] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> GetAlignedBufferView(BufferData &buffer, const BufferViewData::GL_ArrayType target);
|
||||
std::shared_ptr<BufferViewData> AddRawBufferView(BufferData &buffer, const char *source, uint32_t bytes);
|
||||
std::shared_ptr<BufferViewData> AddBufferViewForFile(BufferData &buffer, const std::string &filename);
|
||||
|
||||
template<class T>
|
||||
std::shared_ptr<AccessorData> AddAccessorWithView(
|
||||
BufferViewData &bufferView, const GLType &type, const std::vector<T> &source, std::string name)
|
||||
{
|
||||
auto accessor = accessors.hold(new AccessorData(bufferView, type));
|
||||
auto accessor = accessors.hold(new AccessorData(bufferView, type, name));
|
||||
accessor->appendAsBinaryArray(source, *binary);
|
||||
bufferView.byteLength = accessor->byteLength();
|
||||
return accessor;
|
||||
|
@ -138,7 +110,7 @@ struct GltfModel
|
|||
accessor->count = attribArr.size();
|
||||
} else {
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER);
|
||||
accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr);
|
||||
accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr, std::string(""));
|
||||
}
|
||||
primitive.AddAttrib(attrDef.gltfName, *accessor);
|
||||
return accessor;
|
||||
|
@ -156,30 +128,14 @@ struct GltfModel
|
|||
}
|
||||
}
|
||||
|
||||
void serializeHolders(json &glTFJson)
|
||||
{
|
||||
serializeHolder(glTFJson, "buffers", buffers);
|
||||
serializeHolder(glTFJson, "bufferViews", bufferViews);
|
||||
serializeHolder(glTFJson, "scenes", scenes);
|
||||
serializeHolder(glTFJson, "accessors", accessors);
|
||||
serializeHolder(glTFJson, "images", images);
|
||||
serializeHolder(glTFJson, "samplers", samplers);
|
||||
serializeHolder(glTFJson, "textures", textures);
|
||||
serializeHolder(glTFJson, "materials", materials);
|
||||
serializeHolder(glTFJson, "meshes", meshes);
|
||||
serializeHolder(glTFJson, "skins", skins);
|
||||
serializeHolder(glTFJson, "animations", animations);
|
||||
serializeHolder(glTFJson, "cameras", cameras);
|
||||
serializeHolder(glTFJson, "nodes", nodes);
|
||||
}
|
||||
void serializeHolders(json &glTFJson);
|
||||
|
||||
const bool isGlb;
|
||||
|
||||
// cache BufferViewData instances that've already been created from a given filename
|
||||
std::map<std::string, std::shared_ptr<BufferViewData>> filenameToBufferView;
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t> > binary;
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t>> binary;
|
||||
|
||||
Holder<BufferData> buffers;
|
||||
Holder<BufferViewData> bufferViews;
|
||||
|
@ -194,4 +150,17 @@ struct GltfModel
|
|||
Holder<CameraData> cameras;
|
||||
Holder<NodeData> nodes;
|
||||
Holder<SceneData> scenes;
|
||||
|
||||
std::shared_ptr<SamplerData> defaultSampler;
|
||||
std::shared_ptr<BufferData> defaultBuffer;
|
||||
|
||||
private:
|
||||
SamplerData *buildDefaultSampler() {
|
||||
return new SamplerData();
|
||||
}
|
||||
BufferData *buildDefaultBuffer(const GltfOptions &options) {
|
||||
return options.outputBinary ?
|
||||
new BufferData(binary) :
|
||||
new BufferData(extBufferFilename, binary, options.embedResources);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "utils/String_Utils.hpp"
|
||||
#include "utils/Image_Utils.hpp"
|
||||
#include <utils/File_Utils.hpp>
|
||||
|
||||
#include "raw/RawModel.hpp"
|
||||
|
||||
#include "gltf/properties/AccessorData.hpp"
|
||||
|
@ -37,204 +38,13 @@
|
|||
#include "gltf/properties/SkinData.hpp"
|
||||
#include "gltf/properties/TextureData.hpp"
|
||||
|
||||
#include "TextureBuilder.hpp"
|
||||
#include "GltfModel.hpp"
|
||||
|
||||
typedef uint32_t TriangleIndex;
|
||||
|
||||
#define DEFAULT_SCENE_NAME "Root Scene"
|
||||
|
||||
/**
|
||||
* glTF 2.0 is based on the idea that data structs within a file are referenced by index; an accessor will
|
||||
* point to the n:th buffer view, and so on. The Holder class takes a freshly instantiated class, and then
|
||||
* creates, stored, and returns a shared_ptr<T> for it.
|
||||
*
|
||||
* The idea is that every glTF resource in the file will live as long as the Holder does, and the Holders
|
||||
* are all kept in the GLTFData struct. Clients may certainly cnhoose to perpetuate the full shared_ptr<T>
|
||||
* reference counting type, but generally speaking we pass around simple T& and T* types because the GLTFData
|
||||
* struct will, by design, outlive all other activity that takes place during in a single conversion run.
|
||||
*/
|
||||
template<typename T>
|
||||
struct Holder
|
||||
{
|
||||
std::vector<std::shared_ptr<T>> ptrs;
|
||||
std::shared_ptr<T> hold(T *ptr)
|
||||
{
|
||||
ptr->ix = ptrs.size();
|
||||
ptrs.emplace_back(ptr);
|
||||
return ptrs.back();
|
||||
}
|
||||
};
|
||||
|
||||
struct GLTFData
|
||||
{
|
||||
explicit GLTFData(bool _isGlb)
|
||||
: binary(new std::vector<uint8_t>),
|
||||
isGlb(_isGlb)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> GetAlignedBufferView(BufferData &buffer, const BufferViewData::GL_ArrayType target)
|
||||
{
|
||||
unsigned long bufferSize = this->binary->size();
|
||||
if ((bufferSize % 4) > 0) {
|
||||
bufferSize += (4 - (bufferSize % 4));
|
||||
this->binary->resize(bufferSize);
|
||||
}
|
||||
return this->bufferViews.hold(new BufferViewData(buffer, bufferSize, target));
|
||||
}
|
||||
|
||||
// add a bufferview on the fly and copy data into it
|
||||
std::shared_ptr<BufferViewData> AddRawBufferView(BufferData &buffer, const char *source, uint32_t bytes)
|
||||
{
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
|
||||
bufferView->byteLength = bytes;
|
||||
|
||||
// make space for the new bytes (possibly moving the underlying data)
|
||||
unsigned long bufferSize = this->binary->size();
|
||||
this->binary->resize(bufferSize + bytes);
|
||||
|
||||
// and copy them into place
|
||||
memcpy(&(*this->binary)[bufferSize], source, bytes);
|
||||
return bufferView;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> AddBufferViewForFile(BufferData &buffer, const std::string &filename)
|
||||
{
|
||||
// see if we've already created a BufferViewData for this precise file
|
||||
auto iter = filenameToBufferView.find(filename);
|
||||
if (iter != filenameToBufferView.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> result;
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
if (file) {
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<char> fileBuffer(size);
|
||||
if (file.read(fileBuffer.data(), size)) {
|
||||
result = AddRawBufferView(buffer, fileBuffer.data(), size);
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename);
|
||||
}
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't open file %s, skipping file.\n", filename);
|
||||
}
|
||||
// note that we persist here not only success, but also failure, as nullptr
|
||||
filenameToBufferView[filename] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::shared_ptr<AccessorData> AddAccessorWithView(
|
||||
BufferViewData &bufferView, const GLType &type, const std::vector<T> &source)
|
||||
{
|
||||
auto accessor = accessors.hold(new AccessorData(bufferView, type, std::string("")));
|
||||
accessor->appendAsBinaryArray(source, *binary);
|
||||
bufferView.byteLength = accessor->byteLength();
|
||||
return accessor;
|
||||
}
|
||||
|
||||
|
||||
template<class T>
|
||||
std::shared_ptr<AccessorData> AddAccessorWithView(
|
||||
BufferViewData &bufferView, const GLType &type, const std::vector<T> &source, std::string name)
|
||||
{
|
||||
auto accessor = accessors.hold(new AccessorData(bufferView, type, name));
|
||||
accessor->appendAsBinaryArray(source, *binary);
|
||||
bufferView.byteLength = accessor->byteLength();
|
||||
return accessor;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::shared_ptr<AccessorData> AddAccessorAndView(
|
||||
BufferData &buffer, const GLType &type, const std::vector<T> &source)
|
||||
{
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
|
||||
return AddAccessorWithView(*bufferView, type, source);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::shared_ptr<AccessorData> AddAttributeToPrimitive(
|
||||
BufferData &buffer, const RawModel &surfaceModel, PrimitiveData &primitive,
|
||||
const AttributeDefinition<T> &attrDef)
|
||||
{
|
||||
// copy attribute data into vector
|
||||
std::vector<T> attribArr;
|
||||
surfaceModel.GetAttributeArray<T>(attribArr, attrDef.rawAttributeIx);
|
||||
|
||||
std::shared_ptr<AccessorData> accessor;
|
||||
if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) {
|
||||
primitive.AddDracoAttrib(attrDef, attribArr);
|
||||
|
||||
accessor = accessors.hold(new AccessorData(attrDef.glType));
|
||||
accessor->count = attribArr.size();
|
||||
} else {
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER);
|
||||
accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr);
|
||||
}
|
||||
primitive.AddAttrib(attrDef.gltfName, *accessor);
|
||||
return accessor;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
void serializeHolder(json &glTFJson, std::string key, const Holder<T> holder)
|
||||
{
|
||||
if (!holder.ptrs.empty()) {
|
||||
std::vector<json> bits;
|
||||
for (const auto &ptr : holder.ptrs) {
|
||||
bits.push_back(ptr->serialize());
|
||||
}
|
||||
glTFJson[key] = bits;
|
||||
}
|
||||
}
|
||||
|
||||
void serializeHolders(json &glTFJson)
|
||||
{
|
||||
serializeHolder(glTFJson, "buffers", buffers);
|
||||
serializeHolder(glTFJson, "bufferViews", bufferViews);
|
||||
serializeHolder(glTFJson, "scenes", scenes);
|
||||
serializeHolder(glTFJson, "accessors", accessors);
|
||||
serializeHolder(glTFJson, "images", images);
|
||||
serializeHolder(glTFJson, "samplers", samplers);
|
||||
serializeHolder(glTFJson, "textures", textures);
|
||||
serializeHolder(glTFJson, "materials", materials);
|
||||
serializeHolder(glTFJson, "meshes", meshes);
|
||||
serializeHolder(glTFJson, "skins", skins);
|
||||
serializeHolder(glTFJson, "animations", animations);
|
||||
serializeHolder(glTFJson, "cameras", cameras);
|
||||
serializeHolder(glTFJson, "nodes", nodes);
|
||||
}
|
||||
|
||||
const bool isGlb;
|
||||
|
||||
// cache BufferViewData instances that've already been created from a given filename
|
||||
std::map<std::string, std::shared_ptr<BufferViewData>> filenameToBufferView;
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t> > binary;
|
||||
|
||||
|
||||
Holder<BufferData> buffers;
|
||||
Holder<BufferViewData> bufferViews;
|
||||
Holder<AccessorData> accessors;
|
||||
Holder<ImageData> images;
|
||||
Holder<SamplerData> samplers;
|
||||
Holder<TextureData> textures;
|
||||
Holder<MaterialData> materials;
|
||||
Holder<MeshData> meshes;
|
||||
Holder<SkinData> skins;
|
||||
Holder<AnimationData> animations;
|
||||
Holder<CameraData> cameras;
|
||||
Holder<NodeData> nodes;
|
||||
Holder<SceneData> scenes;
|
||||
};
|
||||
|
||||
static void WriteToVectorContext(void *context, void *data, int size) {
|
||||
auto *vec = static_cast<std::vector<char> *>(context);
|
||||
for (int ii = 0; ii < size; ii ++) {
|
||||
vec->push_back(((char *) data)[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sanity-checks existance and then returns a *reference* to the *Data instance
|
||||
* registered under that name. This is safe in the context of this tool, where all such data
|
||||
|
@ -311,7 +121,7 @@ ModelData *Raw2Gltf(
|
|||
fmt::printf("%7d animations\n", raw.GetAnimationCount());
|
||||
}
|
||||
|
||||
std::unique_ptr<GLTFData> gltf(new GLTFData(options.outputBinary));
|
||||
std::unique_ptr<GltfModel> gltf(new GltfModel(options));
|
||||
|
||||
std::map<long, std::shared_ptr<NodeData>> nodesById;
|
||||
std::map<std::string, std::shared_ptr<MaterialData>> materialsByName;
|
||||
|
@ -319,10 +129,7 @@ ModelData *Raw2Gltf(
|
|||
std::map<long, std::shared_ptr<MeshData>> meshBySurfaceId;
|
||||
|
||||
// for now, we only have one buffer; data->binary points to the same vector as that BufferData does.
|
||||
BufferData &buffer = *gltf->buffers.hold(
|
||||
options.outputBinary ?
|
||||
new BufferData(gltf->binary) :
|
||||
new BufferData(extBufferFilename, gltf->binary, options.embedResources));
|
||||
BufferData &buffer = *gltf->defaultBuffer;
|
||||
{
|
||||
//
|
||||
// nodes
|
||||
|
@ -398,232 +205,10 @@ ModelData *Raw2Gltf(
|
|||
// samplers
|
||||
//
|
||||
|
||||
SamplerData &defaultSampler = *gltf->samplers.hold(new SamplerData());
|
||||
|
||||
//
|
||||
// textures
|
||||
//
|
||||
|
||||
using pixel = std::array<float, 4>; // pixel components are floats in [0, 1]
|
||||
using pixel_merger = std::function<pixel(const std::vector<const pixel *>)>;
|
||||
|
||||
auto texIndicesKey = [&](std::vector<int> ixVec, std::string tag) -> std::string {
|
||||
std::string result = tag;
|
||||
for (int ix : ixVec) {
|
||||
result += "_" + std::to_string(ix);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new derived TextureData for the two given RawTexture indexes, or return a previously created one.
|
||||
* Each pixel in the derived texture will be determined from its equivalent in each source pixels, as decided
|
||||
* by the provided `combine` function.
|
||||
*/
|
||||
auto getDerivedTexture = [&](
|
||||
std::vector<int> rawTexIndices,
|
||||
const pixel_merger &combine,
|
||||
const std::string &tag,
|
||||
bool transparentOutput
|
||||
) -> std::shared_ptr<TextureData>
|
||||
{
|
||||
const std::string key = texIndicesKey(rawTexIndices, tag);
|
||||
auto iter = textureByIndicesKey.find(key);
|
||||
if (iter != textureByIndicesKey.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
auto describeChannel = [&](int channels) -> std::string {
|
||||
switch(channels) {
|
||||
case 1: return "G";
|
||||
case 2: return "GA";
|
||||
case 3: return "RGB";
|
||||
case 4: return "RGBA";
|
||||
default:
|
||||
return fmt::format("?%d?", channels);
|
||||
}
|
||||
};
|
||||
|
||||
// keep track of some texture data as we load them
|
||||
struct TexInfo {
|
||||
explicit TexInfo(int rawTexIx) : rawTexIx(rawTexIx) {}
|
||||
|
||||
const int rawTexIx;
|
||||
int width {};
|
||||
int height {};
|
||||
int channels {};
|
||||
uint8_t *pixels {};
|
||||
};
|
||||
|
||||
int width = -1, height = -1;
|
||||
std::string mergedFilename = tag;
|
||||
std::vector<TexInfo> texes { };
|
||||
for (const int rawTexIx : rawTexIndices) {
|
||||
TexInfo info(rawTexIx);
|
||||
if (rawTexIx >= 0) {
|
||||
const RawTexture &rawTex = raw.GetTexture(rawTexIx);
|
||||
const std::string &fileLoc = rawTex.fileLocation;
|
||||
const std::string &name = StringUtils::GetFileBaseString(StringUtils::GetFileNameString(fileLoc));
|
||||
if (!fileLoc.empty()) {
|
||||
info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0);
|
||||
if (!info.pixels) {
|
||||
fmt::printf("Warning: merge texture [%d](%s) could not be loaded.\n",
|
||||
rawTexIx,
|
||||
name);
|
||||
} else {
|
||||
if (width < 0) {
|
||||
width = info.width;
|
||||
height = info.height;
|
||||
} else if (width != info.width || height != info.height) {
|
||||
fmt::printf("Warning: texture %s (%d, %d) can't be merged with previous texture(s) of dimension (%d, %d)\n",
|
||||
name,
|
||||
info.width, info.height, width, height);
|
||||
// this is bad enough that we abort the whole merge
|
||||
return nullptr;
|
||||
}
|
||||
mergedFilename += "_" + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
texes.push_back(info);
|
||||
}
|
||||
// at the moment, the best choice of filename is also the best choice of name
|
||||
const std::string mergedName = mergedFilename;
|
||||
|
||||
if (width < 0) {
|
||||
// no textures to merge; bail
|
||||
return nullptr;
|
||||
}
|
||||
// TODO: which channel combinations make sense in input files?
|
||||
|
||||
// write 3 or 4 channels depending on whether or not we need transparency
|
||||
int channels = transparentOutput ? 4 : 3;
|
||||
|
||||
std::vector<uint8_t> mergedPixels(static_cast<size_t>(channels * width * height));
|
||||
std::vector<pixel> pixels(texes.size());
|
||||
std::vector<const pixel *> pixelPointers(texes.size());
|
||||
for (int xx = 0; xx < width; xx ++) {
|
||||
for (int yy = 0; yy < height; yy ++) {
|
||||
pixels.clear();
|
||||
for (int jj = 0; jj < texes.size(); jj ++) {
|
||||
const TexInfo &tex = texes[jj];
|
||||
// each texture's structure will depend on its channel count
|
||||
int ii = tex.channels * (xx + yy*width);
|
||||
int kk = 0;
|
||||
if (tex.pixels != nullptr) {
|
||||
for (; kk < tex.channels; kk ++) {
|
||||
pixels[jj][kk] = tex.pixels[ii++] / 255.0f;
|
||||
}
|
||||
}
|
||||
for (; kk < pixels[jj].size(); kk ++) {
|
||||
pixels[jj][kk] = 1.0f;
|
||||
}
|
||||
pixelPointers[jj] = &pixels[jj];
|
||||
}
|
||||
const pixel merged = combine(pixelPointers);
|
||||
int ii = channels * (xx + yy*width);
|
||||
for (int jj = 0; jj < channels; jj ++) {
|
||||
mergedPixels[ii + jj] = static_cast<uint8_t>(fmax(0, fmin(255.0f, merged[jj] * 255.0f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write a .png iff we need transparency in the destination texture
|
||||
bool png = transparentOutput;
|
||||
|
||||
std::vector<char> imgBuffer;
|
||||
int res;
|
||||
if (png) {
|
||||
res = stbi_write_png_to_func(WriteToVectorContext, &imgBuffer,
|
||||
width, height, channels, mergedPixels.data(), width * channels);
|
||||
} else {
|
||||
res = stbi_write_jpg_to_func(WriteToVectorContext, &imgBuffer,
|
||||
width, height, channels, mergedPixels.data(), 80);
|
||||
}
|
||||
if (!res) {
|
||||
fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ImageData *image;
|
||||
if (options.outputBinary) {
|
||||
const auto bufferView = gltf->AddRawBufferView(buffer, imgBuffer.data(), imgBuffer.size());
|
||||
image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
|
||||
|
||||
} else {
|
||||
const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg");
|
||||
const std::string imagePath = outputFolder + imageFilename;
|
||||
FILE *fp = fopen(imagePath.c_str(), "wb");
|
||||
if (fp == nullptr) {
|
||||
fmt::printf("Warning:: Couldn't write file '%s' for writing.\n", imagePath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fwrite(imgBuffer.data(), imgBuffer.size(), 1, fp) != 1) {
|
||||
fmt::printf("Warning: Failed to write %lu bytes to file '%s'.\n", imgBuffer.size(), imagePath);
|
||||
fclose(fp);
|
||||
return nullptr;
|
||||
}
|
||||
fclose(fp);
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Wrote %lu bytes to texture '%s'.\n", imgBuffer.size(), imagePath);
|
||||
}
|
||||
|
||||
image = new ImageData(mergedName, imageFilename);
|
||||
}
|
||||
|
||||
std::shared_ptr<TextureData> texDat = gltf->textures.hold(
|
||||
new TextureData(mergedName, defaultSampler, *gltf->images.hold(image)));
|
||||
textureByIndicesKey.insert(std::make_pair(key, texDat));
|
||||
return texDat;
|
||||
};
|
||||
|
||||
/** Create a new TextureData for the given RawTexture index, or return a previously created one. */
|
||||
auto getSimpleTexture = [&](int rawTexIndex, const std::string &tag) {
|
||||
const std::string key = texIndicesKey({ rawTexIndex }, tag);
|
||||
auto iter = textureByIndicesKey.find(key);
|
||||
if (iter != textureByIndicesKey.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
const RawTexture &rawTexture = raw.GetTexture(rawTexIndex);
|
||||
const std::string textureName = StringUtils::GetFileBaseString(rawTexture.name);
|
||||
const std::string relativeFilename = StringUtils::GetFileNameString(rawTexture.fileLocation);
|
||||
|
||||
ImageData *image = nullptr;
|
||||
if (options.outputBinary) {
|
||||
auto bufferView = gltf->AddBufferViewForFile(buffer, rawTexture.fileLocation);
|
||||
if (bufferView) {
|
||||
std::string suffix = StringUtils::GetFileSuffixString(rawTexture.fileLocation);
|
||||
image = new ImageData(relativeFilename, *bufferView, ImageUtils::suffixToMimeType(suffix));
|
||||
}
|
||||
|
||||
} else if (!relativeFilename.empty()) {
|
||||
image = new ImageData(relativeFilename, relativeFilename);
|
||||
std::string outputPath = outputFolder + relativeFilename;
|
||||
if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath)) {
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath);
|
||||
}
|
||||
} else {
|
||||
// no point commenting further on read/write error; CopyFile() does enough of that, and we
|
||||
// certainly want to to add an image struct to the glTF JSON, with the correct relative path
|
||||
// reference, even if the copy failed.
|
||||
}
|
||||
}
|
||||
if (!image) {
|
||||
// fallback is tiny transparent PNG
|
||||
image = new ImageData(
|
||||
textureName,
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
|
||||
);
|
||||
}
|
||||
|
||||
std::shared_ptr<TextureData> texDat = gltf->textures.hold(
|
||||
new TextureData(textureName, defaultSampler, *gltf->images.hold(image)));
|
||||
textureByIndicesKey.insert(std::make_pair(key, texDat));
|
||||
return texDat;
|
||||
};
|
||||
TextureBuilder textureBuilder(raw, options, outputFolder, *gltf);
|
||||
|
||||
//
|
||||
// materials
|
||||
|
@ -638,54 +223,15 @@ ModelData *Raw2Gltf(
|
|||
Vec3f emissiveFactor;
|
||||
float emissiveIntensity;
|
||||
|
||||
const Vec3f dielectric(0.04f, 0.04f, 0.04f);
|
||||
|
||||
// acquire the texture of a specific RawTextureUsage as *TextData, or nullptr if none exists
|
||||
auto simpleTex = [&](RawTextureUsage usage) -> std::shared_ptr<TextureData> {
|
||||
return (material.textures[usage] >= 0) ? getSimpleTexture(material.textures[usage], "simple") : nullptr;
|
||||
return (material.textures[usage] >= 0) ? textureBuilder.simple(material.textures[usage], "simple") : nullptr;
|
||||
};
|
||||
|
||||
TextureData *normalTexture = simpleTex(RAW_TEXTURE_USAGE_NORMAL).get();
|
||||
TextureData *emissiveTexture = simpleTex(RAW_TEXTURE_USAGE_EMISSIVE).get();
|
||||
TextureData *occlusionTexture = nullptr;
|
||||
|
||||
// acquire derived texture of single RawTextureUsage as *TextData, or nullptr if it doesn't exist
|
||||
auto merge1Tex = [&](
|
||||
const std::string tag,
|
||||
RawTextureUsage usage,
|
||||
const pixel_merger &combine,
|
||||
bool outputHasAlpha
|
||||
) -> std::shared_ptr<TextureData> {
|
||||
return getDerivedTexture({ material.textures[usage] }, combine, tag, outputHasAlpha);
|
||||
};
|
||||
|
||||
// acquire derived texture of two RawTextureUsage as *TextData, or nullptr if neither exists
|
||||
auto merge2Tex = [&](
|
||||
const std::string tag,
|
||||
RawTextureUsage u1,
|
||||
RawTextureUsage u2,
|
||||
const pixel_merger &combine,
|
||||
bool outputHasAlpha
|
||||
) -> std::shared_ptr<TextureData> {
|
||||
return getDerivedTexture(
|
||||
{ material.textures[u1], material.textures[u2] },
|
||||
combine, tag, outputHasAlpha);
|
||||
};
|
||||
|
||||
// acquire derived texture of two RawTextureUsage as *TextData, or nullptr if neither exists
|
||||
auto merge3Tex = [&](
|
||||
const std::string tag,
|
||||
RawTextureUsage u1,
|
||||
RawTextureUsage u2,
|
||||
RawTextureUsage u3,
|
||||
const pixel_merger &combine,
|
||||
bool outputHasAlpha
|
||||
) -> std::shared_ptr<TextureData> {
|
||||
return getDerivedTexture(
|
||||
{ material.textures[u1], material.textures[u2], material.textures[u3] },
|
||||
combine, tag, outputHasAlpha);
|
||||
};
|
||||
|
||||
std::shared_ptr<PBRMetallicRoughness> pbrMetRough;
|
||||
if (options.usePBRMetRough) {
|
||||
// albedo is a basic texture, no merging needed
|
||||
|
@ -702,12 +248,18 @@ ModelData *Raw2Gltf(
|
|||
*/
|
||||
RawMetRoughMatProps *props = (RawMetRoughMatProps *) material.info.get();
|
||||
// merge metallic into the blue channel and roughness into the green, of a new combinatory texture
|
||||
aoMetRoughTex = merge3Tex("ao_met_rough",
|
||||
RAW_TEXTURE_USAGE_OCCLUSION, RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_ROUGHNESS,
|
||||
[&](const std::vector<const pixel *> pixels) -> pixel {
|
||||
aoMetRoughTex = textureBuilder.combine(
|
||||
{
|
||||
material.textures[RAW_TEXTURE_USAGE_OCCLUSION],
|
||||
material.textures[RAW_TEXTURE_USAGE_METALLIC],
|
||||
material.textures[RAW_TEXTURE_USAGE_ROUGHNESS],
|
||||
},
|
||||
"ao_met_rough",
|
||||
[&](const std::vector<const TextureBuilder::pixel *> pixels) -> TextureBuilder::pixel {
|
||||
return { {(*pixels[0])[0], (*pixels[2])[0], (*pixels[1])[0], 1} };
|
||||
},
|
||||
false);
|
||||
|
||||
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
|
||||
diffuseFactor = props->diffuseFactor;
|
||||
metallic = props->metallic;
|
||||
|
@ -738,19 +290,21 @@ ModelData *Raw2Gltf(
|
|||
// shininess 2 -> roughness ~0.7
|
||||
// shininess 6 -> roughness 0.5
|
||||
// shininess 16 -> roughness ~0.33
|
||||
|
||||
// as shininess ==> oo, roughness ==> 0
|
||||
auto getRoughness = [&](float shininess) {
|
||||
return sqrtf(2.0f / (2.0f + shininess));
|
||||
};
|
||||
aoMetRoughTex = merge1Tex("ao_met_rough",
|
||||
RAW_TEXTURE_USAGE_SHININESS,
|
||||
[&](const std::vector<const pixel *> pixels) -> pixel {
|
||||
|
||||
aoMetRoughTex = textureBuilder.combine(
|
||||
{ material.textures[RAW_TEXTURE_USAGE_SHININESS], },
|
||||
"ao_met_rough",
|
||||
[&](const std::vector<const TextureBuilder::pixel *> pixels) -> TextureBuilder::pixel {
|
||||
// do not multiply with props->shininess; that doesn't work like the other factors.
|
||||
float shininess = props->shininess * (*pixels[0])[0];
|
||||
return { {0, getRoughness(shininess), metallic, 1} };
|
||||
},
|
||||
false);
|
||||
|
||||
if (aoMetRoughTex != nullptr) {
|
||||
// if we successfully built a texture, factors are just multiplicative identity
|
||||
metallic = roughness = 1.0f;
|
||||
|
@ -858,7 +412,7 @@ ModelData *Raw2Gltf(
|
|||
} else {
|
||||
const AccessorData &indexes = *gltf->AddAccessorWithView(
|
||||
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ELEMENT_ARRAY_BUFFER),
|
||||
useLongIndices ? GLT_UINT : GLT_USHORT, getIndexArray(surfaceModel));
|
||||
useLongIndices ? GLT_UINT : GLT_USHORT, getIndexArray(surfaceModel), std::string(""));
|
||||
primitive.reset(new PrimitiveData(indexes, mData));
|
||||
};
|
||||
|
||||
|
@ -992,7 +546,6 @@ ModelData *Raw2Gltf(
|
|||
//
|
||||
|
||||
for (int i = 0; i < raw.GetNodeCount(); i++) {
|
||||
|
||||
const RawNode &node = raw.GetNode(i);
|
||||
auto nodeData = gltf->nodes.ptrs[i];
|
||||
|
||||
|
|
|
@ -14,7 +14,10 @@
|
|||
|
||||
#include <utils/Image_Utils.hpp>
|
||||
#include <utils/String_Utils.hpp>
|
||||
#include <utils/File_Utils.hpp>
|
||||
|
||||
#include <gltf/properties/ImageData.hpp>
|
||||
#include <gltf/properties/TextureData.hpp>
|
||||
|
||||
// keep track of some texture data as we load them
|
||||
struct TexInfo {
|
||||
|
@ -30,7 +33,8 @@ struct TexInfo {
|
|||
std::shared_ptr<TextureData> TextureBuilder::combine(
|
||||
const std::vector<int> &ixVec,
|
||||
const std::string &tag,
|
||||
const pixel_merger &computePixel)
|
||||
const pixel_merger &computePixel,
|
||||
bool includeAlphaChannel)
|
||||
{
|
||||
const std::string key = texIndicesKey(ixVec, tag);
|
||||
auto iter = textureByIndicesKey.find(key);
|
||||
|
@ -80,7 +84,7 @@ std::shared_ptr<TextureData> TextureBuilder::combine(
|
|||
// TODO: which channel combinations make sense in input files?
|
||||
|
||||
// write 3 or 4 channels depending on whether or not we need transparency
|
||||
int channels = transparentOutput ? 4 : 3;
|
||||
int channels = includeAlphaChannel ? 4 : 3;
|
||||
|
||||
std::vector<uint8_t> mergedPixels(static_cast<size_t>(channels * width * height));
|
||||
std::vector<pixel> pixels(texes.size());
|
||||
|
@ -112,7 +116,7 @@ std::shared_ptr<TextureData> TextureBuilder::combine(
|
|||
}
|
||||
|
||||
// write a .png iff we need transparency in the destination texture
|
||||
bool png = transparentOutput;
|
||||
bool png = includeAlphaChannel;
|
||||
|
||||
std::vector<char> imgBuffer;
|
||||
int res;
|
||||
|
@ -130,9 +134,9 @@ std::shared_ptr<TextureData> TextureBuilder::combine(
|
|||
|
||||
ImageData *image;
|
||||
if (options.outputBinary) {
|
||||
const auto bufferView = gltf->AddRawBufferView(buffer, imgBuffer.data(), imgBuffer.size());
|
||||
return std::make_unique<ImageData>(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
|
||||
}
|
||||
const auto bufferView = gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), imgBuffer.size());
|
||||
image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
|
||||
} else {
|
||||
const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg");
|
||||
const std::string imagePath = outputFolder + imageFilename;
|
||||
FILE *fp = fopen(imagePath.c_str(), "wb");
|
||||
|
@ -150,11 +154,12 @@ std::shared_ptr<TextureData> TextureBuilder::combine(
|
|||
if (verboseOutput) {
|
||||
fmt::printf("Wrote %lu bytes to texture '%s'.\n", imgBuffer.size(), imagePath);
|
||||
}
|
||||
std::shared_ptr<TextureData> texDat = gltf->textures.hold(
|
||||
new TextureData(mergedName, defaultSampler, *image));
|
||||
image = new ImageData(mergedName, imageFilename);
|
||||
}
|
||||
std::shared_ptr<TextureData> texDat = gltf.textures.hold(
|
||||
new TextureData(mergedName, *gltf.defaultSampler, *gltf.images.hold(image)));
|
||||
textureByIndicesKey.insert(std::make_pair(key, texDat));
|
||||
|
||||
return std::make_unique<TextureData>(mergedName, imageFilename);
|
||||
return texDat;
|
||||
}
|
||||
|
||||
/** Create a new TextureData for the given RawTexture index, or return a previously created one. */
|
||||
|
@ -171,7 +176,7 @@ std::shared_ptr<TextureData> TextureBuilder::simple(int rawTexIndex, const std::
|
|||
|
||||
ImageData *image = nullptr;
|
||||
if (options.outputBinary) {
|
||||
auto bufferView = gltf->AddBufferViewForFile(buffer, rawTexture.fileLocation);
|
||||
auto bufferView = gltf.AddBufferViewForFile(*gltf.defaultBuffer, rawTexture.fileLocation);
|
||||
if (bufferView) {
|
||||
std::string suffix = StringUtils::GetFileSuffixString(rawTexture.fileLocation);
|
||||
image = new ImageData(relativeFilename, *bufferView, ImageUtils::suffixToMimeType(suffix));
|
||||
|
@ -198,8 +203,8 @@ std::shared_ptr<TextureData> TextureBuilder::simple(int rawTexIndex, const std::
|
|||
);
|
||||
}
|
||||
|
||||
std::shared_ptr<TextureData> texDat = gltf->textures.hold(
|
||||
new TextureData(textureName, defaultSampler, *gltf->images.hold(image)));
|
||||
std::shared_ptr<TextureData> texDat = gltf.textures.hold(
|
||||
new TextureData(textureName, *gltf.defaultSampler, *gltf.images.hold(image)));
|
||||
textureByIndicesKey.insert(std::make_pair(key, texDat));
|
||||
return texDat;
|
||||
|
||||
|
|
|
@ -15,14 +15,18 @@
|
|||
|
||||
#include <gltf/properties/ImageData.hpp>
|
||||
|
||||
using pixel = std::array<float, 4>; // pixel components are floats in [0, 1]
|
||||
using pixel_merger = std::function<pixel(const std::vector<const pixel *>)>;
|
||||
#include "GltfModel.hpp"
|
||||
|
||||
class TextureBuilder
|
||||
{
|
||||
public:
|
||||
TextureBuilder(const RawModel &raw, GltfModel &gltf)
|
||||
using pixel = std::array<float, 4>; // pixel components are floats in [0, 1]
|
||||
using pixel_merger = std::function<pixel(const std::vector<const pixel *>)>;
|
||||
|
||||
TextureBuilder(const RawModel &raw, const GltfOptions &options, const std::string &outputFolder, GltfModel &gltf)
|
||||
: raw(raw)
|
||||
, options(options)
|
||||
, outputFolder(outputFolder)
|
||||
, gltf(gltf)
|
||||
{}
|
||||
~TextureBuilder() {}
|
||||
|
@ -30,7 +34,8 @@ public:
|
|||
std::shared_ptr<TextureData> combine(
|
||||
const std::vector<int> &ixVec,
|
||||
const std::string &tag,
|
||||
const pixel_merger &mergeFunction
|
||||
const pixel_merger &mergeFunction,
|
||||
bool transparency
|
||||
);
|
||||
|
||||
std::shared_ptr<TextureData> simple(int rawTexIndex, const std::string &tag);
|
||||
|
@ -63,6 +68,9 @@ public:
|
|||
|
||||
private:
|
||||
const RawModel &raw;
|
||||
const GltfOptions &options;
|
||||
const std::string outputFolder;
|
||||
GltfModel &gltf;
|
||||
|
||||
std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
|
||||
};
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
struct SceneData : Holdable
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
struct SkinData : Holdable
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
struct TextureData : Holdable
|
||||
|
|
|
@ -15,63 +15,6 @@
|
|||
|
||||
#include "FBX2glTF.h"
|
||||
|
||||
/**
|
||||
* The variuos situations in which the user may wish for us to (re-)compute normals for our vertices.
|
||||
*/
|
||||
enum class ComputeNormalsOption {
|
||||
NEVER, // do not ever compute any normals (results in broken glTF for some sources)
|
||||
BROKEN, // replace zero-length normals in any mesh that has a normal layer
|
||||
MISSING, // if a mesh lacks normals, compute them all
|
||||
ALWAYS // compute a new normal for every vertex, obliterating whatever may have been there before
|
||||
};
|
||||
|
||||
enum class UseLongIndicesOptions {
|
||||
NEVER, // only ever use 16-bit indices
|
||||
AUTO, // use shorts or longs depending on vertex count
|
||||
ALWAYS, // only ever use 32-bit indices
|
||||
};
|
||||
|
||||
/**
|
||||
* User-supplied options that dictate the nature of the glTF being generated.
|
||||
*/
|
||||
struct GltfOptions
|
||||
{
|
||||
/**
|
||||
* If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that
|
||||
* specify the largest set of attributes that'll ever be kept for a vertex.
|
||||
* The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the
|
||||
* attributes to keep are inferred from which textures are supplied.
|
||||
*/
|
||||
int keepAttribs { -1 };
|
||||
/** Whether to output a .glb file, the binary format of glTF. */
|
||||
bool outputBinary { false };
|
||||
/** If non-binary, whether to inline all resources, for a single (large) .glTF file. */
|
||||
bool embedResources { false };
|
||||
|
||||
/** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */
|
||||
struct {
|
||||
bool enabled = false;
|
||||
int compressionLevel = -1;
|
||||
int quantBitsPosition = -1;
|
||||
int quantBitsTexCoord = -1;
|
||||
int quantBitsNormal = -1;
|
||||
int quantBitsColor = -1;
|
||||
int quantBitsGeneric = -1;
|
||||
} draco;
|
||||
|
||||
/** Whether to use KHR_materials_unlit to extend materials definitions. */
|
||||
bool useKHRMatUnlit { false };
|
||||
/** Whether to populate the pbrMetallicRoughness substruct in materials. */
|
||||
bool usePBRMetRough { false };
|
||||
/** Whether to include blend shape normals, if present according to the SDK. */
|
||||
bool useBlendShapeNormals { false };
|
||||
/** Whether to include blend shape tangents, if present according to the SDK. */
|
||||
bool useBlendShapeTangents { false };
|
||||
/** When to compute vertex normals from geometry. */
|
||||
ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN;
|
||||
/** When to use 32-bit indices. */
|
||||
UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO;
|
||||
};
|
||||
|
||||
enum RawVertexAttribute
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue