Move to C++17 and std::filesystem.
With this, we are able to get rid of all the increasingly broken file system utility code, and trust std::filesystem to handle all the cross- platform complexity. Unfortunately std::filesystem support remains a little elusive; it is well supported in Visual Studio (especially 2019), but not by default in Mac OS X, and even in GCC 8.0 it requires an explicit '-l c++fs'. This also silences some of the more egregious compiler warnings, mostly by being explicit about where we cast to the uint32 types glTF prefers.
This commit is contained in:
parent
769454e964
commit
95063ba9f1
|
@ -7,7 +7,7 @@ if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
|
|||
"Hint: mkdir -p build; cmake -H. -Bbuild; make -Cbuild\n")
|
||||
endif ()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
include(ExternalProject)
|
||||
|
@ -35,6 +35,13 @@ find_package(Iconv QUIET)
|
|||
# create a compilation database for e.g. clang-tidy
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
if (WIN32)
|
||||
add_compile_definitions(
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING
|
||||
)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
# this will suffice for now; don't really care about 32-bit
|
||||
set(LIBXML2_INCLUDE_DIRS ${FBXSDK_INCLUDE_DIR})
|
||||
|
@ -190,7 +197,6 @@ set(LIB_SOURCE_FILES
|
|||
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
|
||||
third_party/CLI11/CLI11.hpp
|
||||
)
|
||||
|
@ -237,6 +243,12 @@ if (WIN32)
|
|||
optimized ${ZLIB_LIBRARIES}
|
||||
debug ${ZLIB_LIBRARIES_DEBUG}
|
||||
)
|
||||
# quiet warnings related to fopen, sscanf
|
||||
target_compile_definitions(libFBX2glTF PRIVATE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING
|
||||
)
|
||||
|
||||
else()
|
||||
target_link_libraries(libFBX2glTF
|
||||
${LIBXML2_LIBRARIES}
|
||||
|
|
|
@ -13,13 +13,6 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define _stricmp strcasecmp
|
||||
#endif
|
||||
|
||||
#include <CLI11.hpp>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
|
@ -271,10 +264,7 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
if (outputPath.empty()) {
|
||||
// if -o is not given, default to the basename of the .fbx
|
||||
outputPath = fmt::format(
|
||||
".{}{}",
|
||||
(const char)StringUtils::GetPathSeparator(),
|
||||
StringUtils::GetFileBaseString(inputPath));
|
||||
outputPath = "./" + FileUtils::GetFileBaseString(inputPath);
|
||||
}
|
||||
// the output folder in .gltf mode, not used for .glb
|
||||
std::string outputFolder;
|
||||
|
@ -282,14 +272,17 @@ int main(int argc, char* argv[]) {
|
|||
// the path of the actual .glb or .gltf file
|
||||
std::string modelPath;
|
||||
if (gltfOptions.outputBinary) {
|
||||
// in binary mode, we write precisely where we're asked
|
||||
const auto& suffix = FileUtils::GetFileSuffix(outputPath);
|
||||
// add .glb to output path, unless it already ends in exactly that
|
||||
if (suffix.has_value() && suffix.value() == "glb") {
|
||||
modelPath = outputPath;
|
||||
} else {
|
||||
modelPath = outputPath + ".glb";
|
||||
|
||||
}
|
||||
} else {
|
||||
// in gltf mode, we create a folder and write into that
|
||||
outputFolder =
|
||||
fmt::format("{}_out{}", outputPath.c_str(), (const char)StringUtils::GetPathSeparator());
|
||||
modelPath = outputFolder + StringUtils::GetFileNameString(outputPath) + ".gltf";
|
||||
outputFolder = fmt::format("{}_out/", outputPath.c_str());
|
||||
modelPath = outputFolder + FileUtils::GetFileNameString(outputPath) + ".gltf";
|
||||
}
|
||||
if (!FileUtils::CreatePath(modelPath.c_str())) {
|
||||
fmt::fprintf(stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str());
|
||||
|
@ -302,7 +295,7 @@ int main(int argc, char* argv[]) {
|
|||
if (verboseOutput) {
|
||||
fmt::printf("Loading FBX File: %s\n", inputPath);
|
||||
}
|
||||
if (!LoadFBXFile(raw, inputPath.c_str(), "png;jpg;jpeg")) {
|
||||
if (!LoadFBXFile(raw, inputPath, {"png", "jpg", "jpeg"})) {
|
||||
fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath);
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,15 @@ using json = nlohmann::basic_json<workaround_fifo_map>;
|
|||
|
||||
extern bool verboseOutput;
|
||||
|
||||
/**
|
||||
* Centralises all the laborious downcasting from your OS' 64-bit
|
||||
* index variables down to the uint32s that glTF is built out of.
|
||||
*/
|
||||
inline uint32_t to_uint32(size_t n) {
|
||||
assert(n < UINT_MAX);
|
||||
return static_cast<uint32_t>(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* The variuos situations in which the user may wish for us to (re-)compute normals for our vertices.
|
||||
*/
|
||||
|
|
|
@ -925,7 +925,7 @@ static std::string GetInferredFileName(
|
|||
}
|
||||
// Get the file name with file extension.
|
||||
const std::string fileName =
|
||||
StringUtils::GetFileNameString(StringUtils::GetCleanPathString(fbxFileName));
|
||||
FileUtils::GetFileNameString(FileUtils::GetCanonicalPath(fbxFileName));
|
||||
|
||||
// Try to find a match with extension.
|
||||
for (const auto& file : directoryFileList) {
|
||||
|
@ -935,12 +935,12 @@ static std::string GetInferredFileName(
|
|||
}
|
||||
|
||||
// Get the file name without file extension.
|
||||
const std::string fileBase = StringUtils::GetFileBaseString(fileName);
|
||||
const std::string fileBase = FileUtils::GetFileBaseString(fileName);
|
||||
|
||||
// Try to find a match without file extension.
|
||||
for (const auto& file : directoryFileList) {
|
||||
// If the two extension-less base names match.
|
||||
if (StringUtils::CompareNoCase(fileBase, StringUtils::GetFileBaseString(file)) == 0) {
|
||||
if (StringUtils::CompareNoCase(fileBase, FileUtils::GetFileBaseString(file)) == 0) {
|
||||
// Return the name with extension of the file in the directory.
|
||||
return std::string(directory) + file;
|
||||
}
|
||||
|
@ -960,14 +960,14 @@ static std::string GetInferredFileName(
|
|||
*/
|
||||
static void FindFbxTextures(
|
||||
FbxScene* pScene,
|
||||
const char* fbxFileName,
|
||||
const char* extensions,
|
||||
const std::string fbxFileName,
|
||||
const std::set<std::string>& extensions,
|
||||
std::map<const FbxTexture*, FbxString>& textureLocations) {
|
||||
// Get the folder the FBX file is in.
|
||||
const std::string folder = StringUtils::GetFolderString(fbxFileName);
|
||||
const std::string folder = FileUtils::GetFolderString(fbxFileName);
|
||||
|
||||
// Check if there is a filename.fbm folder to which embedded textures were extracted.
|
||||
const std::string fbmFolderName = folder + StringUtils::GetFileBaseString(fbxFileName) + ".fbm/";
|
||||
const std::string fbmFolderName = folder + FileUtils::GetFileBaseString(fbxFileName) + ".fbm/";
|
||||
|
||||
// Search either in the folder with embedded textures or in the same folder as the FBX file.
|
||||
const std::string searchFolder = FileUtils::FolderExists(fbmFolderName) ? fbmFolderName : folder;
|
||||
|
@ -996,14 +996,17 @@ static void FindFbxTextures(
|
|||
}
|
||||
}
|
||||
|
||||
bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExtensions) {
|
||||
bool LoadFBXFile(
|
||||
RawModel& raw,
|
||||
const std::string fbxFileName,
|
||||
const std::set<std::string>& textureExtensions) {
|
||||
FbxManager* pManager = FbxManager::Create();
|
||||
FbxIOSettings* pIoSettings = FbxIOSettings::Create(pManager, IOSROOT);
|
||||
pManager->SetIOSettings(pIoSettings);
|
||||
|
||||
FbxImporter* pImporter = FbxImporter::Create(pManager, "");
|
||||
|
||||
if (!pImporter->Initialize(fbxFileName, -1, pManager->GetIOSettings())) {
|
||||
if (!pImporter->Initialize(fbxFileName.c_str(), -1, pManager->GetIOSettings())) {
|
||||
if (verboseOutput) {
|
||||
fmt::printf("%s\n", pImporter->GetStatus().GetErrorString());
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
|
||||
#include "raw/RawModel.hpp"
|
||||
|
||||
bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExtensions);
|
||||
bool LoadFBXFile(
|
||||
RawModel& raw,
|
||||
const std::string fbxFileName,
|
||||
const std::set<std::string>& textureExtensions);
|
||||
|
||||
json TranscribeProperty(FbxProperty& prop);
|
|
@ -95,7 +95,7 @@ class FbxBlendShapesAccess {
|
|||
}
|
||||
|
||||
FbxAnimCurve* GetAnimation(size_t channelIx, size_t animIx) const {
|
||||
return channels.at(channelIx).ExtractAnimation(animIx);
|
||||
return channels.at(channelIx).ExtractAnimation(to_uint32(animIx));
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -37,7 +37,7 @@ class FbxSkinningAccess {
|
|||
return jointNodes[jointIndex];
|
||||
}
|
||||
|
||||
const long GetJointId(const int jointIndex) const {
|
||||
const uint64_t GetJointId(const int jointIndex) const {
|
||||
return jointIds[jointIndex];
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ class FbxSkinningAccess {
|
|||
return jointInverseGlobalTransforms[jointIndex];
|
||||
}
|
||||
|
||||
const long GetRootNode() const {
|
||||
const uint64_t GetRootNode() const {
|
||||
assert(rootIndex != -1);
|
||||
return jointIds[rootIndex];
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class FbxSkinningAccess {
|
|||
|
||||
private:
|
||||
int rootIndex;
|
||||
std::vector<long> jointIds;
|
||||
std::vector<uint64_t> jointIds;
|
||||
std::vector<FbxNode*> jointNodes;
|
||||
std::vector<FbxMatrix> jointSkinningTransforms;
|
||||
std::vector<FbxMatrix> jointInverseGlobalTransforms;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
std::shared_ptr<BufferViewData> GltfModel::GetAlignedBufferView(
|
||||
BufferData& buffer,
|
||||
const BufferViewData::GL_ArrayType target) {
|
||||
unsigned long bufferSize = this->binary->size();
|
||||
uint32_t bufferSize = to_uint32(this->binary->size());
|
||||
if ((bufferSize % 4) > 0) {
|
||||
bufferSize += (4 - (bufferSize % 4));
|
||||
this->binary->resize(bufferSize);
|
||||
|
@ -27,7 +27,7 @@ GltfModel::AddRawBufferView(BufferData& buffer, const char* source, uint32_t byt
|
|||
bufferView->byteLength = bytes;
|
||||
|
||||
// make space for the new bytes (possibly moving the underlying data)
|
||||
unsigned long bufferSize = this->binary->size();
|
||||
uint32_t bufferSize = to_uint32(this->binary->size());
|
||||
this->binary->resize(bufferSize + bytes);
|
||||
|
||||
// and copy them into place
|
||||
|
@ -52,7 +52,7 @@ std::shared_ptr<BufferViewData> GltfModel::AddBufferViewForFile(
|
|||
|
||||
std::vector<char> fileBuffer(size);
|
||||
if (file.read(fileBuffer.data(), size)) {
|
||||
result = AddRawBufferView(buffer, fileBuffer.data(), size);
|
||||
result = AddRawBufferView(buffer, fileBuffer.data(), to_uint32(size));
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ template <typename T>
|
|||
class Holder {
|
||||
public:
|
||||
std::shared_ptr<T> hold(T* ptr) {
|
||||
ptr->ix = ptrs.size();
|
||||
ptr->ix = to_uint32(ptrs.size());
|
||||
ptrs.emplace_back(ptr);
|
||||
return ptrs.back();
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ class GltfModel {
|
|||
primitive.AddDracoAttrib(attrDef, attribArr);
|
||||
|
||||
accessor = accessors.hold(new AccessorData(attrDef.glType));
|
||||
accessor->count = attribArr.size();
|
||||
accessor->count = to_uint32(attribArr.size());
|
||||
} else {
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER);
|
||||
accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr, std::string(""));
|
||||
|
|
|
@ -256,29 +256,28 @@ ModelData* Raw2Gltf(
|
|||
if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
|
||||
/**
|
||||
* PBR FBX Material -> PBR Met/Rough glTF.
|
||||
*
|
||||
* METALLIC and ROUGHNESS textures are packed in G and B channels of a rough/met texture.
|
||||
* Other values translate directly.
|
||||
*/
|
||||
RawMetRoughMatProps* props = (RawMetRoughMatProps*)material.info.get();
|
||||
|
||||
// diffuse and emissive are noncontroversial
|
||||
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
|
||||
diffuseFactor = props->diffuseFactor;
|
||||
emissiveFactor = props->emissiveFactor;
|
||||
emissiveIntensity = props->emissiveIntensity;
|
||||
|
||||
// we always send the metallic/roughness factors onto the glTF generator
|
||||
metallic = props->metallic;
|
||||
roughness = props->roughness;
|
||||
|
||||
// determine if we need to generate a combined map
|
||||
bool hasMetallicMap = material.textures[RAW_TEXTURE_USAGE_METALLIC] >= 0;
|
||||
bool hasRoughnessMap = material.textures[RAW_TEXTURE_USAGE_ROUGHNESS] >= 0;
|
||||
bool hasOcclusionMap = material.textures[RAW_TEXTURE_USAGE_OCCLUSION] >= 0;
|
||||
bool atLeastTwoMaps = hasMetallicMap ? (hasRoughnessMap || hasOcclusionMap)
|
||||
: (hasRoughnessMap && hasMetallicMap);
|
||||
if (atLeastTwoMaps) {
|
||||
// if there's at least two of metallic/roughness/occlusion, it makes sense to
|
||||
// merge them: occlusion into the red channel, metallic into blue channel, and
|
||||
// roughness into the green.
|
||||
if (!atLeastTwoMaps) {
|
||||
// this handles the case of 0 or 1 maps supplied
|
||||
aoMetRoughTex = hasMetallicMap
|
||||
? simpleTex(RAW_TEXTURE_USAGE_METALLIC)
|
||||
: (hasRoughnessMap
|
||||
? simpleTex(RAW_TEXTURE_USAGE_ROUGHNESS)
|
||||
: (hasOcclusionMap ? simpleTex(RAW_TEXTURE_USAGE_OCCLUSION) : nullptr));
|
||||
} else {
|
||||
// otherwise merge occlusion into the red channel, metallic into blue channel, and
|
||||
// roughness into the green, of a new combinatory texture
|
||||
aoMetRoughTex = textureBuilder.combine(
|
||||
{
|
||||
material.textures[RAW_TEXTURE_USAGE_OCCLUSION],
|
||||
|
@ -289,27 +288,24 @@ ModelData* Raw2Gltf(
|
|||
[&](const std::vector<const TextureBuilder::pixel*> pixels)
|
||||
-> TextureBuilder::pixel {
|
||||
const float occlusion = (*pixels[0])[0];
|
||||
const float metallic = (*pixels[1])[0];
|
||||
const float roughness = (*pixels[2])[0];
|
||||
const float metallic = (*pixels[1])[0] * (hasMetallicMap ? 1 : props->metallic);
|
||||
const float roughness =
|
||||
(*pixels[2])[0] * (hasRoughnessMap ? 1 : props->roughness);
|
||||
return {{occlusion,
|
||||
props->invertRoughnessMap ? 1.0f - roughness : roughness,
|
||||
metallic,
|
||||
1}};
|
||||
},
|
||||
false);
|
||||
if (hasOcclusionMap) {
|
||||
// will only be true if there were actual non-trivial pixels
|
||||
}
|
||||
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
|
||||
diffuseFactor = props->diffuseFactor;
|
||||
metallic = props->metallic;
|
||||
roughness = props->roughness;
|
||||
emissiveFactor = props->emissiveFactor;
|
||||
emissiveIntensity = props->emissiveIntensity;
|
||||
// this will set occlusionTexture to null, if no actual occlusion map exists
|
||||
occlusionTexture = aoMetRoughTex.get();
|
||||
}
|
||||
} else {
|
||||
// this handles the case of 0 or 1 maps supplied
|
||||
if (hasMetallicMap) {
|
||||
aoMetRoughTex = simpleTex(RAW_TEXTURE_USAGE_METALLIC);
|
||||
} else if (hasRoughnessMap) {
|
||||
aoMetRoughTex = simpleTex(RAW_TEXTURE_USAGE_ROUGHNESS);
|
||||
}
|
||||
// else only occlusion map is possible: that check is handled further below
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Traditional FBX Material -> PBR Met/Rough glTF.
|
||||
|
@ -393,7 +389,6 @@ ModelData* Raw2Gltf(
|
|||
|
||||
khrCmnUnlitMat.reset(new KHRCmnUnlitMaterial());
|
||||
}
|
||||
// after all the special cases have had a go, check if we need to look up occlusion map
|
||||
if (!occlusionTexture) {
|
||||
occlusionTexture = simpleTex(RAW_TEXTURE_USAGE_OCCLUSION).get();
|
||||
}
|
||||
|
@ -447,11 +442,11 @@ ModelData* Raw2Gltf(
|
|||
|
||||
std::shared_ptr<PrimitiveData> primitive;
|
||||
if (options.draco.enabled) {
|
||||
int triangleCount = surfaceModel.GetTriangleCount();
|
||||
size_t triangleCount = surfaceModel.GetTriangleCount();
|
||||
|
||||
// initialize Draco mesh with vertex index information
|
||||
auto dracoMesh(std::make_shared<draco::Mesh>());
|
||||
dracoMesh->SetNumFaces(static_cast<size_t>(triangleCount));
|
||||
dracoMesh->SetNumFaces(triangleCount);
|
||||
dracoMesh->set_num_points(surfaceModel.GetVertexCount());
|
||||
|
||||
for (uint32_t ii = 0; ii < triangleCount; ii++) {
|
||||
|
@ -464,7 +459,7 @@ ModelData* Raw2Gltf(
|
|||
|
||||
AccessorData& indexes =
|
||||
*gltf->accessors.hold(new AccessorData(useLongIndices ? GLT_UINT : GLT_USHORT));
|
||||
indexes.count = 3 * triangleCount;
|
||||
indexes.count = to_uint32(3 * triangleCount);
|
||||
primitive.reset(new PrimitiveData(indexes, mData, dracoMesh));
|
||||
} else {
|
||||
const AccessorData& indexes = *gltf->AddAccessorWithView(
|
||||
|
@ -499,11 +494,13 @@ ModelData* Raw2Gltf(
|
|||
GLT_VEC3F,
|
||||
draco::GeometryAttribute::NORMAL,
|
||||
draco::DT_FLOAT32);
|
||||
const auto _ =
|
||||
gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_NORMAL);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) {
|
||||
const AttributeDefinition<Vec4f> ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F);
|
||||
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_TANGENT);
|
||||
const auto _ = gltf->AddAttributeToPrimitive<Vec4f>(
|
||||
buffer, surfaceModel, *primitive, ATTR_TANGENT);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) {
|
||||
const AttributeDefinition<Vec4f> ATTR_COLOR(
|
||||
|
@ -512,6 +509,7 @@ ModelData* Raw2Gltf(
|
|||
GLT_VEC4F,
|
||||
draco::GeometryAttribute::COLOR,
|
||||
draco::DT_FLOAT32);
|
||||
const auto _ =
|
||||
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_COLOR);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
|
||||
|
@ -521,7 +519,8 @@ ModelData* Raw2Gltf(
|
|||
GLT_VEC2F,
|
||||
draco::GeometryAttribute::TEX_COORD,
|
||||
draco::DT_FLOAT32);
|
||||
gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0);
|
||||
const auto _ = gltf->AddAttributeToPrimitive<Vec2f>(
|
||||
buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
|
||||
const AttributeDefinition<Vec2f> ATTR_TEXCOORD_1(
|
||||
|
@ -530,7 +529,8 @@ ModelData* Raw2Gltf(
|
|||
GLT_VEC2F,
|
||||
draco::GeometryAttribute::TEX_COORD,
|
||||
draco::DT_FLOAT32);
|
||||
gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
|
||||
const auto _ = gltf->AddAttributeToPrimitive<Vec2f>(
|
||||
buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) {
|
||||
const AttributeDefinition<Vec4i> ATTR_JOINTS(
|
||||
|
@ -539,6 +539,7 @@ ModelData* Raw2Gltf(
|
|||
GLT_VEC4I,
|
||||
draco::GeometryAttribute::GENERIC,
|
||||
draco::DT_UINT16);
|
||||
const auto _ =
|
||||
gltf->AddAttributeToPrimitive<Vec4i>(buffer, surfaceModel, *primitive, ATTR_JOINTS);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) {
|
||||
|
@ -548,6 +549,7 @@ ModelData* Raw2Gltf(
|
|||
GLT_VEC4F,
|
||||
draco::GeometryAttribute::GENERIC,
|
||||
draco::DT_FLOAT32);
|
||||
const auto _ =
|
||||
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_WEIGHTS);
|
||||
}
|
||||
|
||||
|
@ -633,7 +635,7 @@ ModelData* Raw2Gltf(
|
|||
draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer);
|
||||
assert(status.code() == draco::Status::OK);
|
||||
|
||||
auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), dracoBuffer.size());
|
||||
auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), to_uint32(dracoBuffer.size()));
|
||||
primitive->NoteDracoBuffer(*view);
|
||||
}
|
||||
mesh->AddPrimitive(primitive);
|
||||
|
@ -735,7 +737,7 @@ ModelData* Raw2Gltf(
|
|||
type = LightData::Type::Spot;
|
||||
break;
|
||||
}
|
||||
gltf->lights.hold(new LightData(
|
||||
const auto _ = gltf->lights.hold(new LightData(
|
||||
light.name,
|
||||
type,
|
||||
light.color,
|
||||
|
@ -842,13 +844,13 @@ ModelData* Raw2Gltf(
|
|||
gltfOutStream.write(glb2BinaryHeader, 8);
|
||||
|
||||
// append binary buffer directly to .glb file
|
||||
uint32_t binaryLength = gltf->binary->size();
|
||||
size_t binaryLength = gltf->binary->size();
|
||||
gltfOutStream.write((const char*)&(*gltf->binary)[0], binaryLength);
|
||||
while ((binaryLength % 4) != 0) {
|
||||
gltfOutStream.put('\0');
|
||||
binaryLength++;
|
||||
}
|
||||
uint32_t totalLength = (uint32_t)gltfOutStream.tellp();
|
||||
uint32_t totalLength = to_uint32(gltfOutStream.tellp());
|
||||
|
||||
// seek back to sub-header for json chunk
|
||||
gltfOutStream.seekp(8);
|
||||
|
|
|
@ -120,7 +120,7 @@ const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"};
|
|||
* The base of any indexed glTF entity.
|
||||
*/
|
||||
struct Holdable {
|
||||
uint32_t ix;
|
||||
uint32_t ix = UINT_MAX;
|
||||
|
||||
virtual json serialize() const = 0;
|
||||
};
|
||||
|
|
|
@ -49,8 +49,7 @@ std::shared_ptr<TextureData> TextureBuilder::combine(
|
|||
if (rawTexIx >= 0) {
|
||||
const RawTexture& rawTex = raw.GetTexture(rawTexIx);
|
||||
const std::string& fileLoc = rawTex.fileLocation;
|
||||
const std::string& name =
|
||||
StringUtils::GetFileBaseString(StringUtils::GetFileNameString(fileLoc));
|
||||
const std::string& name = FileUtils::GetFileBaseString(FileUtils::GetFileNameString(fileLoc));
|
||||
if (!fileLoc.empty()) {
|
||||
info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0);
|
||||
if (!info.pixels) {
|
||||
|
@ -142,7 +141,7 @@ std::shared_ptr<TextureData> TextureBuilder::combine(
|
|||
ImageData* image;
|
||||
if (options.outputBinary) {
|
||||
const auto bufferView =
|
||||
gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), imgBuffer.size());
|
||||
gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), to_uint32(imgBuffer.size()));
|
||||
image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
|
||||
} else {
|
||||
const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg");
|
||||
|
@ -180,20 +179,30 @@ std::shared_ptr<TextureData> TextureBuilder::simple(int rawTexIndex, const std::
|
|||
}
|
||||
|
||||
const RawTexture& rawTexture = raw.GetTexture(rawTexIndex);
|
||||
const std::string textureName = StringUtils::GetFileBaseString(rawTexture.name);
|
||||
const std::string relativeFilename = StringUtils::GetFileNameString(rawTexture.fileLocation);
|
||||
const std::string textureName = FileUtils::GetFileBaseString(rawTexture.name);
|
||||
const std::string relativeFilename = FileUtils::GetFileNameString(rawTexture.fileLocation);
|
||||
|
||||
ImageData* image = nullptr;
|
||||
if (options.outputBinary) {
|
||||
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));
|
||||
const auto& suffix = FileUtils::GetFileSuffix(rawTexture.fileLocation);
|
||||
std::string mimeType;
|
||||
if (suffix) {
|
||||
mimeType = ImageUtils::suffixToMimeType(suffix.value());
|
||||
} else {
|
||||
mimeType = "image/jpeg";
|
||||
fmt::printf(
|
||||
"Warning: Can't deduce mime type of texture '%s'; using %s.\n",
|
||||
rawTexture.fileLocation,
|
||||
mimeType);
|
||||
}
|
||||
image = new ImageData(relativeFilename, *bufferView, mimeType);
|
||||
}
|
||||
|
||||
} else if (!relativeFilename.empty()) {
|
||||
image = new ImageData(relativeFilename, relativeFilename);
|
||||
std::string outputPath = outputFolder + StringUtils::NormalizePath(relativeFilename);
|
||||
std::string outputPath = FileUtils::GetCanonicalPath(outputFolder + "/" + relativeFilename);
|
||||
if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath, true)) {
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath);
|
||||
|
|
|
@ -24,7 +24,7 @@ void AnimationData::AddNodeChannel(
|
|||
const AccessorData& accessor,
|
||||
std::string path) {
|
||||
assert(channels.size() == samplers.size());
|
||||
uint32_t ix = channels.size();
|
||||
uint32_t ix = to_uint32(channels.size());
|
||||
channels.emplace_back(channel_t(ix, node, std::move(path)));
|
||||
samplers.emplace_back(sampler_t(timeAccessor, accessor.ix));
|
||||
}
|
||||
|
|
|
@ -59,13 +59,15 @@ void to_json(json& j, const PBRMetallicRoughness& d) {
|
|||
if (d.baseColorFactor.LengthSquared() > 0) {
|
||||
j["baseColorFactor"] = toStdVec(d.baseColorFactor);
|
||||
}
|
||||
// we always copy metallic/roughness straight to the glTF:
|
||||
// - if there's a texture, they're linear multiplier
|
||||
// - if there's no texture, they're constants
|
||||
j["metallicFactor"] = d.metallic;
|
||||
j["roughnessFactor"] = d.roughness;
|
||||
if (d.metRoughTexture != nullptr) {
|
||||
j["metallicRoughnessTexture"] = *d.metRoughTexture;
|
||||
// if a texture is provided, throw away metallic/roughness values
|
||||
j["roughnessFactor"] = 1.0f;
|
||||
j["metallicFactor"] = 1.0f;
|
||||
} else {
|
||||
// without a texture, however, use metallic/roughness as constants
|
||||
j["metallicFactor"] = d.metallic;
|
||||
j["roughnessFactor"] = d.roughness;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ struct PrimitiveData {
|
|||
componentCount * draco::DataTypeLength(attribute.dracoComponentType),
|
||||
0);
|
||||
|
||||
const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size());
|
||||
const int dracoAttId = dracoMesh->AddAttribute(att, true, to_uint32(attribArr.size()));
|
||||
draco::PointAttribute* attPtr = dracoMesh->attribute(dracoAttId);
|
||||
|
||||
std::vector<uint8_t> buf(sizeof(T));
|
||||
|
|
|
@ -632,7 +632,7 @@ void RawModel::CreateMaterialModels(
|
|||
}
|
||||
}
|
||||
|
||||
int RawModel::GetNodeById(const long nodeId) const {
|
||||
int RawModel::GetNodeById(const uint32_t nodeId) const {
|
||||
for (size_t i = 0; i < nodes.size(); i++) {
|
||||
if (nodes[i].id == nodeId) {
|
||||
return (int)i;
|
||||
|
@ -641,7 +641,7 @@ int RawModel::GetNodeById(const long nodeId) const {
|
|||
return -1;
|
||||
}
|
||||
|
||||
int RawModel::GetSurfaceById(const long surfaceId) const {
|
||||
int RawModel::GetSurfaceById(const uint32_t surfaceId) const {
|
||||
for (size_t i = 0; i < surfaces.size(); i++) {
|
||||
if (surfaces[i].id == surfaceId) {
|
||||
return (int)i;
|
||||
|
|
|
@ -10,171 +10,41 @@
|
|||
#include "File_Utils.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define _getcwd getcwd
|
||||
#define _mkdir(a) mkdir(a, 0777)
|
||||
#elif defined(_WIN32)
|
||||
#include <direct.h>
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <direct.h>
|
||||
#include <process.h>
|
||||
#endif
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
#include "String_Utils.hpp"
|
||||
|
||||
namespace FileUtils {
|
||||
|
||||
std::string GetCurrentFolder() {
|
||||
char cwd[StringUtils::MAX_PATH_LENGTH];
|
||||
if (!_getcwd(cwd, sizeof(cwd))) {
|
||||
return std::string();
|
||||
}
|
||||
cwd[sizeof(cwd) - 1] = '\0';
|
||||
StringUtils::GetCleanPath(cwd, cwd, StringUtils::PATH_UNIX);
|
||||
const size_t length = strlen(cwd);
|
||||
if (cwd[length - 1] != '/' && length < StringUtils::MAX_PATH_LENGTH - 1) {
|
||||
cwd[length + 0] = '/';
|
||||
cwd[length + 1] = '\0';
|
||||
}
|
||||
return std::string(cwd);
|
||||
}
|
||||
|
||||
bool FileExists(const std::string& filePath) {
|
||||
std::ifstream stream(filePath);
|
||||
return stream.good();
|
||||
}
|
||||
|
||||
bool FolderExists(const std::string& folderPath) {
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
DIR* dir = opendir(folderPath.c_str());
|
||||
if (dir) {
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
const DWORD ftyp = GetFileAttributesA(folderPath.c_str());
|
||||
if (ftyp == INVALID_FILE_ATTRIBUTES) {
|
||||
return false; // bad path
|
||||
}
|
||||
return (ftyp & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MatchExtension(const char* fileExtension, const char* matchExtensions) {
|
||||
if (matchExtensions[0] == '\0') {
|
||||
return true;
|
||||
}
|
||||
if (fileExtension[0] == '.') {
|
||||
fileExtension++;
|
||||
}
|
||||
for (const char* end = matchExtensions; end[0] != '\0';) {
|
||||
for (; end[0] == ';'; end++) {
|
||||
}
|
||||
const char* ext = end;
|
||||
for (; end[0] != ';' && end[0] != '\0'; end++) {
|
||||
}
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
if (strncasecmp(fileExtension, ext, end - ext) == 0)
|
||||
#else
|
||||
if (_strnicmp(fileExtension, ext, end - ext) == 0)
|
||||
#endif
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> ListFolderFiles(const char* folder, const char* matchExtensions) {
|
||||
std::vector<std::string> ListFolderFiles(
|
||||
const std::string folder,
|
||||
const std::set<std::string>& matchExtensions) {
|
||||
std::vector<std::string> fileList;
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
DIR* dir = opendir(strlen(folder) > 0 ? folder : ".");
|
||||
if (dir != nullptr) {
|
||||
for (;;) {
|
||||
struct dirent* dp = readdir(dir);
|
||||
if (dp == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (dp->d_type == DT_DIR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* fileName = dp->d_name;
|
||||
const char* fileExt = strrchr(fileName, '.');
|
||||
|
||||
if (!fileExt || !MatchExtension(fileExt, matchExtensions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fileList.emplace_back(fileName);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
#else
|
||||
std::string pathStr = folder;
|
||||
pathStr += "*";
|
||||
|
||||
WIN32_FIND_DATA FindFileData;
|
||||
HANDLE hFind = FindFirstFile(pathStr.c_str(), &FindFileData);
|
||||
if (hFind != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
std::string fileName = FindFileData.cFileName;
|
||||
std::string::size_type extPos = fileName.rfind('.');
|
||||
if (extPos != std::string::npos &&
|
||||
MatchExtension(fileName.substr(extPos + 1).c_str(), matchExtensions)) {
|
||||
fileList.push_back(fileName);
|
||||
for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(folder)) {
|
||||
const std::filesystem::path& path = entry.path();
|
||||
if (matchExtensions.find(path.extension().string()) != matchExtensions.end()) {
|
||||
fileList.push_back(path.string());
|
||||
}
|
||||
}
|
||||
} while (FindNextFile(hFind, &FindFileData));
|
||||
|
||||
FindClose(hFind);
|
||||
}
|
||||
#endif
|
||||
return fileList;
|
||||
}
|
||||
|
||||
bool CreatePath(const char* path) {
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
StringUtils::PathSeparator separator = StringUtils::PATH_UNIX;
|
||||
#else
|
||||
StringUtils::PathSeparator separator = StringUtils::PATH_WIN;
|
||||
#endif
|
||||
std::string folder = StringUtils::GetFolderString(path);
|
||||
std::string clean = StringUtils::GetCleanPathString(folder, separator);
|
||||
std::string build = clean;
|
||||
for (int i = 0; i < clean.length(); i++) {
|
||||
if (clean[i] == separator && i > 0) {
|
||||
build[i] = '\0';
|
||||
if (i > 1 || build[1] != ':') {
|
||||
if (_mkdir(build.c_str()) != 0 && errno != EEXIST) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
build[i] = clean[i];
|
||||
}
|
||||
bool CreatePath(const std::string path) {
|
||||
const auto& parent = std::filesystem::path(path).parent_path();
|
||||
if (parent.empty()) {
|
||||
// this is either CWD or std::filesystem root; either way it exists
|
||||
return true;
|
||||
}
|
||||
if (std::filesystem::exists(parent)) {
|
||||
return std::filesystem::is_directory(parent);
|
||||
}
|
||||
return std::filesystem::create_directory(parent);
|
||||
}
|
||||
|
||||
bool CopyFile(const std::string& srcFilename, const std::string& dstFilename, bool createPath) {
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -19,13 +23,51 @@ std::string GetCurrentFolder();
|
|||
bool FileExists(const std::string& folderPath);
|
||||
bool FolderExists(const std::string& folderPath);
|
||||
|
||||
bool MatchExtension(const char* fileExtension, const char* matchExtensions);
|
||||
std::vector<std::string> ListFolderFiles(const char* folder, const char* matchExtensions);
|
||||
std::vector<std::string> ListFolderFiles(
|
||||
const std::string folder,
|
||||
const std::set<std::string>& matchExtensions);
|
||||
|
||||
bool CreatePath(const char* path);
|
||||
bool CreatePath(std::string path);
|
||||
|
||||
bool CopyFile(
|
||||
const std::string& srcFilename,
|
||||
const std::string& dstFilename,
|
||||
bool createPath = false);
|
||||
|
||||
inline std::string GetCurrentFolder() {
|
||||
return std::filesystem::current_path().string() + "/";
|
||||
}
|
||||
|
||||
inline bool FileExists(const std::string& filePath) {
|
||||
return std::filesystem::exists(filePath) && std::filesystem::is_regular_file(filePath);
|
||||
}
|
||||
|
||||
inline bool FolderExists(const std::string& folderPath) {
|
||||
return std::filesystem::exists(folderPath) && std::filesystem::is_directory(folderPath);
|
||||
}
|
||||
|
||||
inline std::string GetFolderString(const std::string& path) {
|
||||
return std::filesystem::path(path).parent_path().string();
|
||||
}
|
||||
|
||||
inline std::string GetCanonicalPath(const std::string& path) {
|
||||
return std::filesystem::canonical(path).string();
|
||||
}
|
||||
|
||||
inline std::string GetFileNameString(const std::string& path) {
|
||||
return std::filesystem::canonical(path).filename().string();
|
||||
}
|
||||
|
||||
inline std::string GetFileBaseString(const std::string& path) {
|
||||
return std::filesystem::canonical(path).stem().string();
|
||||
}
|
||||
|
||||
inline std::optional<std::string> GetFileSuffix(const std::string& path) {
|
||||
const auto& extension = std::filesystem::canonical(path).extension();
|
||||
if (extension.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return extension.string().substr(1);
|
||||
}
|
||||
|
||||
} // namespace FileUtils
|
||||
|
|
|
@ -9,72 +9,12 @@
|
|||
|
||||
#include "String_Utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace StringUtils {
|
||||
|
||||
PathSeparator operator!(const PathSeparator& s) {
|
||||
return (s == PATH_WIN) ? PATH_UNIX : PATH_WIN;
|
||||
}
|
||||
|
||||
PathSeparator GetPathSeparator() {
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
return PATH_UNIX;
|
||||
#else
|
||||
return PATH_WIN;
|
||||
#endif
|
||||
}
|
||||
const std::string NormalizePath(const std::string& path) {
|
||||
PathSeparator separator = GetPathSeparator();
|
||||
char replace;
|
||||
if (separator == PATH_WIN) {
|
||||
replace = PATH_UNIX;
|
||||
} else {
|
||||
replace = PATH_WIN;
|
||||
}
|
||||
std::string normalizedPath = path;
|
||||
for (size_t s = normalizedPath.find(replace, 0); s != std::string::npos;
|
||||
s = normalizedPath.find(replace, s)) {
|
||||
normalizedPath[s] = separator;
|
||||
}
|
||||
return normalizedPath;
|
||||
}
|
||||
|
||||
const std::string GetFolderString(const std::string& path) {
|
||||
size_t s = path.rfind(PATH_WIN);
|
||||
s = (s != std::string::npos) ? s : path.rfind(PATH_UNIX);
|
||||
return path.substr(0, s + 1);
|
||||
}
|
||||
|
||||
const std::string GetCleanPathString(const std::string& path, const PathSeparator separator) {
|
||||
std::string cleanPath = path;
|
||||
for (size_t s = cleanPath.find(!separator, 0); s != std::string::npos;
|
||||
s = cleanPath.find(!separator, s)) {
|
||||
cleanPath[s] = separator;
|
||||
}
|
||||
return cleanPath;
|
||||
}
|
||||
|
||||
const std::string GetFileNameString(const std::string& path) {
|
||||
size_t s = path.rfind(PATH_WIN);
|
||||
s = (s != std::string::npos) ? s : path.rfind(PATH_UNIX);
|
||||
return path.substr(s + 1, std::string::npos);
|
||||
}
|
||||
|
||||
const std::string GetFileBaseString(const std::string& path) {
|
||||
const std::string fileName = GetFileNameString(path);
|
||||
return fileName.substr(0, fileName.rfind('.')).c_str();
|
||||
}
|
||||
|
||||
const std::string GetFileSuffixString(const std::string& path) {
|
||||
const std::string fileName = GetFileNameString(path);
|
||||
size_t pos = fileName.rfind('.');
|
||||
if (pos == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
return fileName.substr(++pos);
|
||||
}
|
||||
|
||||
int CompareNoCase(const std::string& s1, const std::string& s2) {
|
||||
return strncasecmp(s1.c_str(), s2.c_str(), MAX_PATH_LENGTH);
|
||||
return strncasecmp(s1.c_str(), s2.c_str(), std::max(s1.length(), s2.length()));
|
||||
}
|
||||
|
||||
} // namespace StringUtils
|
||||
|
|
|
@ -21,34 +21,8 @@
|
|||
|
||||
namespace StringUtils {
|
||||
|
||||
static const unsigned int MAX_PATH_LENGTH = 1024;
|
||||
|
||||
enum PathSeparator { PATH_WIN = '\\', PATH_UNIX = '/' };
|
||||
|
||||
PathSeparator operator!(const PathSeparator& s);
|
||||
|
||||
PathSeparator GetPathSeparator();
|
||||
const std::string NormalizePath(const std::string& path);
|
||||
|
||||
const std::string GetCleanPathString(
|
||||
const std::string& path,
|
||||
const PathSeparator separator = PATH_WIN);
|
||||
|
||||
template <size_t size>
|
||||
void GetCleanPath(char (&dest)[size], const char* path, const PathSeparator separator = PATH_WIN) {
|
||||
size_t len = size - 1;
|
||||
strncpy(dest, path, len);
|
||||
char* destPtr = dest;
|
||||
while ((destPtr = strchr(destPtr, !separator)) != nullptr) {
|
||||
*destPtr = separator;
|
||||
}
|
||||
inline int CompareNoCase(const std::string& s1, const std::string& s2) {
|
||||
return strncasecmp(s1.c_str(), s2.c_str(), std::max(s1.length(), s2.length()));
|
||||
}
|
||||
|
||||
const std::string GetFolderString(const std::string& path);
|
||||
const std::string GetFileNameString(const std::string& path);
|
||||
const std::string GetFileBaseString(const std::string& path);
|
||||
const std::string GetFileSuffixString(const std::string& path);
|
||||
|
||||
int CompareNoCase(const std::string& s1, const std::string& s2);
|
||||
|
||||
} // namespace StringUtils
|
||||
|
|
Loading…
Reference in New Issue